fix(BACKEND-TENSTORRENT-QWEN35): EnsureDevice2D consumed stale host bytes under host-free decode; Qwen3.5-0.8B sacred gate 16/16 on ambient - #1768
Conversation
…nt — allow-list, op delta, first e2e gate (mudler#1715) The wiring row the GDN spec's ## Owed names: register Qwen3_5ForConditionalGeneration on the TT allow-list (landing last — the discrete P150 refuses an op miss by name, never falls back to CPU), complete the op delta the 0.8B forward refuses by name (candidates from source inspection: kGdnPostConv and kSigmoidGateBf16 are direct dispatches with no composite fallback; kAttnQkNormRopeGate and kAttnQkNormRope are registration-guarded fused preambles whose unfused path needs an also-unregistered AttnGateSplit — a W0 refusal sweep pins the authoritative list), and prove the family end to end with the device-golden pair treatment the Mistral row ratified (VT_DUMP_IDS bootstrap, transformers teacher-forced near-tie gaps via scripts/qwen3-neartie-gap-transformers.py, 16/16 with BACKEND PROOF). Capacity decision recorded, not deferred: Qwen/Qwen3.5-0.8B bf16 ~1.6 GB fits the proven 14.5 GB envelope; 27B bf16 ~53.8 GB and the GGUF k-quant arms (no TT kernels) are refused by name and owed. Weights download authorized 2026-08-23; revision pinned at 2fc0636471. W3 picks up the GDN row's reviewer leftovers (d2h counter completeness, the conv_transposed fast-path check). Records: the backend-matrix row, the claim, and both checker re-pins (check-agent-record BACKEND 86->87; check-gate-commands RUNNABLE_BASELINE +row, inherited credit in the mudler#1541 shape — the full TT suite, CPU gate, and preflight run today; the row's own sweep/e2e gates are owed). Nothing implemented yet; W0-W3 owed in the spec. Issue mudler#1715 stays open until the family runs. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:zai-glm-5.3 [Maki]
…fused ops + ScatterRowsExact fix (mudler#1715) The W0 refusal sweep ran the Qwen3.5-0.8B forward on the P150 with scratch CPU stubs and surfaced three by-name op refusals this change implements as native Tenstorrent kernels (SigmoidGateBf16, GdnPostConv, AttnQkNormRopeGate), each red-first and gated doctest-vs-CPU-oracle. The softplus inside GdnPostConv composes relu(x)+log1p(exp(-|x|)) in f32 because the one-shot SFPU softplus poly carries ~1e-6 absolute fit error that becomes 4.4e-4 relative on small g values (measured, first green attempt failed 5/100). The q|gate split of AttnQkNormRopeGate happens in the host gather because slice+reshape on a fresh from_vector TILE upload returned wrong data on the P150 while the full-width leg stayed correct; every leg now uploads already-shaped. ScatterRowsExact moves to a zero-copy ROW_MAJOR view + indexed_fill so the Qwen3.5 GDN cache shapes stop OOMing (32 GiB TILE inflation) and overflowing L1 (ROW_MAJOR reshape); kGdnDecode/kGdnStateScatter register the real kernels, and all five W0 sweep stubs are deleted so the tree carries only real kernels. The scratch arch allow-list is reverted; the arch stays refused at load until W2, and the CausalConv1dUpdate bf16-state capability refusal is escalated in ## Owed. Full gate both legs (ambient and VT_TT_HOST_FREE_DECODE=0): 36/36 cases, 2259/2259 assertions. Negative mutations M1-M4 all red, restored byte-for-byte. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:zai-glm-5.3 [Maki]
…ow-list, split-shadow scatter, pool-tenancy seam, gemma bake (mudler#1715) Lands the W2a and W2b engine waves for the Qwen3.5 GDN family on the Blackhole P150, each pinned by the evidence recorded in .agents/specs/tenstorrent-qwen35.md. W2a: SupportsCompressedConvState/SupportsCompressedGdnState flip to true (the TT kernels compute through f32 shadows and re-round committed values through bf16 storage, mirroring CUDA), the arch joins TenstorrentPlatform::supports_model_architecture last in implementation order, ScatterRowsExact stages born-split shadows (SplitFactor keeps indexed_fill pages inside L1 — the 262144-wide ssm_state row previously threw "grow to 2208704 B"), and the parity gate gains its Tenstorrent device-golden harness plus wide-row gather/scatter/decode arms. Pool tenancy: DevicePool::Get hands recycled blocks to new tensors without passing through Alloc, so Tenstorrent's host-pointer-keyed device shadows survived tenancy changes and the next reader downloaded the PREVIOUS tenant's dead tensor (got 2048 want 4096 on a [4,1024] readback). New vt::Backend::OnScratchBlockAcquired (default no-op) is called on every free-list hit; Tenstorrent overrides with MarkHostWritten. CommitDeviceLogical2D now asserts the committed device volume against rows*cols so producer shape bugs die at their producer. Gemma: RmsNormKernel's device arm handed ttnn::rms_norm the raw affine, but Qwen3.5 sets gemma=true at 21 sites — under host-free decode every forced- device norm dropped the +1 and collapsed prefill to degenerate tokens. The device arm now bakes w+1 host-side in f32 (the oracle's order, as the fused preamble's weff does) into a transient gamma that bypasses the raw- affine slot cache. Pinned by a new op-level case whose drop-the-bake mutation goes red. Diagnostics (env-gated or error-path-only): DownloadToHost reports caller context, device shape/dtype/layout and a backtrace on mismatch; VT_TT_SLOT_TRACE logs slot register/unregister/commit/ensure; the d2d copy hook reports pointers and frames. All four located today's defects. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
… diagnosis chains, fixed-engine golden pair (mudler#1715) The spec's W2b sections record the full bisection chains and evidence logs: the pool-tenancy defect (slot-trace pinned the stale [8,256] bf16 shadow to the previous tenant of a recycled DevicePool block; mutation cell closed at e2e depth) and the gemma defect (per-branch kill-switch convicted the forced-device residual+RMS arm; drop-the-bake mutation red at op level). The claim file moves to the 2026-08-24 state. The Tenstorrent device-golden pair (our_ids_tenstorrent.npy + neartie_gap_mnats_tenstorrent.npy, teacher-forced via qwen3-neartie-gap-transformers.py on torch 2.7.1+cpu / transformers 5.8.1) describes the FIXED engine's greedy sequence: 247/256 steps inside the 500 mnats near-tie band. The pair is committed now so the remaining red is attributable to the six named out-of-band steps (defect 2b, leg-independent, intrinsic) rather than to missing artifacts; the full gate stays open until 2b closes. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
…to the qkvz projection output (mudler#1715) Adds two env-gated debug hooks to the Qwen3.5 paged forward: DumpGdnStage inside GdnBlockPaged (mixed/conv/postconv/core/gated intermediates under $VT_DUMP_ACT, numbered per invocation) and a pre-layer dual-stream snapshot beside the existing per-layer residual dump. The hooks localize defect 2b on prompt 1: pre-layer inputs are bit-identical between the CPU and Tenstorrent arms; post_input_norm is exact (max 0.0098, corr 1.0 — the gemma bake holds); the qkvz projection output already carries scattered gross errors (max_abs 2.1, sign flips, ~0.3% of elements), reproduced identically under VT_GDN_IN_BF16=0. The bf16 activation path and dtype policy are exonerated; the suspect is the projection GEMM on TT at [T,1024]x[1024,6144] with real layer-0 weights. Next: op-level replay of that shape against the CPU result, then read the winning kernel's accumulation path. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
… capture-reliability finding (mudler#1715) Adds VT_DUMP_QKVZ per-invocation captures (mixer input h, resident merged qkvz weight, BA resident weight, and per-call x/out at the MatmulBTRawD boundary) and records what they proved and disproved. Proven by isolated replay with real captured bytes: kMatmulBT is clean at the engine's exact shapes and dtype combinations, including the split-arm BA signature with the REAL h0/w_ba bytes — worst errors are bf16-envelope. Disproven by behavior: the stage-dump claims of garbage projection/BA outputs cannot describe the live dataflow — the engine stays fluent where the captures read 1e38/NaN, and VT_POOL_BYPASS=1 output is identical to the pooled run, so pool-block state is not the carrier either. The capture path itself (DBuf-tmp download under host-free decode, likely via the d2d clone interaction) can serve bytes the live chain never consumes. Every named-kernel claim derived from those dumps is withdrawn; the behavioral ground truth stands (247/256 in-band, six out-of-band steps, solo == batch, f32 leg reproduces, bypass neutral). Next: trusted-capture re-localization (dual-read verification or =0-leg dumps) before naming any kernel for defect 2b. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
|
The Tenstorrent Qwen3.5 implementation is mergeable and the functional/record gates are otherwise green, but the repository's |
… the mixer-input identity anomaly (mudler#1715) Re-runs the stage localization entirely on the VT_TT_HOST_FREE_DECODE=0 leg where no host-free capture hooks are active. Confirmed under the trusted path: the residual stream diverges from layer 0; layer 0's post_input_norm is BIT-IDENTICAL between arms (both run the host gemma arm there); post_attn_norm diverges (max 3.28, corr 0.965) — the defect is born inside the layer-0 GDN mixer, whose real-pass stages diverge progressively (mixed 4.21/0.82 down to gated 3.98/0.66). Adds the triple-read probe and records a precisely-bounded identity anomaly: the buffer captured at ProjectGdnQkvz differs from the bit-identical post_input_norm by max 4.06 / corr 0.85 SYMMETRICALLY on both arms, at the SAME data pointer, while pre- and post-mixer reads return the original bytes — with slot trace showing one registration and no writers, pool bypass neutral, and isolated replays clean. Both explanatory horns contradict surviving facts; the next session breaks the tie with an in-process three-point hash plus immediate op-replay of the pre-GEMM bytes (no cross-process assumptions). FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
…consumed another slice's staging (mudler#1715) The staging cache keyed hits on (base slot, dims): an interior slice view of a packed weight resolved to the base slot and, when a previous view with equal dims had staged, consumed THAT slice's device bytes. Engine proof on Qwen3.5: the `a` projection of every GDN layer computed with the `b` weight rows — TT-a equaled CPU-b at corr 0.99998 while sharing the b input bit-for-bit — poisoning g/beta and diverging the mixer from layer 0. Fix: EnsureDevice2D hits and stores require t.data == slot host base; interior views upload from their own bytes as unregistered transients, and an interior view of a device-current base refuses loudly instead of serving stale bytes. Same base-pointer guard applied to EnsureAffine1D. Permanent regression case runs the engine's exact b-then-a sliced-view sequence twice against numerically far-apart halves; the mutation that neuters the base-pointer check goes red at worst 49.5. Post-fix trusted localization (=0 leg, whole-allocation dumps only): layer-0 divergence shrinks but persists, now pinned on kCausalConv1dFwd (conv output max 1.99 corr 0.999 between arms whose inputs match; the arms take different conv sub-paths — indexed gather vs manual). Next: whole-tensor replay of the conv with real captured inputs. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
…weight staging verified bit-perfect (mudler#1715) Adds an ops-layer debug seam (declared in tenstorrent_device.h, defined in tenstorrent_ops.cpp) that stages a tensor through EnsureDevice2D exactly as a consuming kernel would and reads the DEVICE copy back as f32. Model-side VT_DUMP_QKVZ capture now emits both the host master (w_host.bin) and this device readback (w_device.bin) for the merged GDN qkvz resident. First result: TT's device-staged in_proj_qkvz is BIT-PERFECT against the host master across all 8,388,608 elements — weight staging is exonerated, and host masters are byte-identical across arms, exonerating the loader. The spec records a MEASUREMENT RESET: several cross-run divergence numbers from ad-hoc captures mixed dtypes (bf16 bytes parsed as f32) and paired matmul calls across arms that take different projection arms, so those specific numbers are withdrawn. What stands: the behavioral signature, the layer-0 mixer origin on whole-allocation =0 dumps, and the three fixed defects. Next session builds one dtype-explicit dual-read dump utility and redoes the stage localization through it. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
…harness (mudler#1715) Adds TrustDump (ops-layer, declared beside DebugDeviceReadbackF32): whole- allocation dumps with a typed header (magic/dtype/rank/dims/numel/verified) and DUAL-READ verification — two independent Synchronize+Copy passes must agree byte-for-byte or the payload is omitted. Wired to the GDN mixer stages (packed/mixed/z/conv/pc_q/pc_v/core/gated) on both arms. First trusted results on the =0 leg: post_input_norm bit-identical; conv matches (max 0.035); gated matches within envelope (max 0.039); pc_q is UNCORRELATED (corr +0.028) yet correctly l2-normalized per head on both arms — and mutual-nearest matching rules out a layout permutation. Since gated sits downstream of ql2 and still matches, one of the two readings is lying about buffer identity; the pool itself is exonerated by a 200k-cycle Get/Put hammer (zero collisions). Next session instruments inside kGdnPostConvKernel at the commit site and captures g/beta, which were never dumped. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
…he TrustDump commit; preflight pipe swallowed the red) (mudler#1715) The TrustDump commit was pushed while check-env-doc was red because the gate ran through a pipeline whose exit status came from tail. Own it, repair it, and note the lesson: never interpose a filter between a gate and its consumer when the gate's status is the chain condition. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
…r misalignment; mixer chain exonerated (mudler#1715) Adds kernel-commit-site TrustDump captures inside kGdnPostConvKernel (q/k/v/g/beta at the CommitDeviceLogical2D sites, plus the g-chain intermediates) and split/merged BA-projection device-truth captures in ProjectGdnBA, all under VT_DUMP_TRUST. The trusted stage matrix then collapsed: emission-aligned comparison across all 18 layers shows pc_q corr >= 0.9991, gated >= 0.9983 and conv >= 0.9996 on every layer. The earlier "pc_q uncorrelated while gated matches" contradiction was an analysis artifact — sorted()[0] paired CPU layer-0 against TT layer-11 because the arms emit different dump-site mixes. Supporting checks along the way: kernel q_out == model-side dump bit-exact; split-path BA gate inputs read real values on the =0 leg with device==host. Two instrument caveats are recorded in the spec as owed work: TrustDump must refresh host bytes via EnsureHostBytes before its dual read (the ambient leg reads stale copies that dual-read verification cannot catch), and VT_GLUE_FUSE=0 is not runnable on Tenstorrent because GdnConvSplit has no native kernel. With the mixer chain exonerated, the remaining gap narrows to the decode-phase packed path, state carry, or the ambient staleness family itself. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
… under host-free decode (mudler#1715) Root cause of the ambient-leg corruption. Under host-free decode a producer commits device-only (host_current=false); EnsureDevice2D then built its upload buffer from the slot's HOST bytes without refreshing them, uploaded pool-fresh zeros to the device, and marked the slot host_current=true — corrupting both the value and the residency record for every later reader. Behavioral split, same build: ambient emitted "!!!(1, 2, 3, 4, 5" for "The capital of France is" while VT_TT_HOST_FREE_DECODE=0 emitted "Paris.". Emission-aligned trusted dumps localized the zeroing between the embedding (real on both legs) and layer 0's mixer input (all-zero on ambient only). With the one-line EnsureHostBytes refresh before the read loop, ambient without any instrumentation completes coherently ("Water boils at" -> "100°C. If a 100"); the =0 leg is unchanged. TrustDump gets the same refresh: its Copy resolves t.data through whichever memory the address maps to, so ambient dual-read verification could not see stale host bytes. Interim dump instrumentation masked this defect because that same refresh heals exactly the state the defect corrupts; recorded in the spec as a Heisenbug note. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
…ncy fix — sacred gate 16/16 on ambient (mudler#1715) Quantified parity against the pinned ROCm oracle greedy_ids on the DEFAULT ambient configuration: exact token cells 135/256 -> 213/256, fully-exact prompts 5/16 -> 10/16. The fixed engine's tokens match the pinned oracle exactly on cells where the stale anchor diverged (prompt[1] tok0 our == oracle == 303 vs anchor 948; prompt[12] tok0 9565 both) — the stale pair encoded the corrupt-ambient zeros, which is the justified-drift proof the gate's own message requires before re-derivation. Re-derived via the sanctioned procedure: VT_DUMP_IDS=1 bootstrap dump of the fresh sequence, then qwen3-neartie-gap-transformers.py teacher-forcing against the transformers secondary oracle. Max teacher-forced gap 0.375 nats; every divergence inside the 0.5-nat near-tie band. Full gate verdict on the board: 16/16 prompts PASS (10 strict token-exact, 6 near-tie-band only), 0 forward-divergent, doctest 146/146 SUCCESS. Mutation proof stands from the previous commit's scratch build: removing the EnsureDevice2D refresh regresses ambient to the exact pre-fix garbage; restored byte-for-byte afterward. Owed, recorded in the spec: the test binary segfaults during teardown after printing its verdict (ttnn deallocate / GraphTracker device-destruction order); verdict unaffected. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
…T-QWEN35 ahead of review Union-resolved the single conflict in scripts/check-gate-commands.py (both sides appended their row ID to the runnable population). No source conflicts; the residency fix, TrustDump refresh, and re-derived TT golden pair carry over unchanged. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
|
Triaging the reds on this draft so you know which are yours.
This is AGENTS.md
Leaving the rest to finish. Ping me when you take it out of draft and I will review the Tenstorrent op delta and the e2e gate properly. |
… break and a false-necessity claim (mudler#1715) Fresh-review findings on 1d07d49, repaired: F1 (medium): TrustDump and DebugDeviceReadbackF32 are defined only in the tt-gated ops TU but model TUs call them under a runtime env check, so a default (non-tt-metal) configure of examples/vllm-cli failed to link with undefined references. Added inline no-op stubs under !VLLM_CPP_TENSTORRENT in tenstorrent_device.h, mirroring the WarmPagedKvShadow pattern. Verified: default OFF-build now links clean (503/503) and the tt-on tree still builds. F2 (low): TrustDump's entry EnsureHostBytes was claimed necessary; it is redundant today because Backend::Copy already refreshes the source (tenstorrent_backend.cpp:66-68, since 7faa9c6). Reworded the comment as defense-in-depth and corrected the spec's instrument-caveat note: the ambient all-zero readings were poisoned-slot truth from the EnsureDevice2D defect plus bf16/f32 decode mistakes in analysis scripts, not an unsound Copy path. The necessity claim is withdrawn; only EnsureDevice2D prevents the corruption class. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:x-preview-f-free [opencode]
What changed
Qwen3.5-0.8B on Tenstorrent produced corrupt text on the DEFAULT configuration
(host-free decode,
VT_TT_HOST_FREE_DECODEunset): "The capital of France is"completed as
!!!(1, 2, 3, 4, 5whileVT_TT_HOST_FREE_DECODE=0completedParis.. This row lands the root-cause fix, the measurement harness thatfound it, and the re-derived correctness goldens.
EnsureDevice2D(src/vt/tenstorrent/tenstorrent_ops.cpp) built its uploadbuffer from HOST bytes without checking slot residency. Under host-free
decode a producer commits device-only (
host_current=false), so the nextconsumer staged pool-fresh zeros onto the device and marked the slot
host_current=true, corrupting both value and record for every laterreader. Fix: one
EnsureHostBytes(t.data)before the read loop.TrustDumpgains the same refresh as defense-in-depth (review finding F2:redundant today since
Backend::Copyalready refreshes its source).!VLLM_CPP_TENSTORRENTforTrustDump/DebugDeviceReadbackF32: model TUs call them under a runtime env check,and default (non-tt-metal) configure of examples previously failed to link.
tests/parity/goldens/qwen35_greedy_0_8b/{our_ids_tenstorrent.npy, neartie_gap_mnats_tenstorrent.npy}via the sanctioned procedure(VT_DUMP_IDS=1 bootstrap, then qwen3-neartie-gap-transformers.py
teacher-forcing against the transformers secondary oracle).
.agents/specs/tenstorrent-qwen35.mdcarries the full evidence chain,including two methodology post-mortems (a layer-misalignment analysis bug
and a Heisenbug note: interim dump instrumentation masked this defect
because EnsureHostBytes' side effect heals exactly the corrupted state).
Why is the change needed
Issue #1715 tracks the Tenstorrent bring-up of Qwen3.5-0.8B. With this fix the
default configuration generates coherent, factual text where it previously
emitted degenerate output, and the row's sacred near-tie correctness gate
passes 16/16 on ambient for the first time.
How can a reviewer verify it
Behavioral split, same build, same prompt (P150 board, flock'd):
!!!(1, 2, 3, 4, 5100°C. If a 100("Water boils at")VT_TT_HOST_FREE_DECODE=0Paris.Paris.(unchanged)Mutation proof (fresh reviewer, scratch clone): disabling only the
EnsureDevice2D refresh regresses ambient to the exact pre-fix garbage class;
restored byte-for-byte restores green.
Parity vs the pinned ROCm oracle greedy_ids (16 prompts x 16 tokens):
exact cells 135/256 -> 213/256; fully-exact prompts 5/16 -> 10/16; max
teacher-forced gap 0.375 nats (band 0.5); full gate 16/16 prompts PASS
(10 strict-exact, 6 near-tie), doctest 146/146 SUCCESS, 0 forward-divergent.
Link proof: default configure (
cmake -B build -G Ninja) now linksexamples/vllm-cli clean (503/503) where the pre-repair tree failed with
undefined
vt::tenstorrent::TrustDump/DebugDeviceReadbackF32.Fresh review: static review + board mutation + scoped re-review of the repair
both returned PASS with findings NONE after remediation.
What remains unverified or out of scope
(ttnn deallocate / GraphTracker device-destruction order). Verdict
unaffected; recorded as owed debt in the spec, fix to follow separately.
this change does not claim them.
scripts/agent-preflight.shon this host skipstest_ltx25_render_compare(python3-numpy unavailable to system python); CI installs numpy and runs it.
arbitrate the examples-on/default-backend link lane this repair addresses.
Refs #1715
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:x-preview-f-free [opencode]