fix(CRITICAL): engine death on image request + stop overwriting base corex modules

Root cause from latest docker build log:
  ValueError: You set image=0 in --limit-mm-per-prompt, but found 1 items
  → Engine background task crashes → AsyncEngineDeadError → all subsequent 503

Fixes:
1. computility-run.yaml: add --limit-mm-per-prompt image=1
   Prevents multimodal ValueError from killing the engine process.

2. patch_ops.sh: DON'T overwrite base image's corex_gdn.py/corex_moe.py
   Comp 168 log proves base image's corex modules work with libcorex_gdn.so.
   Our overwrite broke CoreXGDN.__init__ (unexpected kwarg 'num_v_heads').
   Only deploy ours if base has NO corex modules at all.
   Also deploy corex_fa2.py if base lacks it.

3. qwen3_5.py: try multiple CoreXGDN init signatures
   Base image CoreXGDN may accept different kwargs than ours.
   Try kwargs form first, fall back to positional.

4. corex_gdn.py: accept both calling conventions in __init__
   Future-proof for when we DO need to deploy ours.

5. Copied upstream_ref headers: ilu_layer_fused_moe.h, ilu_layer_attention.h
   Last 2 missing ILU files from xllm. All 14/14 now present.
This commit is contained in:
Claude
2026-08-10 09:12:05 +00:00
parent ff3562b941
commit f87689a4ef
7 changed files with 435 additions and 24 deletions

101
DEVELOPMENT_STATUS.md Normal file
View File

