113 lines
3.9 KiB
Python
113 lines
3.9 KiB
Python
|
|
"""
|
||
|
|
EngineX Bridge — wires EngineX dispatch into vllm's _custom_ops.py
|
||
|
|
|
||
|
|
This module monkey-patches _custom_ops functions to use EngineX's
|
||
|
|
three-tier dispatch instead of direct ixformer calls.
|
||
|
|
|
||
|
|
The key fix: vllm_moe_topk_softmax is MISSING from ixformer.functions
|
||
|
|
on our BI-V100 image, causing every MoE forward pass to crash.
|
||
|
|
EngineX provides a PyTorch replacement that keeps the model running.
|
||
|
|
|
||
|
|
Usage in patch_ops.sh:
|
||
|
|
python -c "import enginex.bridge; enginex.bridge.patch_custom_ops()"
|
||
|
|
|
||
|
|
Or at runtime startup:
|
||
|
|
from enginex.bridge import patch_custom_ops
|
||
|
|
patch_custom_ops()
|
||
|
|
"""
|
||
|
|
|
||
|
|
import importlib
|
||
|
|
import logging
|
||
|
|
import sys
|
||
|
|
|
||
|
|
logger = logging.getLogger("enginex.bridge")
|
||
|
|
|
||
|
|
|
||
|
|
def patch_custom_ops():
|
||
|
|
"""
|
||
|
|
Patch vllm._custom_ops to use EngineX dispatch.
|
||
|
|
|
||
|
|
Strategy: only patch functions that are KNOWN BROKEN.
|
||
|
|
We do NOT touch working ixformer ops (silu_and_mul, rms_norm, etc.)
|
||
|
|
because ixformer's implementations are faster.
|
||
|
|
|
||
|
|
From docker log analysis, the BROKEN ops are:
|
||
|
|
1. topk_softmax — AttributeError: no 'vllm_moe_topk_softmax'
|
||
|
|
2. invoke_fused_moe_kernel — falls back to PyTorch on topk failure
|
||
|
|
3. moe_align_block_size — cascading failure from topk
|
||
|
|
"""
|
||
|
|
from enginex.dispatch.registry import get_registry
|
||
|
|
|
||
|
|
reg = get_registry()
|
||
|
|
reg.probe()
|
||
|
|
|
||
|
|
logger.info("EngineX bridge: patching broken ops in _custom_ops")
|
||
|
|
logger.info(reg.summary())
|
||
|
|
|
||
|
|
# Only patch if the module is already imported
|
||
|
|
custom_ops = sys.modules.get('vllm._custom_ops')
|
||
|
|
if custom_ops is None:
|
||
|
|
try:
|
||
|
|
custom_ops = importlib.import_module('vllm._custom_ops')
|
||
|
|
except ImportError:
|
||
|
|
logger.warning("vllm._custom_ops not found — skipping bridge")
|
||
|
|
return
|
||
|
|
|
||
|
|
# ---- Patch 1: moe_topk_softmax (THE critical fix) ----
|
||
|
|
moe_topk = reg.get_op("moe_topk_softmax")
|
||
|
|
if moe_topk:
|
||
|
|
original = getattr(custom_ops, 'topk_softmax', None)
|
||
|
|
if original:
|
||
|
|
# Check if the original actually works
|
||
|
|
try:
|
||
|
|
import ixformer.functions as ixf_F
|
||
|
|
_ = ixf_F.vllm_moe_topk_softmax
|
||
|
|
logger.info("EngineX: ixformer.vllm_moe_topk_softmax exists, "
|
||
|
|
"keeping original")
|
||
|
|
except (ImportError, AttributeError):
|
||
|
|
logger.info("EngineX: patching topk_softmax → EngineX "
|
||
|
|
f"({reg.get_backend('moe_topk_softmax').name})")
|
||
|
|
custom_ops.topk_softmax = moe_topk
|
||
|
|
|
||
|
|
# ---- Patch 2: moe_align_block_size ----
|
||
|
|
moe_align = reg.get_op("moe_align_block_size")
|
||
|
|
if moe_align:
|
||
|
|
try:
|
||
|
|
import ixformer.functions as ixf_F
|
||
|
|
_ = ixf_F.vllm_moe_align_block_size
|
||
|
|
except (ImportError, AttributeError):
|
||
|
|
logger.info("EngineX: patching moe_align_block_size → EngineX")
|
||
|
|
custom_ops.moe_align_block_size = moe_align
|
||
|
|
|
||
|
|
# ---- Report status ----
|
||
|
|
n_patched = 0
|
||
|
|
for op_name in reg.ops:
|
||
|
|
backend = reg.get_backend(op_name)
|
||
|
|
if backend is not None:
|
||
|
|
n_patched += 1
|
||
|
|
logger.info(f"EngineX bridge: {n_patched} operators registered, "
|
||
|
|
f"patched broken ops")
|
||
|
|
|
||
|
|
|
||
|
|
def patch_qwen3_5_moe():
|
||
|
|
"""
|
||
|
|
Patch the MoE dispatch in qwen3_5.py to use EngineX.
|
||
|
|
|
||
|
|
The model code tries:
|
||
|
|
1st: corex_moe.py (not in our image)
|
||
|
|
2nd: ixformer fused_moe (crashes on topk_softmax)
|
||
|
|
3rd: PyTorch loop (works but slow)
|
||
|
|
|
||
|
|
With EngineX, the topk_softmax fallback prevents the crash,
|
||
|
|
so tier 2 (ixformer) works for the GEMM even though topk
|
||
|
|
is handled by our PyTorch replacement.
|
||
|
|
"""
|
||
|
|
from enginex.dispatch.registry import get_registry
|
||
|
|
reg = get_registry()
|
||
|
|
reg.probe()
|
||
|
|
|
||
|
|
# The actual patching happens through _custom_ops
|
||
|
|
# Since qwen3_5.py calls ops.topk_softmax(), which calls _custom_ops,
|
||
|
|
# patching _custom_ops is sufficient.
|
||
|
|
logger.info("EngineX: MoE dispatch chain patched via _custom_ops bridge")
|