Tests xllm_norm, xllm_activation, xllm_rope, xllm_moe, ix_full_bridge, and 11 corex_*.so modules with correctness checks against PyTorch reference. Run: python3 test_ex_engine_cuda.py
272 lines
9.4 KiB
Python
272 lines
9.4 KiB
Python
"""
|
|
test_ex_engine_cuda.py — Test all ex_engine CUDA kernels on BI-V100.
|
|
|
|
Tests every prebuilt .so file by loading it, calling each exported function
|
|
with synthetic data, and verifying output against PyTorch reference.
|
|
|
|
Run: python3 test_ex_engine_cuda.py [--vllm-root /path/to/vllm]
|
|
|
|
Source mapping:
|
|
xllm_norm.so → norm.cu + xllm_norm_bind.cpp
|
|
xllm_activation.so → activation.cu + xllm_activation_bind.cpp
|
|
xllm_rope.so → rope.cu + xllm_rope_bind.cpp
|
|
xllm_cache.so → reshape_paged_cache.cu + block_copy.cu + xllm_cache_bind.cpp
|
|
xllm_moe.so → moe_fused_topk.cu + moe_compute_index.cu + moe_combine.cu + xllm_moe_bind.cpp
|
|
ix_full_bridge.so → ix_full_bridge_v2.cpp → ixformer::infer namespace
|
|
corex_*.so → corex_*.cu (16 individual kernels)
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import importlib
|
|
import importlib.util
|
|
import argparse
|
|
import traceback
|
|
import torch
|
|
import torch.nn.functional as F
|
|
|
|
|
|
def load_so(name, search_dirs):
|
|
"""Load a .so by name from search dirs."""
|
|
for d in search_dirs:
|
|
path = os.path.join(d, f"{name}.so")
|
|
if os.path.isfile(path):
|
|
try:
|
|
spec = importlib.util.spec_from_file_location(name, path)
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
return mod, path
|
|
except Exception as e:
|
|
print(f" LOAD FAIL {path}: {e}")
|
|
return None, None
|
|
|
|
|
|
def test_xllm_norm(mod):
|
|
"""Test rms_norm and fused_add_rms_norm."""
|
|
T, H = 4, 128
|
|
x = torch.randn(T, H, dtype=torch.float16, device="cuda")
|
|
w = torch.ones(H, dtype=torch.float16, device="cuda")
|
|
eps = 1e-6
|
|
|
|
# rms_norm
|
|
out = torch.empty_like(x)
|
|
mod.rms_norm(out, x, w, eps)
|
|
|
|
# reference
|
|
var = x.float().pow(2).mean(-1, keepdim=True)
|
|
ref = (x.float() * torch.rsqrt(var + eps) * w.float()).half()
|
|
err = (out.float() - ref.float()).abs().max().item()
|
|
assert err < 0.01, f"rms_norm max error {err}"
|
|
print(f" rms_norm: max_err={err:.6f} ✓")
|
|
|
|
# fused_add_rms_norm
|
|
if hasattr(mod, 'fused_add_rms_norm'):
|
|
x2 = torch.randn(T, H, dtype=torch.float16, device="cuda")
|
|
res = torch.randn(T, H, dtype=torch.float16, device="cuda")
|
|
out2 = torch.empty_like(x2)
|
|
res_out = torch.empty_like(x2)
|
|
mod.fused_add_rms_norm(out2, x2, res, w, eps)
|
|
combined = x2.float() + res.float()
|
|
var2 = combined.pow(2).mean(-1, keepdim=True)
|
|
ref2 = (combined * torch.rsqrt(var2 + eps) * w.float()).half()
|
|
err2 = (out2.float() - ref2.float()).abs().max().item()
|
|
assert err2 < 0.01, f"fused_add_rms_norm max error {err2}"
|
|
print(f" fused_add_rms_norm: max_err={err2:.6f} ✓")
|
|
|
|
|
|
def test_xllm_activation(mod):
|
|
"""Test silu_and_mul."""
|
|
T, I = 4, 64
|
|
x = torch.randn(T, 2 * I, dtype=torch.float16, device="cuda")
|
|
out = torch.empty(T, I, dtype=torch.float16, device="cuda")
|
|
mod.silu_and_mul(out, x)
|
|
|
|
gate, up = x.float().chunk(2, dim=-1)
|
|
ref = (F.silu(gate) * up).half()
|
|
err = (out.float() - ref.float()).abs().max().item()
|
|
assert err < 0.01, f"silu_and_mul max error {err}"
|
|
print(f" silu_and_mul: max_err={err:.6f} ✓")
|
|
|
|
|
|
def test_xllm_rope(mod):
|
|
"""Test rotary_embedding."""
|
|
T, NH, HD = 4, 8, 128
|
|
q = torch.randn(T, NH * HD, dtype=torch.float16, device="cuda")
|
|
k = torch.randn(T, NH * HD, dtype=torch.float16, device="cuda")
|
|
positions = torch.arange(T, device="cuda", dtype=torch.int64)
|
|
cos_sin_cache = torch.randn(1024, HD, dtype=torch.float16, device="cuda")
|
|
|
|
q_orig = q.clone()
|
|
k_orig = k.clone()
|
|
mod.rotary_embedding(positions, q, k, HD, cos_sin_cache, True)
|
|
|
|
# Just verify it modified q and k (not NaN)
|
|
assert not q.isnan().any(), "rotary_embedding produced NaN in query"
|
|
assert not k.isnan().any(), "rotary_embedding produced NaN in key"
|
|
assert not torch.equal(q, q_orig), "rotary_embedding didn't modify query"
|
|
print(f" rotary_embedding: no NaN, values modified ✓")
|
|
|
|
|
|
def test_xllm_moe(mod):
|
|
"""Test moe_fused_topk."""
|
|
if not hasattr(mod, 'moe_fused_topk'):
|
|
print(" moe_fused_topk: not found, skip")
|
|
return
|
|
T, E, K = 4, 64, 8
|
|
logits = torch.randn(T, E, dtype=torch.float32, device="cuda")
|
|
weights, ids = mod.moe_fused_topk(logits, K, True, None, "softmax")
|
|
|
|
assert weights.shape == (T, K), f"shape mismatch: {weights.shape}"
|
|
assert ids.shape == (T, K), f"shape mismatch: {ids.shape}"
|
|
assert not weights.isnan().any(), "topk weights NaN"
|
|
assert (ids >= 0).all() and (ids < E).all(), "topk ids out of range"
|
|
print(f" moe_fused_topk: shapes correct, no NaN ✓")
|
|
|
|
|
|
def test_ix_full_bridge(mod):
|
|
"""Test ix_full_bridge functions."""
|
|
# silu_and_mul
|
|
if hasattr(mod, 'silu_and_mul'):
|
|
T, I = 4, 64
|
|
x = torch.randn(T, 2 * I, dtype=torch.float16, device="cuda")
|
|
out = mod.silu_and_mul(x)
|
|
gate, up = x.float().chunk(2, dim=-1)
|
|
ref = (F.silu(gate) * up).half()
|
|
err = (out.float() - ref.float()).abs().max().item()
|
|
assert err < 0.01, f"silu_and_mul max error {err}"
|
|
print(f" silu_and_mul: max_err={err:.6f} ✓")
|
|
|
|
# rms_norm
|
|
if hasattr(mod, 'rms_norm'):
|
|
T, H = 4, 128
|
|
x = torch.randn(T, H, dtype=torch.float16, device="cuda")
|
|
w = torch.ones(H, dtype=torch.float16, device="cuda")
|
|
out = torch.empty_like(x)
|
|
mod.rms_norm(out, x, w, 1e-6)
|
|
assert not out.isnan().any(), "rms_norm NaN"
|
|
print(f" rms_norm: no NaN ✓")
|
|
|
|
# linear
|
|
if hasattr(mod, 'linear'):
|
|
M, K, N = 4, 128, 256
|
|
x = torch.randn(M, K, dtype=torch.float16, device="cuda")
|
|
w = torch.randn(N, K, dtype=torch.float16, device="cuda")
|
|
out = mod.linear(x, w)
|
|
ref = F.linear(x, w)
|
|
err = (out.float() - ref.float()).abs().max().item()
|
|
print(f" linear: max_err={err:.4f} {'✓' if err < 1.0 else '✗'}")
|
|
|
|
fns = [x for x in dir(mod) if not x.startswith("_")]
|
|
print(f" exported functions: {fns}")
|
|
|
|
|
|
def test_corex_module(name, mod):
|
|
"""Basic smoke test for corex_*.so — check it loaded and has functions."""
|
|
fns = [x for x in dir(mod) if not x.startswith("_")]
|
|
if not fns:
|
|
print(f" {name}: no exported functions ✗")
|
|
return
|
|
print(f" {name}: {len(fns)} functions: {fns[:5]}{'...' if len(fns)>5 else ''} ✓")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--vllm-root", default=None)
|
|
parser.add_argument("--prebuilt-dir", default=None)
|
|
args = parser.parse_args()
|
|
|
|
# Build search paths
|
|
here = os.path.dirname(os.path.abspath(__file__))
|
|
search_dirs = []
|
|
if args.prebuilt_dir:
|
|
search_dirs.append(args.prebuilt_dir)
|
|
search_dirs.append(os.path.join(here, "qwen3_6_scripts", "prebuilt",
|
|
"corex-3.2.3-ivcore10"))
|
|
if args.vllm_root:
|
|
search_dirs.append(args.vllm_root)
|
|
try:
|
|
import vllm
|
|
search_dirs.append(os.path.dirname(vllm.__file__))
|
|
except ImportError:
|
|
pass
|
|
|
|
print(f"Search dirs: {search_dirs}")
|
|
print(f"CUDA available: {torch.cuda.is_available()}")
|
|
if torch.cuda.is_available():
|
|
print(f"Device: {torch.cuda.get_device_name(0)}")
|
|
print()
|
|
|
|
results = {"pass": 0, "fail": 0, "skip": 0}
|
|
|
|
# Test xllm_*.so
|
|
for name, test_fn in [
|
|
("xllm_norm", test_xllm_norm),
|
|
("xllm_activation", test_xllm_activation),
|
|
("xllm_rope", test_xllm_rope),
|
|
("xllm_moe", test_xllm_moe),
|
|
]:
|
|
print(f"[{name}]")
|
|
mod, path = load_so(name, search_dirs)
|
|
if mod is None:
|
|
print(f" NOT FOUND — skip")
|
|
results["skip"] += 1
|
|
continue
|
|
print(f" loaded from {path}")
|
|
try:
|
|
test_fn(mod)
|
|
results["pass"] += 1
|
|
except Exception as e:
|
|
print(f" FAIL: {e}")
|
|
traceback.print_exc()
|
|
results["fail"] += 1
|
|
print()
|
|
|
|
# Test ix_full_bridge.so
|
|
print("[ix_full_bridge]")
|
|
mod, path = load_so("ix_full_bridge", search_dirs)
|
|
if mod is None:
|
|
print(" NOT FOUND — skip")
|
|
results["skip"] += 1
|
|
else:
|
|
print(f" loaded from {path}")
|
|
try:
|
|
test_ix_full_bridge(mod)
|
|
results["pass"] += 1
|
|
except Exception as e:
|
|
print(f" FAIL: {e}")
|
|
traceback.print_exc()
|
|
results["fail"] += 1
|
|
print()
|
|
|
|
# Smoke test corex_*.so
|
|
print("[corex_*.so smoke tests]")
|
|
corex_names = [
|
|
"corex_attn_head_rms_norm", "corex_gdn_causal_conv",
|
|
"corex_gdn_packed_decode", "corex_gdn_qk_map",
|
|
"corex_moe_topk_softmax", "corex_moe_direct_routed",
|
|
"corex_moe_exact_reduce", "corex_moe_index_combine",
|
|
"corex_moe_weight_gather", "corex_paged_kv_gather",
|
|
"corex_fused_paged_prefill",
|
|
]
|
|
for name in corex_names:
|
|
mod, path = load_so(name, search_dirs)
|
|
if mod is None:
|
|
print(f" {name}: NOT FOUND")
|
|
results["skip"] += 1
|
|
else:
|
|
try:
|
|
test_corex_module(name, mod)
|
|
results["pass"] += 1
|
|
except Exception as e:
|
|
print(f" {name}: FAIL {e}")
|
|
results["fail"] += 1
|
|
|
|
print()
|
|
print(f"=== RESULTS: {results['pass']} pass, {results['fail']} fail, "
|
|
f"{results['skip']} skip ===")
|
|
return 0 if results["fail"] == 0 else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|