@@ -0,0 +1,101 @@
# 系统开发状态分析 — 基于 comp 168 日志 AST 链条
## 日志分析: 两次运行对比
### 运行1: 基础镜像原生 (07-23, Sub168) — ✅ 正常
```
AST调用链条 (真机上确实在调用):
corex_gdn.py:56 → dlopen /usr/local/corex/lib64/libcorex_gdn.so ✅
corex_gdn.py:228 → GDN prefill fused kernel ✅
corex_gdn.py:138 → GDN decode fused kernel ✅
corex_moe.py:339 → MoE prefill: expert-grouped-wmma ✅
corex_moe.py:249 → MoE decode fused ✅
corex_fa2.py:333 → FA2 packed prefill (B=2 Hq=4 Hkv=1 D=256) ✅
corex_fa2.py:507 → FA2 paged chunked prefill ✅
corex_fa2.py:225 → FA2 paged decode (partition=256) ✅
结果: generation throughput ~22 tokens/s, 无NaN, 无OOM
```
### 运行2: 我们的Docker (08-07, Sub508) — ❌ 失败
```
问题链条:
max_model_len=100000 (yaml未生效! 应为80000)
max_num_seqs=1 (yaml未生效! 应为2)
qwen3_5.py NaN: GDN layer 0 frac=0.9998, layer 1-4 同样
_custom_ops.py topk_softmax: module 'ixformer.functions' has no attribute 'vllm_moe_topk_softmax' × 500+
MoE falling back to pure PyTorch experts permanently
OOM crash at 03:51 → 引擎死亡
结果: 功能测试大量失败, 最终OOM崩溃
```
## 关键发现: 三个dlopen链条 (来自 comp 168 真机证据)
### 1. libcorex_gdn.so — GDN decode/prefill
- 路径: `/usr/local/corex/lib64/libcorex_gdn.so`
- 调用者: `corex_gdn.py` (我们已有, 246行)
- 状态: 我们的corex_gdn.py已部署, 但qwen3_5.py的GDN数学有NaN
- 需要: 修复qwen3_5.py中GDN的fp32 accumulation
### 2. ixformer MoE pipeline — 7步fused MoE
- 路径: 基础镜像 `/usr/local/corex/lib/python3/dist-packages/ixformer/`
- 调用者: `corex_moe.py` (我们已有, 237行)
- 7步: topk_softmax → gen_idx → expand → group_gemm(w13) → silu_mul → group_gemm(w2) → combine
- 状态: Python binding `ixf_F.vllm_moe_topk_softmax` 不存在
- 但C++层 `ixformer::infer::topk_softmax` 在 libixformer.so 中 **存在**
- 需要: ix_bridge.cpp 需要编译, 让Python能调到C++层的MoE函数
### 3. ixformer FA2 — FlashAttention2 三模式
- 路径: `ixformer.contrib.vllm_flash_attn` (Python, 基础镜像自带)
- 调用者: `corex_fa2.py` (我们已有, 279行)
- 状态: corex_fa2.py **没有被部署**, 也**没有被qwen3_5.py调用**
- 基础镜像的qwen3_5.py直接调corex_fa2, 但我们替换了qwen3_5.py后,
attention走的是vllm内置Attention → xformers后端
- 需要: 把corex_fa2.py也部署, 并在qwen3_5.py的Qwen3_5FullAttention中
优先走CoreX FA2 (三模式dispatch)
## upstream_ref 代码搬运状态
### 已搬运 (接口完全对齐):
| 源文件 | 目标 | 行数 | 状态 |
|--------|------|------|------|
| xllm/core/kernels/ilu/ixformer.h | ex_engine/include/ixformer.h | 147 | ✅ 完全一致 |
| xllm/core/kernels/ilu/ilu_ops_api.h | ex_engine/include/ilu_ops_api.h | 153 | ✅ 完全一致 |
| xllm/core/kernels/ilu/utils.h | ex_engine/include/ilu_utils.h | 62 | ✅ 完全一致 |
| xllm/core/kernels/ilu/fused_moe.cpp | ex_engine/csrc/ilu_kernel_fused_moe.cpp | 99 | ✅ 完全一致 |
| xllm/core/kernels/ilu/attention.cpp | ex_engine/csrc/ilu_kernel_attention.cpp | 162 | ✅ 完全一致 |
| xllm/core/kernels/ilu/activation.cpp | ex_engine/csrc/ilu_kernel_activation.cpp | 32 | ✅ 完全一致 |
| xllm/core/kernels/ilu/group_gemm.cpp | ex_engine/csrc/ilu_kernel_group_gemm.cpp | 39 | ✅ 完全一致 |
| xllm/core/kernels/ilu/matmul.cpp | ex_engine/csrc/ilu_kernel_matmul.cpp | 73 | ✅ 完全一致 |
| xllm/core/kernels/ilu/norm.cpp | ex_engine/csrc/ilu_kernel_norm.cpp | 50 | ✅ 完全一致 |
| xllm/core/kernels/ilu/rope.cpp | ex_engine/csrc/ilu_kernel_rope.cpp | 31 | ✅ 完全一致 |
| xllm/core/layers/ilu/fused_moe.cpp | ex_engine/csrc/ilu_layer_fused_moe.cpp | 797 | ✅ 完全一致 |
| xllm/core/layers/ilu/attention.cpp | ex_engine/csrc/ilu_layer_attention.cpp | 189 | ✅ 完全一致 |
### 未搬运 (需要搬运):
| 源文件 | 行数 | 用途 |
|--------|------|------|
| xllm/core/layers/ilu/fused_moe.h | 131 | MoE层头文件 |
| xllm/core/layers/ilu/attention.h | 82 | Attention层头文件 |
## 代码量统计
- 我们的代码(排除upstream/cccl/vllm): 130文件, 45,103行
- 已从upstream搬运的ILU代码: 2,047行 (接口完全对齐)
- 总代码量充足
## 立即行动项 (不需要思考, 直接写代码)
### P0: 修复 computility-run.yaml 参数不生效问题
Aug 7日志显示 max_model_len=100000, 但yaml写的80000。
需要确认yaml格式正确, enable_chunked_prefill要显式写。
### P1: 部署 corex_fa2.py 并接入 qwen3_5.py
comp 168日志证明FA2三模式dispatch是真机上跑的。
我们的qwen3_5.py替换了base的, 但丢失了FA2调用。
### P2: 搬运 fused_moe.h + attention.h (2个文件)
upstream_ref中最后2个未搬运的头文件。
### P3: 确认可提交
Dockerfile + computility-run.yaml + patch_ops.sh 链路完整。

View File

@@ -32,6 +32,8 @@ command:
- '8192'
- --dtype
- half
- --limit-mm-per-prompt
- image=1
env:
- name: VLLM_ENGINE_ITERATION_TIMEOUT_S
value: '3600'

View File

@@ -0,0 +1,82 @@
/* Copyright 2025 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#pragma once
#include <torch/torch.h>
#include <tuple>
#include "framework/kv_cache/kv_cache.h"
#include "framework/model/model_input_params.h"
#include "layers/common/attention_metadata.h"
namespace xllm {
namespace layer {
class AttentionImpl : public torch::nn::Module {
public:
AttentionImpl() = default;
AttentionImpl(int64_t num_heads,
int64_t head_size,
float scale,
int64_t num_kv_heads,
int64_t sliding_window);
AttentionImpl(int64_t num_heads,
int64_t head_size,
int64_t num_kv_heads,
int64_t v_head_dim,
int64_t sliding_window,
float scale,
bool use_fused_mla_qkv,
bool enable_lighting_indexer,
bool enable_mla);
std::tuple<torch::Tensor, std::optional<torch::Tensor>> forward(
const AttentionMetadata& attn_metadata,
torch::Tensor& query,
torch::Tensor& key,
torch::Tensor& value,
KVCache& kv_cache);
void prefill_forward(torch::Tensor& query,
torch::Tensor& key,
torch::Tensor& value,
torch::Tensor& output,
const torch::Tensor& k_cache,
const std::optional<torch::Tensor>& v_cache,
const AttentionMetadata& attn_metadata);
void decoder_forward(torch::Tensor& query,
torch::Tensor& output,
const torch::Tensor& k_cache,
const std::optional<torch::Tensor>& v_cache,
const AttentionMetadata& attn_metadata);
private:
int64_t num_heads_;
int64_t head_size_;
float scale_;
int64_t num_kv_heads_;
int64_t v_head_dim_;
bool use_fused_mla_qkv_;
bool enable_lighting_indexer_;
bool enable_mla_;
int64_t sliding_window_;
};
TORCH_MODULE(Attention);
} // namespace layer
} // namespace xllm

View File

@@ -0,0 +1,131 @@
/* Copyright 2025 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#pragma once
#include <torch/torch.h>
#include "framework/model/model_args.h"
#include "framework/model/model_input_params.h"
#include "framework/parallel_state/parallel_args.h"
#include "framework/quant_args.h"
#include "framework/state_dict/state_dict.h"
#include "framework/state_dict/utils.h"
#include "layers/common/deep_ep.h"
#include "layers/common/dense_mlp.h"
#include "layers/common/fused_moe_base.h"
#include "layers/common/linear.h"
#include "platform/device.h"
#include "util/tensor_helper.h"
namespace xllm {
namespace layer {
class FusedMoEImpl : public torch::nn::Module {
public:
FusedMoEImpl() = default;
FusedMoEImpl(const ModelArgs& model_args,
const FusedMoEArgs& moe_args,
const QuantArgs& quant_args,
const ParallelArgs& parallel_args,
const torch::TensorOptions& options);
torch::Tensor forward_experts(const torch::Tensor& hidden_states,
const torch::Tensor& router_logits,
bool enable_all2all_communication);
torch::Tensor forward(const torch::Tensor& hidden_states,
const ModelInputParams& input_params);
void load_state_dict(const StateDict& state_dict);
private:
// struct to store the selected expert info
struct SelectedExpertInfo {
torch::Tensor reduce_weight;
torch::Tensor combine_idx;
torch::Tensor token_count_slice;
std::optional<torch::Tensor> cusum_token_count;
std::optional<torch::Tensor> input_scale;
};
// initial steps for MoE computation, select the experts for each token
torch::Tensor select_experts(const torch::Tensor& hidden_states_2d,
const torch::Tensor& router_logits_2d,
SelectedExpertInfo& selected_expert_info,
bool enable_all2all_communication);
private:
int64_t num_total_experts_;
int64_t topk_;
int64_t num_expert_group_;
int64_t topk_group_;
double route_scale_;
int64_t hidden_size_;
int64_t n_shared_experts_;
bool is_gated_;
int64_t renormalize_;
std::string hidden_act_;
std::string scoring_func_;
bool is_smoothquant_;
int64_t num_experts_per_rank_;
int64_t start_expert_id_;
// Deep EP related parameters
bool enable_deep_ep_;
DeepEPBuffer deep_ep_buffer_;
DeepEPParams deep_ep_params_;
torch::Tensor dispatch_recv_token_tensor_head_;
torch::Tensor dispatch_recv_token_tensor_tail_;
// steams for parallel shared experts
std::unique_ptr<Stream> shared_stream_;
std::unique_ptr<Stream> routed_stream_;
xllm::Device device_;
bool stream_initialized_ = false;
ReplicatedLinear gate_{nullptr};
DenseMLP shared_experts_{nullptr};
DeepEP deep_ep_{nullptr};
QuantArgs quant_args_;
ParallelArgs parallel_args_;
torch::TensorOptions options_;
ProcessGroup* tp_pg_;
DEFINE_WEIGHT(w13);
DEFINE_FUSED_WEIGHT(w1);
DEFINE_FUSED_WEIGHT(w3);
DEFINE_FUSED_WEIGHT(w2);
DEFINE_WEIGHT(e_score_correction_bias);
DEFINE_WEIGHT(w13_scale);
DEFINE_FUSED_WEIGHT(w1_scale);
DEFINE_FUSED_WEIGHT(w3_scale);
DEFINE_FUSED_WEIGHT(w2_scale);
DEFINE_FUSED_WEIGHT(input_smooth);
DEFINE_FUSED_WEIGHT(act_smooth);
void load_e_score_correction_bias(const StateDict& state_dict);
void load_experts(const StateDict& state_dict);
// create the group gemm output tensor with the workspace
torch::Tensor create_group_gemm_output(const torch::Tensor& a,
const torch::Tensor& b,
const torch::Tensor& group_list,
torch::ScalarType dtype,
torch::Tensor& workspace);
};
TORCH_MODULE(FusedMoE);
} // namespace layer
} // namespace xllm

View File

@@ -82,18 +82,33 @@ class CoreXGDN:
def __init__(
self,
num_heads: int,
head_dim: int,
num_heads: int = 0,
head_dim: int = 128,
layer_idx: int = 0,
chunk_size: int = 16,
eps: float = 1e-6,
# kwargs from qwen3_5.py (GatedDeltaNet uses separate k/v dims)
num_v_heads: int = 0,
num_k_heads: int = 0,
head_k_dim: int = 0,
head_v_dim: int = 0,
conv_kernel_size: int = 4,
**kwargs, # future-proof
):
self.num_heads = num_heads
self.head_dim = head_dim
# Accept both calling conventions:
# CoreXGDN(num_heads, head_dim) — simple
# CoreXGDN(num_v_heads=.., num_k_heads=.., head_k_dim=.., head_v_dim=..) — from qwen3_5.py
self.num_v_heads = num_v_heads or num_heads
self.num_k_heads = num_k_heads or num_heads
self.head_k_dim = head_k_dim or head_dim
self.head_v_dim = head_v_dim or head_dim
self.num_heads = self.num_v_heads
self.head_dim = self.head_k_dim
self.layer_idx = layer_idx
self.chunk_size = chunk_size
self.eps = eps
self.scale = head_dim ** -0.5
self.conv_kernel_size = conv_kernel_size
self.scale = self.head_k_dim ** -0.5
self._decode_warned = False
self._prefill_warned = False
@@ -106,20 +121,68 @@ class CoreXGDN:
def forward(
self,
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
gate: torch.Tensor,
beta: torch.Tensor,
conv_state: Optional[torch.Tensor],
temporal_state: Optional[torch.Tensor],
hidden_states: torch.Tensor,
attn_metadata,
) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
conv_state: torch.Tensor,
temporal_state: torch.Tensor,
in_proj_qkv, # nn.Module — projects hidden → conv_dim
in_proj_z, # nn.Module — projects hidden → val_dim
in_proj_b, # nn.Module — projects hidden → num_v_heads (beta)
in_proj_a, # nn.Module — projects hidden → num_v_heads (alpha/dt)
conv1d_weight, # (conv_dim, 1, kernel_size) depthwise conv weight
A_log, # (num_v_heads,) log decay parameters
dt_bias, # (num_v_heads,) dt bias
norm, # GatedRMSNorm module
out_proj, # RowParallelLinear
) -> torch.Tensor:
"""Full GDN layer forward — matches qwen3_5.py calling convention.
This mirrors the PyTorch _pytorch_forward() path but uses ixformer
matmul acceleration and fused CoreX GDN ops when available.
"""
from vllm.model_executor.parallel_utils.communication_op import (
tensor_model_parallel_all_reduce,
)
try:
from vllm.distributed import get_tensor_model_parallel_world_size
except ImportError:
get_tensor_model_parallel_world_size = lambda: 1
tp_size = get_tensor_model_parallel_world_size()
local_key_dim = self.num_k_heads * self.head_k_dim // tp_size
local_val_dim = self.num_v_heads * self.head_v_dim // tp_size
local_num_v = self.num_v_heads
local_num_k = self.num_k_heads
local_conv_dim = local_key_dim * 2 + local_val_dim
is_prefill = getattr(attn_metadata, 'num_prefill_tokens', 0) > 0
# Project all tokens at once
mixed_qkv_all, _ = in_proj_qkv(hidden_states)
z_all, _ = in_proj_z(hidden_states)
b_all, _ = in_proj_b(hidden_states)
a_all, _ = in_proj_a(hidden_states)
if is_prefill:
return self._prefill(q, k, v, gate, beta, temporal_state)
if not self._prefill_warned:
logger.info("Using fused CoreX GDN prefill operator")
self._prefill_warned = True
return self._full_prefill(
hidden_states, attn_metadata, conv_state, temporal_state,
mixed_qkv_all, z_all, b_all, a_all,
conv1d_weight, A_log, dt_bias, norm, out_proj,
local_key_dim, local_val_dim, local_num_v, local_num_k,
local_conv_dim)
else:
return self._decode(q, k, v, gate, beta, conv_state, temporal_state)
if not self._decode_warned:
logger.info("Using fused CoreX GDN decode operator")
self._decode_warned = True
return self._full_decode(
hidden_states, attn_metadata, conv_state, temporal_state,
mixed_qkv_all, z_all, b_all, a_all,
conv1d_weight, A_log, dt_bias, norm, out_proj,
local_key_dim, local_val_dim, local_num_v, local_num_k,
local_conv_dim)
def _prefill(self, q, k, v, gate, beta, temporal_state):
if not self._prefill_warned:

View File

@@ -180,17 +180,34 @@ if [ -n "$VLLM2" ]; then
cp ./chat_utils.py "$VLLM2/entrypoints/chat_utils.py" 2>/dev/null || true
fi
# Deploy corex_gdn.py + corex_moe.py → vllm model_executor/models/
# These provide the fused GDN prefill kernel and MoE pipeline that competitor 168 had
if [ -f "/workspace/ex_engine/python/corex_gdn.py" ]; then
# corex_gdn.py + corex_moe.py: DO NOT overwrite base image originals!
# Comp 168 log proves: base image's corex_gdn.py loads libcorex_gdn.so and works.
# Our overwrite breaks the interface (CoreXGDN.__init__ signature mismatch).
# Only deploy ours if base has NO corex modules at all.
if [ ! -f "$VLLM/model_executor/models/corex_gdn.py" ]; then
cp "/workspace/ex_engine/python/corex_gdn.py" "$VLLM/model_executor/models/corex_gdn.py" 2>/dev/null || true
echo "[patch_ops] corex_gdn.py deployed (base had none)"
fi
if [ ! -f "$VLLM/model_executor/models/corex_moe.py" ]; then
cp "/workspace/ex_engine/python/corex_moe.py" "$VLLM/model_executor/models/corex_moe.py" 2>/dev/null || true
echo "[patch_ops] Deployed: corex_gdn.py + corex_moe.py → $VLLM/model_executor/models/"
if [ -n "$VLLM2" ]; then
cp "/workspace/ex_engine/python/corex_gdn.py" "$VLLM2/model_executor/models/corex_gdn.py" 2>/dev/null || true
cp "/workspace/ex_engine/python/corex_moe.py" "$VLLM2/model_executor/models/corex_moe.py" 2>/dev/null || true
echo "[patch_ops] corex_moe.py deployed (base had none)"
fi
# corex_fa2.py: deploy if base doesn't have it
# Comp 168 log: corex_fa2.py provides FA2 packed/paged/chunked dispatch
if [ ! -f "$VLLM/model_executor/models/corex_fa2.py" ]; then
if [ -f "/workspace/ex_engine/python/corex_fa2.py" ]; then
cp "/workspace/ex_engine/python/corex_fa2.py" "$VLLM/model_executor/models/corex_fa2.py" 2>/dev/null || true
echo "[patch_ops] corex_fa2.py deployed (base had none)"
fi
fi
echo "[patch_ops] CoreX modules: preserved base originals where they exist"
if [ -n "$VLLM2" ]; then
for _CM in corex_gdn.py corex_moe.py corex_fa2.py; do
if [ -f "$VLLM/model_executor/models/$_CM" ] && [ ! -f "$VLLM2/model_executor/models/$_CM" ]; then
cp "$VLLM/model_executor/models/$_CM" "$VLLM2/model_executor/models/$_CM" 2>/dev/null || true
fi
done
fi
# Deploy EX Engine Python module + C++ bridge into vllm importable path
EX_ENGINE_SRC="/workspace/ex_engine"

View File

@@ -463,6 +463,7 @@ class GatedDeltaNet(nn.Module):
self._use_corex_gdn = False
if _corex_gdn_available and _corex_gdn_module is not None:
try:
# Try base image's CoreXGDN signature first (may differ from ours)
self._corex_gdn_obj = _corex_gdn_module.CoreXGDN(
num_v_heads=self.num_v_heads // tp_size,
num_k_heads=self.num_k_heads // tp_size,
@@ -472,11 +473,25 @@ class GatedDeltaNet(nn.Module):
layer_idx=layer_idx,
)
self._use_corex_gdn = True
logger.info("GatedDeltaNet layer %d: CoreX fused GDN enabled", layer_idx)
except TypeError:
# Fallback: simpler signature
try:
self._corex_gdn_obj = _corex_gdn_module.CoreXGDN(
self.num_v_heads // tp_size,
self.head_k_dim,
layer_idx=layer_idx,
)
self._use_corex_gdn = True
except Exception as e2:
logger.warning(
"GatedDeltaNet layer %d: CoreX GDN init failed (%s), PyTorch",
layer_idx, e2)
except Exception as e:
logger.warning(
"GatedDeltaNet layer %d: CoreX GDN init failed (%s), using PyTorch",
"GatedDeltaNet layer %d: CoreX GDN init failed (%s), PyTorch",
layer_idx, e)
if self._use_corex_gdn and layer_idx == 0:
logger.info("GatedDeltaNet: CoreX fused GDN enabled")
def _conv1d_weight_loader(self, param: torch.Tensor,
loaded_weight: torch.Tensor) -> None: