Skip to content

feat(MODEL-MM-GLM53-FLASH-KPOOL-CUDA): give GLM-5.3-Flash's k-pool indexer a device it can run on - #2432

Open
localai-org-maint-bot wants to merge 7 commits into
mainfrom
row/MODEL-MM-GLM53-FLASH-KPOOL-CUDA
Open

feat(MODEL-MM-GLM53-FLASH-KPOOL-CUDA): give GLM-5.3-Flash's k-pool indexer a device it can run on#2432
localai-org-maint-bot wants to merge 7 commits into
mainfrom
row/MODEL-MM-GLM53-FLASH-KPOOL-CUDA

Conversation

@localai-org-maint-bot

Copy link
Copy Markdown
Collaborator

feat(MODEL-MM-GLM53-FLASH-KPOOL-CUDA): give GLM-5.3-Flash's k-pool indexer a device it can run on

#2415 asked one question: does the k-pool DSA indexer get a CUDA kernel, or does
the indexer stay on the host and every DSA layer pay a device-to-host round trip
on every step? This branch answers it with the kernel and closes #2415 with the
argument.

Why the kernel, and not the round trip

The host arm is not a cheap fallback on this model. The pool grid is re-formed
over the WHOLE packed key history on every call, and that row is
2 * index_head_dim + 1 = 257 floats per token per layer — 33.7 MiB per layer
per step at 32k context, 371 MiB per step across the eleven DSA layers, plus a
[B, S, 2051] selection coming back. That is a synchronous stall eleven times on
the critical path of a 101.24 GiB model whose entire reason for having a device
arm is that its weights are already resident. A device arm whose indexer is on
the host is the CPU path with a copy tax. The alternative is not staged, not
flagged and not kept as a fallback.

What the gap was

Measured, not inherited. include/vt/ops.h defined exactly two indexer ops,
kDsaIndexerLogits and kDsaTopkSelect, and its own comment calls them the DSA
"Lightning Indexer" selection pair; they score RAW TOKENS.
git grep -l 'kpool\|index_kpool\|compress_ape\|compress_gate' src/ include/
returned eleven files and every one was a glm5_next_* host file — nothing in
the seam, nothing under src/vt/, nothing in any other model. Composition was
not a way out either: this OpId inventory has no general softmax, no general
reduction and no pooled gather, and its only top-k entries select experts and
sampler tokens.

What this adds

Two ops, and a probe.

vt::Glm5NextKpoolCompress is upstream's get_pooled_states
(modular_glm5_next.py:897-970 @ transformers v5.16.1, the lane pin this row
already carries): index_head_dim independent softmaxes over a pool's members,
one per channel, with the learned intra-pool absolute-position embedding added to
each member's gate score. The pool grid starts at the first VALID token rather
than at slot 0, so a left-padded row groups differently.

vt::Glm5NextKpoolSelect is the selection half of forward (:821-875), with
get_visible_tokens (:877-895) folded in as a predicate rather than
materialised — a [B, S, kv_len] visibility tensor is 2.7 GiB at 32k context for
something a thread evaluates in two instructions, and upstream materialises it
only because torch has no other way to gather under it — and
append_visible_tail (:972-1022) folded in as the tail write, because the
tail's write offset is select_k * index_kpool and only this op knows P.

vllm::glm5_next::KpoolDeviceOpsAvailable() mirrors
deepseek_v4::V4DeviceKernelsAvailable so a forward can ask before it builds
operands rather than after it throws.

vLLM implements glm5_next at no revision — re-verified rather than inherited:
git grep -n 'glm5_next\|Glm5Next' -- vllm/ exits 1 with no output at the parity
pin 5559679229, and vllm#53906 is still open and therefore inadmissible.

The compaction is on the device and P never reaches the host

keep = pool_valid.any(0) (:968) is not cosmetic. select_k reads the
COMPACTED width, so a P one too large moves the ragged tail's write offset by
index_kpool columns and changes what the final [..., :output_width]
truncation keeps. The compress op therefore does a per-pool validity pass, a
single-block exclusive scan over the keep predicate and a compacted write, and
publishes P as a [1] i32 DEVICE scalar the select kernel reads on the device.
Neither op synchronises; every buffer is sized at the static bound
ceil(kv_len / index_kpool), and topk_indices is a fixed 2051 wide on this
checkpoint — not 2048, and sizing it 2048 truncates the tail the model always
keeps.

No CPU provider, on purpose

