Skip to content

MLA: the split A-projection is reachable with VT_MLA_FUSED_NORM_ROPE unset, because a backend that does not REGISTER kFusedNormRope takes the same branch -- GLM-5.3 on gfx1151 #2564

Description

@localai-org-maint-bot

Row: BACKEND-ROCM

Measured on strix:gpu0 (gfx1151, ROCm 7.2.4), loading GLM-5.3 UD-IQ1_S
through vllm-cli --device auto with VT_CPU_MOE=1, on a build of
row/BACKEND-ROCM-IQ-EXPERT-RESIDENCY (#2562). VT_MLA_FUSED_NORM_ROPE was
not set.
The load completes -- 1809 tensors, [vt load] weights 1372.153 s,
the engine sizes its KV cache -- and the FIRST forward throws:

engine-fatal: EngineCore busy loop threw: MLA block: the split A-projection path
needs vt::FusedNormRope to read the merged [kv_lora_rank + qk_rope_head_dim] row,
because a BLOCK-QUANTIZED kv_a_proj_with_mqa (q8_0) has no row slice — a quant
block spans whole rows and vt::SizeOf refuses a per-element size for it. This is
reachable only with VT_MLA_FUSED_NORM_ROPE=0 on a keep-quant MLA checkpoint;
unset it, or load this model with an expanded residency

The message's own diagnosis is wrong, and this run is the counterexample

src/vllm/model_executor/layers/attention/mla_attention.cpp:550-551:

const bool fused_nr = R > 0 && !has_k_rope_norm && MlaFusedNormRopeEnabled() &&
                      vt::OpRegistered(vt::OpId::kFusedNormRope, d.q.device.type);

There are FOUR ways for fused_nr to be false, and the comment above the throw
(:628-631) accounts for one of them:

vt::FusedNormRope is registered on CPU (cpu_ops.cpp) and CUDA
(cuda_ops.cu) and is default-ON, so the only way here on a keep-quant
checkpoint is VT_MLA_FUSED_NORM_ROPE=0.

It enumerates the backends that HAVE the op and forgets the ones that do not.
ROCm registers no kFusedNormRope (it is one of the eight MLA/DSA ops
.agents/specs/rocm-glm53-dsa.md W1.3 records as missing), so the fourth term is
false on this board with every environment variable unset.

And vt::OpRegistered cannot see the reference tier. It is deliberately a
native-only probe (src/vt/op_provider.cpp:788-806), which is right for its own
contract and is exactly what makes this branch wrong here: on an integrated
host-addressable board the portable CPU tier WOULD have served kFusedNormRope
(docs/ROCM.md:53-58), and RocmBackend::FlushPending() was overridden under
#2498 precisely so a host reference kernel reads drained device memory. The
branch never asks. It asks whether a NATIVE kernel exists, and takes a fallback
that this checkpoint's block-quantized kv_a_proj_with_mqa cannot survive.

Why it matters now

This is the last thing between GLM-5.3 non-flash and a token on strix:gpu0.
With #2516 and #2517 fixed (#2562) the load completes and the forward is reached;
reference-tier distinct=0 at the throw, and the three ops selected before it
are all device=5 selected=vt-native, so nothing had fallen back yet.

Three candidate repairs, none of them taken here

  1. Port kFusedNormRope to ROCm. One kernel of the eight
    .agents/specs/rocm-glm53-dsa.md owes, and the one with a CUDA sibling to
    mirror. Smallest and most vLLM-shaped.
  2. Let fused_nr consider the reference tier on a backend whose device
    memory is host-addressable. One predicate, but it changes what "available"
    means at a shared seam and needs its own before/after.
  3. Teach the split path to slice a block-quantized merged row. Largest, and
    it fixes a fallback nobody wants to take.

At minimum the comment and the message must stop naming one cause when there are
two, because the next reader will look for an environment variable that is not
set.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions