fix(BACKEND-TENSTORRENT-HOST-FREE-FORWARD): make captured decode reachable and token-clean at tip (#1105) - #2470
Merged
mudler merged 5 commits intoSep 2, 2026
Conversation
…ure-arm wave (mudler#1105) The row's R4 gate line "capture completes (no TT_FATAL)" is unmet at main tip: VT_TT_DECODE_CAPTURE keeps support_static_graph_mode false by default (tenstorrent.cpp:85-88), so the CLI's default arm never primes the paged-KV shadows and every decode's device PA attempt fails into the host oracle; with capture armed, the first capture TT_FATALs on the capture-only EmbedDeviceIdsInto copy (cold program, mesh_workload.cpp:153). This wave makes the capture step warm its own capture-only segments before GraphCaptureScope, then measures captured replay against the ~18.5 tok/s hybrid opt-out. The spec ## Now also records this week's operator-gate evidence: golden gate PASS at tip; the widened 1.38x Qwen3-0.6B inversion; the Mistral-7B A/B that flips the polarity (default 11.51 vs opt-out 5.91) and makes the inversion model-specific; and the regime probes that reject the attempt-overhead mechanism on both models (identical failed-device-work profiles, 0 device-RAC successes anywhere), bounding the mechanism to the arms' successful paths. The failed-attempt candidate joins the rejected list under ## Owed. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:zai-glm-5.3-flash [maki]
…hable at tip (mudler#1105) Two defects kept every capture-armed decode at tip from completing; both are capture-warming defects, not numerics, and both fixes restore the R5-era capture behaviour that the QWEN35-wave staging edits accidentally retired. First, the capture-only embed segment (EmbedDeviceIdsInto and its hidden-shadow copy) had no eager predecessor, so its programs were cold at first capture and tt-metal refused to load binaries mid-trace (mesh_workload.cpp:153). The warm branch now runs the exact captured segment once before opening the scope; the hold semantics make the second run safe. Second, res.Zero at the top of the captured layer region left the fresh slot host-only: MemsetDeviceIfCapture refused a shadow-less slot and MemsetDeviceFill refuses under capture, so layer-0 EnsureDevice2D restaged from the recycled slot's stale persistent buffer — an enqueue_write, fatal at fd_mesh_command_queue.cpp:760. The no-shadow arm now serves the zero on-device under capture: in-place into the slot's persistent buffer when the [1, bytes/2] geometry matches, else a fresh ttnn::empty installed as persistent. Capture-only is load-bearing: bytes do not name a dtype (f32 KV masters share the pool blocks), so the eager fresh-slot zero keeps its host fallback. The zero tensor and copy program are already warm from the cold step's restage and copy lane. Evidence (P150, one GPU lock): focused capture-armed 80-token gate green on mechanics (rc 0, 0 fatals, 78 replays) — the arm's OUTPUT is not token-clean at this commit: the cold step emits a deterministic wrong first decode token and the run continues on the corrupted history (mudler#2461, pre-existing, replay-exact so only step 1 diverges; the earlier "coherent" claim here was a measurement error, corrected in the row spec). Default-arm golden re-run 16/16, 125/125 PASS; same-binary A/B/C warm medians — default 12.95, opt-out 17.68, captured 27.57 tok/s (2.13x / 1.56x; the wave's beat-~18.5 bar cleared on mechanics, replays=474 and 0 fatals on every capture leg). mudler#1625 boundary sharpened: same-prompt --repeat 2 capture is clean; the 16-prompt battery spins in pure userspace after one capture (cross-prompt KV-geometry re-warm under a live trace). The capture-default flip stays blocked on mudler#1625, and the arm's token gate stays blocked on mudler#2461; the spec records both. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:zai-glm-5.3-flash [maki]
…s evidence record (mudler#2461) The wave's fresh mutation review returned PASS on every claimed guarantee, with one caveat outside its C1-C4 protocol: the reviewer's rerun of the capture-armed focused gate printed deterministic gibberish where the evidence record said "coherent". The operator rerun reproduced it, and the A/B/C capture-leg logs hold the same bytes — the output was never clean; the focused gate's word-count criterion had been counting tt-metal log lines on stdout. Per-step adjudication (VT_TT_DUMP_KV) localizes every divergence to the driver's eager cold pre-warm step (48755 vs plain-eager 3364 " story" on identical inputs, top-2 gap 1.69): prefill is exact, replays are token-exact given their history (golden battery prompts 1-15), and the capture-armed battery's single 1/32 failure is the same cold-step token that this record had mis-filed as a near-tie. The defect is pre-existing (both R4 fixes are provably out of the cold path) and is now owned by mudler#2461, which blocks the PR and the wave's own "answer coherent vs the default arm" gate line. The A/B/C throughput ratios are re-labeled as machinery measurements, not a capability verdict, per correctness-first. This commit corrects the record only; the code under review is unchanged from the reviewed head modulo the implementation commit's body amendment. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:zai-glm-5.3-flash [maki]
… with the view's real strides (mudler#2461) The capture arm's deterministic wrong first decode token was a stride-fabricated descriptor, not driver attn metadata. WarmPagedKvShadow described the flash-KV unbind(1) slice — a rank-4 view of the combined [nb,2,bs,nkv,d] store whose true block stride is 2*bs*nkv*d (dense_attn_block.h KvSlice) — as Tensor::Contiguous, so the shadow prefix upload indexed block b at b*bs*nkv*d elements: one slab early inside the combined buffer. For every physical block >= 1 the K shadow received the previous block's V slab (zeros at prefill positions) and the V shadow the next block's K slab, so the cold step attended over zeros and emitted argmax 48755 where the default arm emits 3364. Block 0 was accidentally safe (offset 0 is each view's own base), the default arm never stages a shadow, and replays replayed the same corrupted shadow self-consistently, which is why only the cold step diverged. The fix builds the descriptor with the view's real strides and threads an accept_unbind_view flag (default false) through EnsurePagedKvTtnn to NhdToTtnnLayoutPrefix, admitting the view only on the warm/capture staging path; every existing caller keeps strict contiguity and the eager path is unchanged. Red to green (Qwen3-0.6B, same binaries): capture arm cold argmax 48755 then 3364 with coherent text; the capture-armed golden battery's single failure moves from prompt[0] tok=1 to prompt[1] tok=1 (engine 374 vs anchor 572, the documented cross-request shadow residual, owned separately); default golden battery 125/125; test_tenstorrent_backend green. Per-step adjudication (VT_TT_DUMP_KV): the cold step is argmax-exact and the first divergence is step 2 at capture top-2 gap 0.375 nats, inside the near-tie band. Token-clean same-binary A/B/C re-measurement (hf-gate3.sh, warm medians): default 12.90 / opt-out 17.80 / captured 27.47 tok/s (2.13x / 1.54x), replays=474 and 0 fatals on every leg, capture legs verified coherent, so the captured replay ratios are now a capability verdict. The row spec records the full wave block. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:zai-glm-5.3-flash [maki]
…de position at request boundaries (mudler#2469) The captured regime never re-seeded the persistent decode position across requests. DecodePosCache()[num_reqs].cur_pos is one persistent device tensor aliased into RacIdxEntry.update_idxs and PaMetaEntry.cur_pos, and its only re-seed lives in WarmDecodePos's seed branch: the replay regime early-returns on captured(), and WarmPaMeta's r2_steady is process-global. A request that recycles a size slot whose trace is still captured therefore kept the dead request's final cur_pos: paged attention attended the dead request's decode KV rows, RAC wrote at the stale virtual position, and the golden battery decoded the previous prompt's tokens — the red battery failed at prompt[1] tok=1 with " Paris"/" France"/" is", the previous prompt's continuation. The repair works at the three consumed surfaces. Qwen3DenseDecodeGraph size slots carry expected_cur_pos, seeded to seq_lens-1 on every seeding step and advanced once per completed replay/capture launch, and Step gates WarmDecodePos's replay regime on the continuation predicate seq_lens[0]-1 == expected_cur_pos. A same-size boundary (captured && !continuation) routes through the proven mudler#1476 re-capture lane: destroy the trace, run this step eagerly against the freshly seeded position, re-capture on the next step. Seed-only was tried and rejected: with the boundary seed verified correct on device, the first post-boundary replay still drifted to a near-tie wrong token (battery ids 13/11/264 across builds vs 374 unfixed), so the boundary re-capture lane is required. On the device side, DecodePosEntry.host_val mirrors what cur_pos holds on device and WarmPaMeta echoes it into cp_host, repairing a vacuous guard: the unconditional e.cp_host = cpos made TryPagedAttentionDeviceDecode's cp_host[0] == seq_lens[0]-1 check compare the host against itself, so it could never fire on a stale device tensor. plus_one_scratch is allocated once per entry and reused, so the boundary seed performs no fresh device allocation under a live trace (the allocator's corruption-under-trace warning). Green (Qwen3-0.6B, P150, this build, all under flock $HOME/gpu.lock): capture-armed golden battery 125/125 assertions, 16/16 prompts PASS, max gap 0.375 nats @ prompt[1] tok=1 — inside the ≤0.5 near-tie band this row already adjudicates — with kPagedAttention selections=1344 and 0 declines; the default arm is unchanged (125/125, 16/16, same max gap); the captured CLI answer is coherent and byte-identical across three runs including one after a tt-smi -r 0 reset, the default-arm CLI answer is coherent with a first-token near-tie flip, and no stale-prompt tokens appear anywhere. The diagnostic probes this investigation added (KV row checksums, cur_pos/page-table/id readbacks, a boundary trace print) were dropped from this port per session precedent: investigation instruments never ride in the fix commit. The row spec records the full evidence; the GDN matrix anchor this shift moved is repaired in the same change. Review repairs: the single-row scope of the continuation predicate (continuous-batching request swap) and the uncounted guard-decline path are recorded under ## Owed; the drift-id attribution now names only the kept artifacts. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:zai-glm-5.3-flash [maki]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(BACKEND-TENSTORRENT-HOST-FREE-FORWARD): make captured decode reachable and token-clean at tip (#1105)
Four strands, one change: two capture-warming defects that kept every
capture-armed decode from completing at tip, one staging defect that
made the completing runs deterministically wrong, and one stale-state
defect that corrupted the first decoded token of every request after
the first in a multi-request capture session.
The capture-only embed segment (EmbedDeviceIdsInto and its
hidden-shadow copy) had no eager predecessor, so its programs were cold
at first capture and tt-metal refused to load binaries mid-trace
(mesh_workload.cpp:153). The warm branch now runs the exact captured
segment once before opening the scope; the hold semantics make the
second run safe.
res.Zero at the top of the captured layer region left the fresh slot
host-only: MemsetDeviceIfCapture refused a shadow-less slot and
MemsetDeviceFill refuses under capture, so layer-0 EnsureDevice2D
restaged from the recycled slot's stale persistent buffer — an
enqueue_write, fatal at fd_mesh_command_queue.cpp:760. The no-shadow arm
now serves the zero on-device under capture: in-place into the slot's
persistent buffer when the [1, bytes/2] geometry matches, else a fresh
ttnn::empty installed as persistent. Capture-only is load-bearing: bytes
do not name a dtype (f32 KV masters share the pool blocks), so the eager
fresh-slot zero keeps its host fallback.
The completing runs were still wrong: WarmPagedKvShadow described the
flash-KV unbind(1) view — a rank-4 slice of the combined
[nb,2,bs,nkv,d] store whose true block stride is 2bsnkv*d — as
Tensor::Contiguous, so the shadow prefix upload indexed one slab early
and, for every physical block >= 1, the K shadow received the V slab
(zeros at prefill positions). The cold step computed on the mis-staged
shadow (deterministic wrong first token, 48755 vs 3364) and replays
re-consumed the same wrong-but-self-consistent state. The staging is now
stride-aware: an IsFlashKvUnbindView helper, a general-stride upload
path behind accept_unbind_view (defaults false, so every existing caller
keeps strict contiguity), and the warm path passing the flag. The
eager/default arm never entered the mis-staged path (its device PA
declines on the same contiguity check and falls back to the host
oracle), so it is untouched.
Even with clean staging, a recycled decode slot crossed a request
boundary with a stale on-device position (TT capture arm: cross-request persistent KV-shadow state corrupts the second sequential request (capture battery prompt[1] tok=1) #2469):
DecodePosCache()[num_reqs].cur_pos is a persistent device tensor aliased
into both the RAC write index and the PA meta, and in the captured
regime no steady-state path re-seeded it — WarmDecodePos early-returned
on captured() and WarmPaMeta's r2_steady is process-global — so
prompt[1] tok=1 attended prompt[0]'s decode KV rows still resident in
the recycled physical block and RAC wrote at the stale virtual position;
the wrong values decode to the previous prompt's own tokens. SizeSlot
now tracks the position its trace expects and gates the replay regime on
a per-slot continuation predicate; a same-size boundary routes through
the TT host-free captured decode goes degenerate at the first KV block boundary; token-exactness evidence does not reproduce #1476 re-capture lane (destroy the trace, run the step eager against
the freshly seeded position, re-capture on the next step) — the boundary
seed alone measured insufficient and, without the lane, the battery does
not complete at all. WarmPaMeta now echoes a device-truth mirror into
cp_host, so the device-PA guard reads what the device holds instead of
comparing the host's expectation against itself (the repaired guard and
its host fallback jointly absorb a missed seed, so the battery pins the
pair, not the predicate alone).
Evidence (P150, one GPU lock, fresh reviewer mutations all red with
sha256-verified restoration): focused capture-armed gate token-clean at
the cold step (3364 == default arm, coherent text, 0 fatals, healthy
replay count); at the merged head the capture-armed golden battery
passes 125/125 assertions and 16/16 prompts with a max top-2 gap of
0.375 nats at the formerly-failing cell (prompt[1] tok=1, inside the
row's <=0.5 near-tie band), the default arm identical, and the capture
CLI byte-identical across three runs including one after a card reset;
same-binary A/B/C warm medians default 12.90 / opt-out 17.80 / captured
27.47 tok/s (~2.1x / ~1.6x), the capture legs coherent for the first
time. Qwen3-4B showed the same capture-arm corruption before the fixes
and is covered by the same backend-level repairs. The earlier wave
record's "coherent" claim for a pre-repair head was a measurement error
(a word-count gate counted tt-metal log lines); the record correction
(3d06b42) is part of this change.
The capture arm stays opt-in (VT_TT_DECODE_CAPTURE). The capture-default
flip stays blocked on #1625 (cross-prompt geometry re-warm spins in pure
userspace under a live trace); the multi-request exactness blocker
#2469 is fixed by strand 4, with the continuous-batching (row r>0) scope
and the uncounted guard-decline counter recorded under the spec's
Owed.
Closes #2461
Closes #2469
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:zai-glm-5.3-flash [maki]