The CPU answer already exists as glm5_next_dsa.cpp and it is this family's
ORACLE. Registering it a second time under these ids would make the seam its own
golden. A CPU queue is refused by name through vt::GetOp, and the gate asserts
that refusal.

f32, because that is upstream's own arithmetic

:823 scores in fp32 and :960-964 takes the pool softmax in fp32. The host
reference widens to double, and the device deliberately does not follow it: a
fp64 pool softmax would put the model path on the 1/64-rate pipe to be MORE
precise than the thing it mirrors, and no token gate can see a dtype that is too
wide. Every accumulation spells its rounding with __fadd_rn / __fmul_rn /
__fdiv_rn in the host reference's own order, so nvcc's default contraction
cannot decide the answer, and head_scale is narrowed once on the host in double
rather than taken from rsqrtf.

Red first, twice, and captured

On bd2c14ce5, with the test in place and no implementation. The first build
stopped at fatal error: vllm/model_executor/models/glm5_next_device.h: No such file or directory. Adding only that seam moved the failure onto the ops
themselves — 'kGlm5NextKpoolCompress' is not a member of 'vt::OpId',
'Glm5NextKpoolSelect' is not a member of 'vt', twelve diagnostics naming the
two ids and the two free functions. Both builds exited 1.

The gate, and the bound it is written against

tests/vllm/models/test_glm5_next_kpool_device.cpp runs the SAME fixture
geometry test_glm5_next_dsa gates on the host — seq_len 21 against
index_topk 8, one row left-padded by three — and reuses
glm5_next_dsa_goldens.inc verbatim, which is the RUN output of the unmodified
Glm5NextTextIndexer at v5.16.1 and carries the intermediates as well as the
result. So the device arm answers to the oracle directly and to the host arm as
well. np = ceil(21/4) = 6 against kNumPools = 5 makes the keep compaction
observable; four short cases exercise P == 0.

Selection error is BIMODAL, so the score bound is TIED TO THE MARGIN rather than
chosen: golden score magnitudes here run to 45.17, where one f32 ULP is already
3.8e-6, so an absolute tolerance below that fails on arithmetic and one far above
it bounds nothing. The gate asserts score_delta * 4 < margin and prints both,
alongside SET equality of the selected token indices and the positionwise
comparison. Every float comparison is isfinite-guarded on both operands first,
because an all-NaN forward on this row once read as a perfect match and the model
then emitted token id 0 eight times.

What is measured, and what is still PENDING

CPU, x86_64, on the merged head: test_glm5_next_kpool_device 4 cases / 4
assertions with the three device cases explicitly SKIPPED and saying so;
test_glm5_next_dsa 10 / 1934; test_ops_dsa_indexer 10 / 176;
test_op_provider 14 / 494; test_glm5_next_moe 13 / 9619;
test_glm5_next_bridge 20 / 32562; test_glm5_next_layer 10 / 1656;
test_glm5_next_forward 23 / 167; test_glm5_next_attn 14 / 160. Sibling
inertness, since include/vt/ops.h, src/vt/ops.cpp and src/vt/op_provider.cpp
are shared: test_deepseek_v4_dsa 13 / 38, test_qwen4_exp_qsa_device 12 / 4697,
test_dots3_note_attn 51 / 6888, test_kimi_kda 14 / 36, and
test_glm_moe_dsa_forward 7 / 5258 under its CTest VT_MOE_EXPERT_STREAM=1
environment. One mutation was killed on the host: adding a CPU provider reds both
CHECK_FALSE(OpRegistered(..., kCPU)) and the CHECK_THROWS, restored
byte-for-byte by sha256 and rebuilt green.

THE DEVICE GATE IS PENDING AND IS NOT REPORTED AS A PASS. It is queued on
dgx:gpu0 (sm_121a) as job 6954476b-1d86-42c1-b4b0-12afc911efbc, building
with -DVLLM_CPP_FLASH_ATTN=ON plus CUTLASS asserted FATAL at configure time,
and carrying a nine-mutation matrix that rebuilds and asserts the binary's sha256
moved before believing any post-mutation result. sm_110 and sm_121a are
different targets and a number from one does not transfer to the other. An
untaken device gate is PENDING, never a pass, and this PR does not merge before
it returns.

No speed claim, and no token claim. Nothing here was measured for
throughput, there is no denominator, and no generation from this artifact was
observed by this branch.

Nothing lands dead: this lands UNREACHED, and here is what owns the wiring

ModelRegistry::Forward still reaches the HOST indexer.
glm5_next_forward.cpp:231-238 still refuses a non-CPU queue by name. The only
callers of either op are the device gate and the availability probe. This is the
shape .agents/reachability.md calls "the test-only driver", and it is staged
deliberately rather than disclosed after the fact.

The row's spec lists it under ## Owed as O36. There is no production call
site to delete, so the reachability question is already answered; the mutation
run in its place deletes the RegisterOp line and is EXPECTED to survive as a
SKIP, which is the finding and not a kill — and is exactly why the device job
reads the skipped-case count instead of the exit code.

Out of scope and untouched: W9c-1 (the MLA route), W9c-2 (the two CPU-only
refusals at glm5_next_kda.cpp:322-325 and glm5_next_moe.cpp:222-225), W9b
(residency).

Closes #2415

FOLLOWING_AGENTS_PROTOCOL

Refs: #2410
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]

mudler added 5 commits August 31, 2026 15:37
…e the kernel down before writing it

#2415 asks whether GLM-5.3-Flash's k-pool DSA indexer gets a CUDA kernel or
stays on the host at eleven device-to-host round trips per step. This block
takes the first answer and records the arithmetic behind it: the packed key
history the pool grid is re-formed over is 257 floats per token per layer, so
the host arm moves 371 MiB per step across the eleven DSA layers before the
selection comes back. A device arm whose indexer is on the host is the CPU path
with a copy tax.

W9c-0 scopes two ops and nothing else. `vt::Glm5NextKpoolCompress` is upstream's
`get_pooled_states` (`modular_glm5_next.py:897-970` @ transformers v5.16.1) with
the `keep` compaction done on the device and `P` published as a device scalar, so
neither op synchronises. `vt::Glm5NextKpoolSelect` is the selection half of
`forward` (`:821-875`) with `get_visible_tokens` (`:877-895`) folded in as a
predicate and `append_visible_tail` (`:972-1022`) folded in as the tail write.
The section says why `PackIndexerStates` and `GetVisibleTokens` deliberately do
NOT become ops, why the kernels are f32 where the host reference is double, and
why there is no CPU provider.

O36 names what the wave leaves unreached, the wave that owns the wiring and the
issue that tracks it, because the ops land before anything calls them.

FOLLOWING_AGENTS_PROTOCOL

Refs: #2415, #2410
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…ce it can run on

GLM-5.3-Flash's DSA indexer is a k-pool indexer, and until this change nothing
in this tree computed one on a device. `include/vt/ops.h` defined exactly two
indexer ops, both of them the DSA Lightning Indexer's, which score RAW TOKENS;
`git grep -l 'kpool\|index_kpool\|compress_ape\|compress_gate' src/ include/`
returned eleven files and every one was a `glm5_next_*` host file. Composition
was not available either: this OpId inventory has no general softmax, no general
reduction and no pooled gather, and its only top-k entries select experts and
sampler tokens.

`vt::Glm5NextKpoolCompress` is the learned pooling — `get_pooled_states`,
`modular_glm5_next.py:897-970` at transformers v5.16.1, the lane pin this row
already carries. `index_head_dim` independent softmaxes over a pool's members,
one per channel, with the learned intra-pool position embedding added to each
member's gate score, the pool grid starting at the first VALID token rather than
at slot 0. `vt::Glm5NextKpoolSelect` is the selection half of `forward`
(`:821-875`) with `get_visible_tokens` (`:877-895`) folded in as a predicate and
`append_visible_tail` (`:972-1022`) as the tail write.

THE COMPACTION IS ON THE DEVICE AND `P` NEVER REACHES THE HOST. `keep =
pool_valid.any(0)` (`:968`) is not cosmetic: `select_k` reads the compacted
width, so a `P` one too large moves the ragged tail's write offset by
`index_kpool` columns and changes what the final truncation keeps. The compress
op therefore does a per-pool validity pass, a single-block exclusive scan and a
compacted write, and publishes `P` as a `[1]` i32 DEVICE scalar the select
kernel reads on the device. Every buffer is sized at the static bound
`ceil(kv_len / index_kpool)`. That is the whole point: eleven DSA layers stop
paying a device-to-host round trip on every step, which is what #2415 asked to
decide.

Both ops register on kCUDA only. There is deliberately no CPU provider — the CPU
answer is `glm5_next_dsa.cpp` and it is this family's ORACLE, so registering it
again under these ids would make the seam its own golden. A CPU queue is refused
by name through `vt::GetOp`, and `vllm::glm5_next::KpoolDeviceOpsAvailable()`
mirrors `deepseek_v4::V4DeviceKernelsAvailable` so a forward can ask before it
builds operands.

