frank-primary: profile the GLM decode step and stage the compile changelist
A torch-profiler kernel-time window (496 decode tokens, rank0 trace parsed for
device-activity time) on the deployed GLM-5.3-Flash shape falsifies this page's
own "the expert gather is the bound" explanation:
46.18 s of kernel time / 496 tokens = ~93 ms per token (actual step ~104 ms)
34.8 % vectorized_elementwise_kernel 708,500 launches (1,428/token)
10.0 % r4d_gemm_moe_w4a16_nt_b16_m64 (the MoE GEMM)
6.7 % elementwise_kernel_manual_unroll
6.4 % wvSplitK_hf_sml_ (split-K GEMM)
8.9 % router: sbtopk + warpMergeSortTopK + bitonicSortKVInPlace
6.5 % r4d_mhc_* (hyper-connections)
1.05 % r4d_moe_lru_gather_kernel <-- the retracted suspect
The earlier arithmetic was circular (it assumed the gather consumed the whole
step). The real bound is unfused pointwise glue: the fork compiles its own Qwen
trunk (support_torch_compile in models/qwen4_exp/amd/model.py) and not its GLM
trunk, so the engine boots with "provides neither a compiled submodule nor
breakable CUDA graphs. Overriding to FULL_DECODE_ONLY." That also explains why
MTP4 measured flat — pointwise cost scales with the token count, so spec decode
amortizes nothing here — and it retires arena/hit-rate tuning (the gather is 1 %).
Staged as documented patch artifacts (not wired into any serve script; the
engine does not yet boot with them) — scripts/patches/glm53-compile-0{1,2,3}*.patch:
-01 models/glm5next/common/model.py decorate Glm5NextModel (R4D_GLM_COMPILE)
-02 model_executor/kernels/r4d_lib.py freeze 10 capability probes that do
os.path.exists/.so lookups inside the traced forward. functools.lru_cache
does NOT protect them: dynamo re-traces the wrapped body even when warm
(verified in-image); a plain module-global read compiles.
-03 compilation/wrapper.py make fullgraph=True opt-out, because
suppress_errors does NOT cover graph breaks and this trunk can never
satisfy fullgraph (indexer metadata syncs to host — .item() at
glm5next/amd/sparse_indexer.py:263,419).
With all three the trunk traces (Dynamo bytecode transform time 1.08 s) and dies
on KeyError: in_proj_qkvbfg_a in the mHC/KDA path — a latent bug the compile
exposes, and the remaining blocker.
Also records two structural constraints: no new HIP kernels are possible (r4d.so
ships without sources), which binary-blocks the fused router at GLM's 288
experts; and DFlash2 for GLM is a TARGET-side port, not a drafter port
(incoai/GLM-5.3-Flash-DFlash2 is architectures ["DFlash2DraftModel"] +
model_type qwen3, already mapped at registry.py:636; glm5next lacks only the
aux-hidden-state plumbing deepseek_v2.py demonstrates).
Runbook frontmatter, intro, mermaid and wall section, AGENTS.md and the work log
corrected to match the measurement. tests/run.sh: 362 seams pass.
This commit is contained in:
1 parent
979a4308f4
commit
bd8c72ff47
6 files changed
+482
-46
No files matched your search
@@ -20,17 +20,58 @@
|
||||
- Measured on the deployed engine: **decode 9.9-10.0 tok/s, prefill 918 tok/s**
|
||||
(warm, 2.6k prompt), KV 138,561 tokens; arena 14.51 GiB = 2,383 slots per
|
||||
rank over 12,096 host-backed experts (73.64 GiB pinned per rank).
|
||||
- **The wall:** live expert-cache telemetry (`R4D_LRU_TELEMETRY=1` + a
|
||||
`with_stack=false` profiler window) puts the cache hit rate at 0.53-0.66 per
|
||||
layer; a step moves 0.82 GiB of misses in ~100 ms = **8.2 GB/s effective
|
||||
against a link measured at 22.74 GB/s** — the same structural gather-read-path
|
||||
gap the 2026-09-29 qwen campaign found. Growing the arena 13.08 -> 14.51 GiB
|
||||
(+9%) changed decode by *nothing* (10.03 -> 9.88 tok/s), so residency stopped
|
||||
being the binding term; 35 tok/s would need a 0.89 hit rate.
|
||||
- **The wall — and a corrected one.** The first account of the shortfall
|
||||
bounded the step with throughput arithmetic (a step moves 0.82 GiB of misses
|
||||
in ~100 ms = "8.2 GB/s effective against a 22.74 GB/s link", needing a 0.89
|
||||
hit rate for 35 tok/s). **A torch-profiler kernel-time window falsified it**
|
||||
(496 decode tokens, rank0 trace parsed for device-activity time): 46.18 s of
|
||||
kernel time = ~93 ms/token, of which **44 % is pointwise glue** —
|
||||
`vectorized_elementwise_kernel` alone 16.07 s over **708,500 launches (1,428
|
||||
per token)**, plus 3.11 s of `elementwise_kernel_manual_unroll` — against
|
||||
**1.05 % for `r4d_moe_lru_gather_kernel`**. The MoE GEMM is 10 %, the split-K
|
||||
GEMM 6.4 %, the router/top-k 8.9 %, the mHC kernels 6.5 %, and the inductor
|
||||
fusions that DO happen 6 %. The real cause is that the fork compiles its own
|
||||
Qwen trunk (`@support_torch_compile` in `models/qwen4_exp/amd/model.py`) and
|
||||
**not** its GLM trunk, so every pointwise op is a separate eager kernel; the
|
||||
engine says so at boot ("...provides neither a compiled submodule nor
|
||||
breakable CUDA graphs. Overriding to FULL_DECODE_ONLY."). Growing the arena
|
||||
13.08 -> 14.51 GiB still changed nothing, consistent with a 1 % gather.
|
||||
- **Fork work staged (compile changelist, WIP — does not boot yet).**
|
||||
`scripts/patches/glm53-compile-01-model-decorator.patch`,
|
||||
`-02-trace-safe-probes.patch`, `-03-fullgraph-optout.patch`, each with its own
|
||||
evidence header and base/patched sha256. Boots established, in order: (1)
|
||||
`functools.lru_cache` does NOT make a probe trace-safe — dynamo re-traces the
|
||||
wrapped body even when warm, so `r4d_mhc_supported -> r4d_mhc_enabled ->
|
||||
available -> os.path.exists` breaks the graph (a plain module-global read
|
||||
compiles; verified in-image); (2) `suppress_errors` does NOT cover graph
|
||||
breaks, only `fullgraph=False` does; (3) the GLM trunk can never satisfy
|
||||
`fullgraph` because its indexer metadata syncs to host
|
||||
(`glm5next/amd/sparse_indexer.py:263,419`), and AOT compile refuses breaks
|
||||
outright. With all three the trunk **traces** (`Dynamo bytecode transform
|
||||
time: 1.08 s`) and then dies on `KeyError: 'in_proj_qkvbfg_a'` in the
|
||||
mHC/KDA path — a latent bug the compile exposes. That is the remaining
|
||||
blocker.
|
||||
- **No new HIP kernels are possible** (the image ships `r4d.so` with no
|
||||
sources), which binary-blocks the second-largest lever: the fused router is
|
||||
gated `num_experts in {128, 256, 512}` and only
|
||||
`r4d_moe_route_kernel<128|256|512>` instantiations exist, while GLM has 288
|
||||
experts. `--kv-cache-dtype fp8` buys no capacity either (the plan's KV
|
||||
reserve is dtype-blind), only removing gfx12's only MLA decode path.
|
||||
- **DFlash2 for GLM is a target-side port, not a drafter port.**
|
||||
`incoai/GLM-5.3-Flash-DFlash2` declares `architectures: ["DFlash2DraftModel"]`
|
||||
and `model_type: "qwen3"` (5 dense layers, sliding attention, block 8,
|
||||
`target_layer_ids [5, 14, 24, 33, 42]` of 45) and the fork **already maps it**
|
||||
(`registry.py:636` -> `qwen3_dflash2.DFlash2Qwen3ForCausalLM`; spec method
|
||||
`"dflash"`). What `glm5next` lacks is the target side: aux-hidden-state
|
||||
capture (`set_aux_hidden_state_layers` / per-layer capture, as `deepseek_v2.py`
|
||||
demonstrates). No speedup is budgeted: a verification pass pays pointwise cost
|
||||
proportional to its token count.
|
||||
- Measured and rejected: **MTP4** (the head loads, arch resolves to
|
||||
`Glm5NextMTPModel`, acceptance is excellent at mean length 3.76-4.90 — but
|
||||
decode was unchanged (9.6-11.2 vs 10.0) while prefill COLLAPSED 918 -> 255,
|
||||
because a verification forward gathers the *union* of the batch's experts);
|
||||
decode was unchanged (9.6-11.2 vs 10.0) while prefill COLLAPSED 918 -> 255.
|
||||
The profiler window above explains it: the per-token cost is dominated by
|
||||
pointwise glue that scales with the token count, so verifying a draft batch
|
||||
pays ~N x that glue for the same N tokens of output);
|
||||
**fp8 KV** (disables the sparse MLA backend's rope-free Triton path, and
|
||||
aiter's MLA decode op is gated on CDNA>2, so the MTP draft step demands an op
|
||||
that cannot exist on gfx12); **NBATCH 8192** (boots, identical prefill).
|
||||
|
||||
Reference in new issue
Block a user