Compare commits

...
Author SHA1 Message Date
varunandTyler Michael Smith be441ac536 zero out attn output
fixes and zero out input to fi kernel
2026-03-17 17:10:20 -04:00
Tyler Michael Smith 3c01ddbc98 Merge remote-tracking branch 'elvir/fix-ep-weight-filter-eplb' into gb200-0317 2026-03-17 17:00:31 -04:00
Elvir CrncevicandClaude Opus 4.6 f12f88f610 [Bugfix] EP weight filter: don't skip scale tensors
The EP weight filter was skipping ALL per-expert tensors for non-local
experts, including tiny scale/metadata tensors (input_scale,
weight_scale, weight_scale_2).  FlashInfer NVFP4 backends compute a
global max of activation scales across ALL experts
(a13_scale.max()), so non-local scale slots left at their
torch.empty() initialization corrupt the global activation scale,
causing ~2% accuracy degradation even without EPLB.

Only filter tensors ending in ".weight" (the heavy ones).  Scale and
metadata tensors are negligibly small and always loaded.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Elvir Crncevic <elvircrn@gmail.com>
2026-03-17 17:31:51 +01:00
Elvir CrncevicandClaude Opus 4.6 e9e14c767a [Bugfix] Disable EP weight filter when EPLB is enabled (#37136)
The EP weight filter (PR #37136) partitions logical experts across ranks
and skips non-local expert weights at the safetensors level. This breaks
EPLB because redundant physical expert slots map to logical experts that
belong to other ranks in the default partition. Those weights get filtered
out, leaving redundant slots uninitialized (zeros), which causes
catastrophic accuracy loss (~0.08 gsm8k vs ~0.95 baseline).

Fix: skip the EP weight filter entirely when EPLB is enabled, since the
weight loader needs to see ALL logical expert weights to populate
redundant physical slots.

Signed-off-by: Elvir Crncevic <elvircrn@gmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 17:20:09 +01:00
4 changed files with 27 additions and 1 deletions
@@ -518,7 +518,7 @@ class MLAAttention(nn.Module, AttentionLayerBase):
self._k_scale,
)
if self.attn_backend.accept_output_buffer:
output = torch.empty(output_shape, dtype=q.dtype, device=q.device)
output = torch.zeros(output_shape, dtype=q.dtype, device=q.device)
torch.ops.vllm.unified_mla_attention_with_output(
q,
kv_c_normed,
@@ -692,6 +692,15 @@ class MLAAttention(nn.Module, AttentionLayerBase):
assert attn_metadata.decode is not None
attn_out, lse = self.impl.forward_mqa(mqa_q, kv_cache, attn_metadata, self)
# Zero out padded region for CUDA graphs. Padded requests have
# seq_lens=0 in the decode metadata (device tensor updated in-place
# before each graph replay), so masked_fill_ always launches a kernel
# during capture yet only zeros padding slots during replay.
if attn_metadata.decode is not None:
decode_seq_lens = attn_metadata.decode.seq_lens
pad_mask = (decode_seq_lens == 0).view(-1, 1, 1)
attn_out[:decode_seq_lens.shape[0]].masked_fill_(pad_mask, 0)
# correct dcp attn_out with lse.
if self.impl.dcp_world_size > 1:
if self.dcp_a2a:
@@ -316,6 +316,13 @@ class DefaultModelLoader(BaseModelLoader):
if not (model_config.is_moe and parallel_config.enable_expert_parallel):
return
# When EPLB is enabled, redundant physical expert slots may map to
# logical experts that belong to other ranks in the default partition.
# The weight loader needs to see ALL logical expert weights so it can
# populate these redundant slots. Skip the filter entirely.
if parallel_config.enable_eplb:
return
num_experts = model_config.get_num_experts()
if num_experts <= 0:
return
@@ -73,4 +73,9 @@ def should_skip_weight(
if eid is None:
# Not an expert weight (dense / shared-expert / embedding) → keep.
return False
# Only skip heavy weight tensors, never scale/metadata tensors.
# Scale tensors are tiny and some backends need them from ALL experts
# (e.g. FlashInfer NVFP4 computes a global max of activation scales).
if not weight_name.endswith(".weight"):
return False
return eid not in local_expert_ids
@@ -181,7 +181,12 @@ class FlashInferMLAImpl(MLACommonImpl[MLACommonMetadata]):
if self.bmm2_scale is None:
self.bmm2_scale = layer._v_scale_float
out = torch.zeros(q.shape[0], q.shape[2], self.kv_lora_rank,
dtype=torch.bfloat16, device=q.device)
self._workspace_buffer.fill_(0)
o = trtllm_batch_decode_with_kv_cache_mla(
out=out,
query=q,
kv_cache=kv_c_and_k_pe_cache.unsqueeze(1),
workspace_buffer=self._workspace_buffer,