Skip to content

fix(BACKEND-TENSTORRENT-HOST-FREE-FORWARD): make captured decode reachable and token-clean at tip (#1105) - #2470

Merged
mudler merged 5 commits into
mudler:mainfrom
lu-zero:row/BACKEND-TENSTORRENT-HOST-FREE-FORWARD
Sep 2, 2026
Merged

fix(BACKEND-TENSTORRENT-HOST-FREE-FORWARD): make captured decode reachable and token-clean at tip (#1105)#2470
mudler merged 5 commits into
mudler:mainfrom
lu-zero:row/BACKEND-TENSTORRENT-HOST-FREE-FORWARD

Conversation

@lu-zero

@lu-zero lu-zero commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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]

…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]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants