fix: fail-fast on ix_bridge failure + probe script for real machine

1. ix_bridge.py: RuntimeError instead of silent PyTorch fallback
   If JIT compile fails, crash immediately with diagnostic message.
   0 score with no error log is worse than a visible crash.

2. qwen3_5.py: explicit WARNING log on import failure (not silent)
   Shows exact error so we can diagnose from docker log.

3. probe_ixformer_symbols.py: definitive test for real machine
   - Finds all ixformer .so files
   - nm/objdump for topk_softmax C++ symbol
   - Checks Python bindings
   - Attempts JIT compile + link (the real test)
   - Prints PASS/FAIL with next-step instructions

Run on real machine: python3 probe_ixformer_symbols.py
This commit is contained in:
EX Engine
2026-08-10 03:04:50 +00:00
parent d21b2505bb
commit e04a3bace9
3 changed files with 243 additions and 15 deletions

View File

@@ -57,21 +57,15 @@ def topk_softmax(gating_output: torch.Tensor, topk: int, renormalize: bool = Tru
"""
Fused topk+softmax via ixformer C++ API.
Args:
gating_output: (num_tokens, num_experts) router logits
topk: number of experts to select
renormalize: whether to renormalize weights
Returns:
(topk_weights, topk_indices) — both (num_tokens, topk)
FAIL FAST: if bridge not available, raises RuntimeError immediately.
No silent fallback — 0 score with no error log is worse than a crash.
"""
if not _ix_bridge_available:
if not _load_bridge():
# Fallback to pure PyTorch
probs = torch.softmax(gating_output.float(), dim=-1)
topk_w, topk_ids = torch.topk(probs, topk, dim=-1)
if renormalize:
topk_w = topk_w / topk_w.sum(dim=-1, keepdim=True)
return topk_w, topk_ids.to(torch.int32)
raise RuntimeError(
"ix_moe_bridge: FATAL — ixformer C++ topk_softmax not available. "
"JIT compile failed. Run probe_ixformer_symbols.py on real machine "
"to diagnose. Cannot fall back silently — would produce 0 score."
)
return _ix_bridge.topk_softmax(gating_output, topk, renormalize)

230
probe_ixformer_symbols.py Normal file
View File

@@ -0,0 +1,230 @@
#!/usr/bin/env python3
"""
probe_ixformer_symbols.py — 在真机上跑,探测 ixformer C++ 符号表
用法: python3 probe_ixformer_symbols.py
输出:
1. ixformer 所有 .so 文件路径
2. 每个 .so 里包含 topk_softmax / moe / gdn / attention 的符号
3. 结论ix_moe_bridge.cpp 能不能链接成功
"""
import subprocess, sys, os, glob
def find_ixformer_so():
"""找到 ixformer 的所有 .so 文件"""
paths = []
# 方法1: 从 Python import 路径找
try:
import ixformer
pkg_dir = os.path.dirname(ixformer.__file__)
paths.extend(glob.glob(os.path.join(pkg_dir, "**/*.so"), recursive=True))
paths.extend(glob.glob(os.path.join(pkg_dir, "**/*.so.*"), recursive=True))
print(f"[1] ixformer package dir: {pkg_dir}")
except ImportError:
print("[1] ixformer not importable")
# 方法2: 搜索常见路径
for base in ["/usr/local/corex/lib64", "/usr/local/corex/lib",
"/usr/local/lib", "/usr/lib"]:
paths.extend(glob.glob(os.path.join(base, "**/libixformer*"), recursive=True))
paths.extend(glob.glob(os.path.join(base, "**/*ixformer*.so"), recursive=True))
paths.extend(glob.glob(os.path.join(base, "**/libixattn*"), recursive=True))
paths.extend(glob.glob(os.path.join(base, "**/libixinfer*"), recursive=True))
# 方法3: 从 torch 找已加载的 .so
try:
import torch
# ixformer 的 C++ 后端可能是 _ixformer_torch.so 或 _C.so
try:
import ixformer._ixformer_torch as ixt
if hasattr(ixt, '__file__') and ixt.__file__:
paths.append(ixt.__file__)
print(f"[2] _ixformer_torch: {ixt.__file__}")
except:
pass
try:
import ixformer._C as ic
if hasattr(ic, '__file__') and ic.__file__:
paths.append(ic.__file__)
print(f"[2] _C: {ic.__file__}")
except:
pass
except:
pass
return list(set(paths))
def nm_grep(so_path, patterns):
"""用 nm 查符号grep 匹配"""
results = []
try:
out = subprocess.run(
["nm", "-D", "--demangle", so_path],
capture_output=True, text=True, timeout=10)
for line in out.stdout.splitlines():
for p in patterns:
if p.lower() in line.lower():
results.append(line.strip())
except Exception as e:
# nm 可能不存在,用 objdump
try:
out = subprocess.run(
["objdump", "-T", so_path],
capture_output=True, text=True, timeout=10)
for line in out.stdout.splitlines():
for p in patterns:
if p.lower() in line.lower():
results.append(line.strip())
except Exception as e2:
results.append(f"ERROR: nm/objdump failed: {e}, {e2}")
return results
def check_python_binding():
"""检查 Python 层面有没有 topk_softmax"""
print("\n=== Python Binding Check ===")
try:
import ixformer.functions as ixf
attrs = dir(ixf)
moe_attrs = [a for a in attrs if 'moe' in a.lower() or 'topk' in a.lower()
or 'softmax' in a.lower() or 'expert' in a.lower()]
print(f" ixformer.functions MoE-related: {moe_attrs}")
if not moe_attrs:
print(f" ixformer.functions ALL ({len(attrs)}): {attrs}")
except Exception as e:
print(f" ixformer.functions: {e}")
try:
import ixformer
# 搜索所有子模块
for attr_name in dir(ixformer):
obj = getattr(ixformer, attr_name)
if hasattr(obj, 'topk_softmax'):
print(f" FOUND: ixformer.{attr_name}.topk_softmax")
if hasattr(obj, 'moe_topk_softmax'):
print(f" FOUND: ixformer.{attr_name}.moe_topk_softmax")
except:
pass
def check_torch_ops():
"""检查 torch.ops 注册"""
print("\n=== torch.ops Check ===")
try:
import torch
# 检查是否有 ixformer 注册的 ops
for ns in ['ixformer', '_ixformer', 'ixf', '_C']:
try:
ns_obj = getattr(torch.ops, ns, None)
if ns_obj:
ops = [x for x in dir(ns_obj) if 'topk' in x.lower() or 'moe' in x.lower()]
if ops:
print(f" torch.ops.{ns} MoE ops: {ops}")
else:
print(f" torch.ops.{ns} exists but no MoE ops: {dir(ns_obj)[:10]}...")
except:
pass
except:
pass
def try_jit_compile():
"""尝试 JIT 编译 ix_moe_bridge.cpp 看链接是否成功"""
print("\n=== JIT Compile Test ===")
test_cpp = "/tmp/ix_probe_test.cpp"
with open(test_cpp, "w") as f:
f.write("""
#include <torch/extension.h>
// Forward-declare — this is what ix_moe_bridge.cpp needs
namespace ixformer { namespace infer {
void topk_softmax(torch::Tensor&, torch::Tensor&, torch::Tensor&,
torch::Tensor&, bool);
}}
void test_link() {
auto a = torch::empty({1,1});
auto b = torch::empty({1,1});
auto c = torch::empty({1,1});
auto d = torch::empty({1,1});
ixformer::infer::topk_softmax(a, b, c, d, false);
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("test_link", &test_link);
}
""")
try:
from torch.utils.cpp_extension import load
ext = load(name="ix_probe_test", sources=[test_cpp],
extra_cflags=["-O0"], verbose=True)
print(" JIT COMPILE + LINK: SUCCESS ✓")
print(" ixformer::infer::topk_softmax symbol resolved!")
return True
except Exception as e:
err = str(e)
if "undefined reference" in err or "undefined symbol" in err:
print(f" JIT LINK FAILED: symbol not found in .so")
print(f" Error: {err[:500]}")
else:
print(f" JIT COMPILE FAILED: {err[:500]}")
return False
if __name__ == "__main__":
print("=" * 70)
print("ixformer Symbol Probe")
print("=" * 70)
# Step 1: Find .so files
print("\n=== .so Files ===")
so_files = find_ixformer_so()
if not so_files:
print(" No ixformer .so files found!")
for f in sorted(set(so_files)):
size = os.path.getsize(f) if os.path.exists(f) else 0
print(f" {f} ({size/1024/1024:.1f} MB)")
# Step 2: Search for symbols
patterns = ["topk_softmax", "moe_topk", "topk_gating",
"moe_compute_token", "moe_expand", "moe_output_reduce",
"moe_w16a16", "group_gemm"]
print("\n=== Symbol Search (MoE-related) ===")
found_any = False
for f in sorted(set(so_files)):
results = nm_grep(f, patterns)
if results:
found_any = True
print(f"\n {os.path.basename(f)}:")
for r in results[:20]:
print(f" {r}")
if not found_any:
print(" No MoE symbols found in any .so")
# Also search for ANY ixformer::infer symbols
print("\n=== Symbol Search (ixformer::infer namespace) ===")
for f in sorted(set(so_files)):
results = nm_grep(f, ["ixformer", "infer"])
if results:
print(f"\n {os.path.basename(f)} ({len(results)} matches):")
for r in results[:30]:
print(f" {r}")
# Step 3: Python binding
check_python_binding()
# Step 4: torch.ops
check_torch_ops()
# Step 5: JIT compile test (the definitive answer)
jit_ok = try_jit_compile()
# Summary
print("\n" + "=" * 70)
if jit_ok:
print("RESULT: ix_moe_bridge.cpp CAN link to ixformer::infer::topk_softmax")
print("ACTION: proceed with C++ bridge approach")
else:
print("RESULT: ix_moe_bridge.cpp CANNOT link to ixformer C++ API")
print("ACTION: need alternative — options:")
print(" A) Build topk_softmax kernel from upstream_ref/xllm CUDA source")
print(" B) Build from upstream_ref/ds_vllm/csrc/moe/topk_softmax_kernels.cu")
print(" C) Keep PyTorch path but add explicit error logging")
print("=" * 70)

View File

@@ -74,6 +74,7 @@ except ImportError:
# ix_bridge: C++ bridge to ixformer::infer::topk_softmax (bypasses missing Python binding)
_ix_bridge_module = None
_ix_bridge_available = False
_ix_topk_softmax = None
try:
from ex_engine.python.ix_bridge import topk_softmax as _ix_topk_softmax
_ix_bridge_available = True
@@ -88,8 +89,11 @@ except ImportError:
from ex_engine.python.ix_bridge import topk_softmax as _ix_topk_softmax
_ix_bridge_available = True
logger.info("ix_bridge: ixformer C++ topk_softmax available (deployed path)")
except ImportError:
logger.info("ix_bridge: not available, MoE uses PyTorch topk")
except ImportError as e:
# NOT silent: log the exact error so we can diagnose from docker logs
logger.warning(
"ix_bridge: IMPORT FAILED (%s). MoE will use PyTorch topk. "
"This is 3x slower. Run probe_ixformer_symbols.py to diagnose.", e)
_corex_gdn_available = False
_corex_moe_available = False