RED FIRST, twice, on `bd2c14ce5` with the test in place and no implementation.
The first build stopped at `fatal error: vllm/model_executor/models/
glm5_next_device.h: No such file or directory`. Adding only that seam moved the
failure onto the ops themselves: `'kGlm5NextKpoolCompress' is not a member of
'vt::OpId'` and `'Glm5NextKpoolSelect' is not a member of 'vt'`, twelve
diagnostics naming the two ids and the two free functions. Both builds exited 1.

f32 throughout, because `:823` and `:960-964` are fp32 upstream. The host
reference widens to `double` and the device deliberately does not follow it: a
fp64 pool softmax would put the model path on the 1/64-rate pipe to be more
precise than the thing it mirrors, and no token gate can see a dtype that is too
wide. Every accumulation spells its rounding with `__fadd_rn` / `__fmul_rn` /
`__fdiv_rn` in the host reference's own order, so nvcc's default contraction
cannot decide the answer, and `head_scale` is narrowed once on the host in
double rather than taken from `rsqrtf`.

NOTHING CALLS EITHER OP YET. `ModelRegistry::Forward` still reaches the host
indexer, `glm5_next_forward.cpp:231-238` still refuses a non-CPU queue by name,
and the only callers are the new device gate and the availability probe. W9c-3
owns the wiring, on row
`MODEL-MM-glm5-next-glm5-next-for-conditional-generation`, tracked by #2410, and
the spec's O36 names all three. This is a staged slice under AGENTS.md "Nothing
lands dead", not a delivered capability.

FOLLOWING_AGENTS_PROTOCOL

Refs: #2415, #2410
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…the margin the top-k decides on, not by a number

The device gate compared `index_scores` against a flat 2e-5. That tolerance was
chosen rather than derived and it was wrong in both directions: the golden score
magnitudes on this fixture run to 45.17, so one f32 ULP there is already 3.8e-6,
and a bound below that fails on arithmetic while one far above it bounds
nothing. Top-k error is bimodal, so the only question a selection gate can
actually answer about a score is whether the numerical difference is small
against the gap the selection turns on.

So the gate now asserts `score_delta * 4 < margin` and prints both, where the
margin is the gap between the `select_k`-th and `select_k + 1`-th masked score
over the rows that actually prune. A future fixture that narrows the margin
fails here instead of quietly becoming a coin flip.

Two records move with it. W9c-0 gains the pre-lease numpy transcription's
numbers and, beside them, the bound that transcription does not cross: it fixes
the algorithm, and it says nothing about the build, nvcc, the launch geometry,
the shared-memory reductions or float rounding. And §W9c's device-gate block
said `dgx:gpu0` was queued and the GB10 leg PENDING; the fleet has since
answered it (`test_cuda_quant_dot` 17 cases / 177,284 assertions and the
GLM-5.3-Flash MoE geometry case's 25, on `sm_121a` with FA2 at `[121a]`), so the
sentence is corrected here rather than left asserting a PENDING that is no
longer true.

FOLLOWING_AGENTS_PROTOCOL

Refs: #2415, #2410
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…ts/CMakeLists.txt additions

`origin/main` moved 27 commits while this branch was written, and the only file
both sides touched is `tests/CMakeLists.txt`, where each added its own
`vllm_cpp_add_test` block at a different place. Git resolves that cleanly; the
resolution is checked by asserting BOTH blocks are present rather than by
trusting the clean exit, because a keyed record that automerges cleanly and
drops a tail row is a failure this repository has already paid for.

Nothing on `main` touches `include/vt/ops.h`'s enum tail, `src/vt/ops.cpp`,
`src/vt/op_provider.cpp`'s name switch, `src/vt/cuda/` or
`.agents/specs/glm5-next-flash.md`, so no claim in W9c-0 or O36 is falsified by
this merge. The device gate queued on `dgx:gpu0` measures `83fa7c8fb`, the SHA
recorded in the archive's `BASE_SHA`; the merge changes no file under test and
the job's own source sha256 says which bytes it built.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…two kCUDA lookups, with the reason beside them

`check-device-leakage.py` reds this branch at `kcuda: 2 > baseline 0`:
`glm5_next_device.cpp` names `vt::DeviceType::kCUDA` twice, and it lives under
`src/vllm`, which the checker scans as the device-agnostic shared layer.

The two sites are the checker's own named exception rather than a hole in it.
They are `vt::OpRegistered` lookups -- the "ask the op/provider table the
question instead" the error message points at -- in a resolver TU that holds no
CUDA code and is always compiled. This is the shape `laguna_device.cpp` already
carries at the same count for the same reason, and `deepseek_v4_device.cpp` at
four times it. So the entry goes in ALLOWLIST, per-file and per-bucket with an
exact expected count, and the baseline is NOT raised. A later edit that changes
the count reds as ALLOWLIST STALE, which is what keeps this from becoming a mute
switch.

The reason records what a reader of the count cannot infer: why there is no CPU
provider to fall back to (the CPU answer is `glm5_next_dsa.cpp` and it is the
ORACLE these kernels are gated against), what a CPU build therefore does (the
probe returns false and `vt::Glm5NextKpoolCompress` refuses by name through
`GetOp`), and which test asserts both. It also inherits the deferred follow-up
the other two entries carry -- thread a runner `DeviceType` through the resolver
instead of hardcoding kCUDA -- and says why it cannot be taken here: W9c-3
(#2410) owns the forward that would supply a runner, and until it lands there is
no device to read from.

FOLLOWING_AGENTS_PROTOCOL

Refs: #2415, #2410
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
@localai-org-maint-bot

Copy link
Copy Markdown
Collaborator Author

Status: the GB10 gate is queued, and one r1 attempt has already been thrown away

The device gate is still PENDING and is not reported as a pass. Recording what has moved so the next reader does not re-derive it.

r1 reached dgx:gpu0 and died in the build. CUDA FA2 compiled-arch manifest: [121a] — the arch assertion passed and the box is GB10 — and then ninja: error: unknown target 'vllm-cli'. The cause is mine and it is worth naming: the job configured with -DVLLM_CPP_BUILD_EXAMPLES=OFF and then asked the gate's own build step to build vllm-cli, which lives in examples/. So a by-product took the gate down with it, which is the one thing a by-product must never be able to do. The prose in that script already said vllm-cli "is a BY-PRODUCT of this lease and never this gate"; the code did not enforce it. r2 builds only test_glm5_next_kpool_device and test_glm5_next_dsa in the gate step and builds vllm-cli separately and non-fatally inside the probe leg.

r3 also makes the source hash FATAL rather than printed. r1 and r2 printed SRC_SHA256 and EXPECTED_SRC_SHA256 next to each other and compared neither — a half-written archive on a CIFS share would have read as a normal run. It now refuses. This matters here concretely: the archive was restaged from the devbox while the job sat in the queue, so the two genuinely can disagree.

The archive was restaged onto the branch head. The first archive was 83fa7c8fb, taken before origin/main was merged in. Every file the gate compiles was byte-identical between the two, but the surrounding tree was not, and a gate is only as good as the tree it ran on. The queued job now measures 4034c368c, the current head.

CPU evidence on the merged head

scripts/agent-preflight.sh --fail-on-skip exits 1: 142 gates ok, 1 failed, 5 skipped, and check-tree-compiles reports 580 of 580 translation units in scope compiled.

The one failure is test_cpu_x86_llamacpp_floor, in test_a_contended_leg_is_discarded_and_never_summarised, printing its own cause — load=22.69 23.15 17.38 builders=0. It is base-caused, and the attribution is structural rather than a judgement call: this branch touches no file that harness reads, and scripts/agent-preflight.sh and the harness itself are byte-identical between origin/main and this head. The 5 SKIPs are all "needs arguments preflight does not supply" (check-arm-isa-build.py, check-cpu-isa-build.py, check-cuda-fat-gencode.py, check-pr-size.py, check-triton-aot-multiarch.py) — the same shape, and the same byte-identical scripts.

Two failures in the first preflight run were mine and are fixed in 4034c368c: an undeclared role, and check-device-leakage.py reporting kcuda: 2 > baseline 0. The latter got an ALLOWLIST entry with its reason, not a raised baseline, mirroring what laguna_device.cpp already carries at the same count for the same resolver shape. The checker now reports DSR 32 == baseline 32, ratchet holds.

What the queued job will produce

Legs 1 and 2 (the device gate and the host reference suite), then a nine-mutation matrix on the CUDA TU — the keep compaction, the learned pool weighting replaced by a mean, the learned position embedding zeroed, pooling from slot 0, the ragged tail, the padded-row score rule, the ReLU, the registration, and a half-probe — each rebuilt with an assertion that the binary's sha256 moved before any post-mutation result is believed, each restored byte-for-byte by sha256. The registration mutation is expected to survive as a SKIP, and that is the finding rather than a kill: it is why the job reads the skipped-case count instead of the exit code.

Leg 3 is the --device cuda probe, and it is a by-product rather than a deliverable. This PR does not merge before the gate returns.

@localai-org-maint-bot

Copy link
Copy Markdown
Collaborator Author

Do not merge: the GB10 gate is queued behind a 5-hour job and has not returned

Recording the exact state so whoever reads this next does not re-derive it or, worse, read a queued gate as a taken one.

Job 79aa5bb5-7536-43fe-a051-ed73ac1302e1, dgx:gpu0, queued at #2 behind dflash2-staged/ima7.sh (submitted --max-runtime 5h) and ltx25-connector-repair. The rc client is detached (ppid=1), so the job survives the session that submitted it and does not need anyone to sit on it.

Where its result will be: /mnt/nas_share/rc/glm53-kpool/out/<worker>-b036a0138f5a-r3/run.log, keyed on the archive's sha256. binary.sha256, kpool.log, dsa.log, mut.M*.log and devcuda.stderr land beside it.

Read the assertion count, not the exit code. The three device cases in test_glm5_next_kpool_device return early with ... device gate is SKIPPED when no CUDA backend is registered, so a CPU-only link exits 0 having measured nothing. The job prints KPOOL_SKIPPED_CASES and says out loud that a non-zero value is not a device pass. A [doctest] assertions: 0 line is the same trap wearing a different hat.

What is already asserted before any result is believed, because each of these has produced a false green here before: the CUDA arch is read off the device and refused if unreadable; CUDA FA2 compiled-arch manifest must not be []; the staged archive's sha256 must equal the recorded one; the binary and every .so beside it must carry both the wrapper's and the kernel's own strings; and every mutation must move the binary's sha256 before its result counts.

The archive is 4034c368c — this branch's head, not an earlier tree. BASE_SHA on the share says so and the job compares it.

Three things this PR does not claim, stated plainly rather than left to inference: no device gate has been taken; no throughput number was measured and there is no denominator; and no generation from this artifact was observed by this branch. The ops land unreachedModelRegistry::Forward still reaches the host indexer and glm5_next_forward.cpp:231-238 still refuses a non-CPU queue by name. W9c-3 owns the wiring, #2410 tracks it, and the row's spec carries it as O36.

…ken on GB10, and the two mutations that are not assertion kills say so

The gate W9c-0 left PENDING has returned from `dgx:gpu0`. Recording it with the
box named beside every number, because `sm_121a` and `thor`'s `sm_110` are
different targets and neither transfers to the other.

`test_glm5_next_kpool_device` runs 4 cases / 918 assertions / 0 failed with
`KPOOL_SKIPPED_CASES=0`. The skip count is the load-bearing line and not the exit
code: the same binary on a CPU-only build passes 4 cases with FOUR assertions and
three cases skipped, so 918-against-4 is what says the device arm ran at all.
`test_glm5_next_dsa` is unmoved at 10 / 1934.

The device answer agrees with the transformers v5.16.1 RUN goldens to 3.58e-07 on
the pooled keys and 6.68e-06 on the scores, and its selection is SET-equal and
positionwise equal to both the oracle and the host reference at 0 mismatches of
462, against a smallest decision margin of 2.58e-03 over 17 pruning rows. That
ratio is the point: the fp32 device result differs from the fp64 host reference
by about a hundredth of the gap the top-k decides on, which is a statement a
bimodal gate can make. `P == 5` from `np == 6` says the `keep` compaction ran.

Nine mutations, each rebuilt with the BINARY's sha256 asserted to have moved
before its result counted, each restored byte-for-byte with the source hash
checked, and the pristine tree rebuilt afterwards to reproduce 918. Seven were
killed by assertions. The other two are recorded rather than counted.

M8 deleted the compress op's registration and SURVIVED AS A SKIP: the probe goes
false, `HasCuda()` goes false, and the suite exits 0 with 4 assertions and 3
skipped cases. That is the reachability answer and not a kill, and it is exactly
why the job reads the skip count.

M5 was killed by the COMPILER, which is weaker. Blanking the tail write orphaned
`valid` and `vis` and `-Werror=all-warnings` refused it with `error #177-D`. A
compiler kill proves the code changed and nothing about whether the gate sees the
defect, so it is rewritten to keep every operand live and re-submitted as job
`3db96e7a`. Until that returns the ragged tail is the one guarantee here with no
assertion-grade mutation behind it, and this entry says so rather than letting
seven-of-nine read as nine.

FOLLOWING_AGENTS_PROTOCOL

Refs: #2415, #2410
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
@localai-org-maint-bot

Copy link
Copy Markdown
Collaborator Author

The GB10 device gate is taken, and it passes

Every number below came from dgx:gpu0 and nowhere else. sm_121a and thor's sm_110 are different targets and neither transfers to the other.

job 79aa5bb5-7536-43fe-a051-ed73ac1302e1
device NVIDIA GB10, GPU-cb5c11ff-4ea1-5472-a9a6-c7a468a4d9f1, driver 580.173.02, compute_cap 12.1, built sm_121a
worker rc-worker-4b8lj, aarch64, 20 cores, boot_id 49b5d969-…
source git archive of 4034c368c, sha256 b036a0138f5a…, compared and fatal on mismatch
build CUTLASS 4.5.0, CUDA FA2 compiled-arch manifest: [121a], 605/605, BUILD=0
binary test_glm5_next_kpool_device sha256 39788d6d51c5fbe5…
run 2026-08-31T22:49:44Z → 23:10:05Z

The CUDA TU compiled clean on the first attempt under -Werror=all-warnings[546/605] Building CUDA object … cuda_glm5_next.cu.o. Until this run it had never been compiled by anything; the pre-lease numpy check bounded the algorithm and explicitly not the build.

Leg 1 — the gate

4 cases, 918 assertions, 0 failed, KPOOL_SKIPPED_CASES=0. The skip count is the load-bearing line, not the exit code: the same binary on a CPU-only build passes 4 cases with 4 assertions and three cases skipped. 918-against-4 is what says the device arm ran.

quantity measured
pool_keys max abs, device vs transformers v5.16.1 3.57628e-07
pool_keys max abs, device vs the host reference 1.78814e-07
index_scores max abs, device vs transformers 6.67572e-06
device P vs golden kNumPools 5 == 5, from np = 6 — the keep compaction ran
pruning rows / smallest decision margin 17 / 2.58482e-03
margin-tied bound score_delta * 4 < margin 2.67e-05 < 2.58e-03, 97x of room

Selection agreement is SET equality and positionwise equality, against the oracle and against the host reference, at 0 mismatches of 462. So the fp32 device answer differs from the fp64 host reference by about a hundredth of the gap the top-k turns on — the statement a bimodal gate can actually make.

Leg 2 — the host reference, unmoved

test_glm5_next_dsa 10 cases / 1934 assertions / 0 failed.

Nine mutations, and the two that are not assertion kills

Each applied to product code, rebuilt with the binary's sha256 asserted to have moved before its result counted, restored byte-for-byte with the source hash checked (9 restores, all verified), and the pristine tree rebuilt afterwards, reproducing 918.

mutation outcome
M2 keep compaction removed KILLED by assertion
M3 learned pool weighting → a mean KILLED by assertion
M3b learned intra-pool position embedding zeroed KILLED by assertion
M4 pool from slot 0, not the first valid token KILLED by assertion
M5 ragged visible tail never written BUILD_BROKE — a compiler kill
M6 score skipped on padded query rows KILLED by assertion
M7 the ReLU before the head mix dropped KILLED by assertion
M8 the compress op's RegisterOp deleted SURVIVED AS A SKIP
M9 the availability probe loses one clause KILLED by assertion

M8 is the reachability result, and it is not a kill. With no provider the probe goes false, HasCuda() goes false, and the suite exits 0 with 4 assertions and 3 skipped cases — the exact shape of a skip wearing a pass. There is no production call site to delete, so the mutation measures the registration and not a capability. This is why the job reads the skip count.

M5 was killed by the compiler, which is weaker, and it is owed. Blanking the tail write orphaned valid and vis; -Werror=all-warnings refused it with error #177-D: variable "valid" was declared but never referenced. That proves the code changed and nothing about whether the gate sees the defect. Rewritten as (valid && vis && idx < 0) — every operand live, and idx is non-negative for every reachable j, so the tail column is still always -1 — and re-submitted as job 3db96e7a-cfe9-4148-aac3-d26eb01a15bc. Until it returns, the ragged tail is the one guarantee here with no assertion-grade mutation behind it, and seven-of-nine should not be read as nine.

Still not claimed

No throughput number and no denominator. No generation from this artifact was observed. The ops remain unreachedModelRegistry::Forward still reaches the host indexer, glm5_next_forward.cpp:231-238 still refuses a non-CPU queue by name. W9c-3 owns the wiring, #2410 tracks it, spec O36 names it.

Evidence recorded in fe2f1dbe9. Leg 3 (the --device cuda probe, a by-product) is still running.

…ot the refusal this row keeps citing

W9c-0's lease drove `--device cuda` on the real `GLM-5.3-Flash-UD-Q2_K_XL`
artifact as a by-product, and it falsifies a sentence this row has repeated for
several waves.

The model loads, auto-fits the KV cache to 256 blocks and `max_model_len` 8192,
enters the engine, and dies in the KV binding: `'model.layers.3.self_attn.
indexer.k_cache' resolved to attn_kv index 45 but only 22 cache(s) arrived`
(`glm5_next_kv.cpp:127`). The guard at `glm5_next_forward.cpp:231-238` is still
in the tree and NEVER FIRES, because `ResolveAttnCache` throws before the forward
is entered. So "`--device cuda` still refuses by name at
`glm5_next_forward.cpp:231-238`" describes code that exists but no behaviour a
user can reach, and W9c-3 -- scoped as the compose that deletes that refusal --
will meet the KV binding first and earlier. The 22 is W5b-2c's own expected count
(11 latents plus 11 indexer caches, O29), so the name index is producing indices
from a larger space than the vector it indexes into.

This is the FIRST run that reached the engine. The prior probe passed the
checkpoint DIRECTORY rather than a shard, died in `hf_config` in five seconds
over an artifact that carries no `config.json`, and captured nothing; it is not a
baseline and is not cited as one.

What is deliberately NOT concluded: whether `--device cpu` reproduces it. This
lease drove the CUDA arm only, the auto-fit above is memory-dependent and may
differ per device, and O30's ` Paris.` predates several waves and a different
resolved config. A general KV-binding defect and a device-only one have different
owners, so O37 leaves the polarity open and names the one `--device cpu` run that
settles it rather than guessing.

Filed as #2445 and listed under `## Owed` as O37. Not repaired in flow: the fix
lives in the multi-KV index mapping W5b-2c owns, and the measurement above has to
come first. It is also not a claim about the k-pool ops this branch lands --
nothing on that path reaches them, and O36 already says they are unreached.

FOLLOWING_AGENTS_PROTOCOL

Refs: #2445, #2415, #2410, #2348
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
@localai-org-maint-bot

Copy link
Copy Markdown
Collaborator Author

Leg 3 (the by-product) found something, and it falsifies a sentence this row repeats

DEVICE_CUDA_PROBE_RC=1. Filed as #2445, recorded as O37 in b20267f16. It is a by-product of the lease, not this PR's deliverable, and it is not a claim about the k-pool ops.

Driven correctly — first shard, not the directory — --device cuda on the real GLM-5.3-Flash-UD-Q2_K_XL loads, auto-fits the KV cache, enters the engine, and dies somewhere the row does not predict:

INFO auto-fit max_model_len: reduced from 1048576 to 8192 to fit the KV cache (256 blocks x 32 tokens).
INFO recurrent-state budget: reduced max_num_seqs from 32 to 1.
engine-fatal: EngineCore busy loop threw: glm5_next KV binding:
  'model.layers.3.self_attn.indexer.k_cache' resolved to attn_kv index 45 but only 22 cache(s) arrived

The refusal at glm5_next_forward.cpp:231-238 is still in the tree and never fires. ResolveAttnCache (glm5_next_kv.cpp:127) throws first. So "--device cuda still refuses by name at glm5_next_forward.cpp:231-238" describes code that exists but no behaviour a user can reach — and W9c-3, scoped as the compose that deletes that refusal, will meet the KV binding first and earlier. The 22 is W5b-2c's own expected count (11 latents + 11 indexer caches), so the name index is producing indices from a larger space than the vector it indexes.

This is the first run that reached the engine. The prior probe passed the checkpoint directory, died in hf_config in five seconds over an artifact with no config.json, and captured nothing — it is not a baseline and is not cited as one.

Deliberately not concluded: whether --device cpu reproduces it. This lease drove the CUDA arm only, the auto-fit is memory-dependent and may differ per device, and O30's Paris. predates several waves and a different resolved config. A general KV-binding defect and a device-only one have different owners, so O37 leaves the polarity open and names the single --device cpu run that settles it.


Remaining before this is mergeable

M5 re-run, job 3db96e7a-cfe9-4148-aac3-d26eb01a15bc, queued on dgx:gpu0. Until it returns, the ragged visible tail is the one guarantee in this gate with no assertion-grade mutation behind it — it was killed by the compiler, which is weaker, and seven-of-nine should not be read as nine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MODEL-MM-GLM53-FLASH-CUDA: GLM-5.3-Flash's k-pool indexer has no device op, so the device arm is not a routing-only port

2 participants