feat(tenstorrent): host-free decode forward — capture investigation + architecture - #694
Open
lu-zero wants to merge 21 commits into
Open
feat(tenstorrent): host-free decode forward — capture investigation + architecture#694lu-zero wants to merge 21 commits into
lu-zero wants to merge 21 commits into
Conversation
lu-zero
force-pushed
the
row/BACKEND-TENSTORRENT-HOST-FREE-R1
branch
from
August 14, 2026 10:07
18c7df4 to
eef9dcd
Compare
lu-zero
added a commit
to lu-zero/vllm.cpp
that referenced
this pull request
Aug 14, 2026
Post-fold record repair before review: - BACKEND-TENSTORRENT-HOST-FREE-FORWARD matrix row: Tests/evidence cell now carries the measured R1-R3b outcome (was the stale "plan spec only; no code yet"); state SPIKE -> ACTIVE (R1-R3b landed, item-5 port open). - The plan spec (tenstorrent-host-free-forward.md) now meets the ACTIVE spike-gate contract (Scope / Upstream chain / Our baseline / Port map / Tests to port / Dependencies / Work breakdown) with the honest content: the port map is the tt-metal vLLM plugin technique table. - The TRACE-RUNNER claim tail records the measured outcome + the mudler#694 supersession (PR mudler#541 closing in its favor). - Dropped tests/parity/goldens/qwen3_greedy_0_6b/our_ids_tenstorrent.i32 — the raw VT_DUMP_IDS bootstrap artifact (untracked-noise class per the handoff; the .npy pair is already on main). It was swept into the fold unintentionally. - Post-rebase verification: default TT suite on the rebased tree on real P150 is 22/22 cases / 826/826 assertions — the env-gated host-free paths are inert by default. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
lu-zero
marked this pull request as ready for review
August 14, 2026 10:52
Contributor
Author
|
Steady-state perf baselines (2026-08-14, real P150, 64-token runs) — corrects two smoke numbers:
|
lu-zero
force-pushed
the
row/BACKEND-TENSTORRENT-HOST-FREE-R1
branch
from
August 14, 2026 18:17
0e3db6a to
4f0a6f9
Compare
lu-zero
added a commit
to lu-zero/vllm.cpp
that referenced
this pull request
Aug 14, 2026
Post-fold record repair before review: - BACKEND-TENSTORRENT-HOST-FREE-FORWARD matrix row: Tests/evidence cell now carries the measured R1-R3b outcome (was the stale "plan spec only; no code yet"); state SPIKE -> ACTIVE (R1-R3b landed, item-5 port open). - The plan spec (tenstorrent-host-free-forward.md) now meets the ACTIVE spike-gate contract (Scope / Upstream chain / Our baseline / Port map / Tests to port / Dependencies / Work breakdown) with the honest content: the port map is the tt-metal vLLM plugin technique table. - The TRACE-RUNNER claim tail records the measured outcome + the mudler#694 supersession (PR mudler#541 closing in its favor). - Dropped tests/parity/goldens/qwen3_greedy_0_6b/our_ids_tenstorrent.i32 — the raw VT_DUMP_IDS bootstrap artifact (untracked-noise class per the handoff; the .npy pair is already on main). It was swept into the fold unintentionally. - Post-rebase verification: default TT suite on the rebased tree on real P150 is 22/22 cases / 826/826 assertions — the env-gated host-free paths are inert by default. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
BACKEND-TENSTORRENT-TRACE-RUNNER. Replaces the prior "no-go" guess with a measured result from actually attempting the capture on-card. The spike ran the real experiment: local-only flips of both support_static_graph_mode() (enables Qwen3DenseDecodeGraph) AND VT_TT_FORCE_DEVICE (RoPE+residual all-device), then Qwen3-0.6B cli smoke. Both overrides reverted; no code shipped. Result: capture ABORTS. ttnn raises TT_FATAL: Reads are not supported during trace capture with a backtrace through ttnn::Tensor::to_vector<float> -- a device->host readback fires inside the captured ForwardLayers region. 0 replays. The T=1 forward does to_vector readbacks (embedding/PA/logits) that ttnn trace prohibits; forcing the two hybrid thresholds all-device is insufficient. This is strictly stronger than the earlier tok/s-recovery guess: decode capture cannot run AT ALL on the current forward without eliminating every to_vector readback in the captured region (a host-free ForwardLayers redesign, not a threshold tweak). Q1 (no host-free region at T=1) and Q2 (all-device costs 12.5->10.7 tok/s, reproducing handoff §6) stand. Decision: NO-GO for pure T=1 decode, measured. Next: prefill capture (separate row) must first audit its to_vector readbacks. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
BACKEND-TENSTORRENT-HOST-FREE-FORWARD (child of BACKEND-TENSTORRENT). The
trace-runner spike measured that decode mesh-trace capture ABORTS
(TT_FATAL: Reads are not supported during trace capture, backtrace through
to_vector<float>) because the per-layer T=1 forward does device->host
readbacks that ttnn trace prohibits. Capture wiring works (it engaged and
tried); the forward is the blocker.
This commit is a PLAN only (no code). It decomposes the host-free forward
into four independent rows, grounded in a full readback map of the current
TT ops:
R1 RmsNorm + RoPE all-device at T=1 (threshold flip; numerics already
proven by BACKEND-TENSTORRENT-RESIDUAL-GOLDEN; eager cost 12.5->10.7
tok/s measured, recovered by capture).
R2 QkvSplit + ReshapeAndCache device-resident (bit-exact memcpy/stride
ops; reuse the landed paged-KV-shadow path).
R3 PagedAttention decode with device-resident metadata (the scope risk --
device SDPA decode exists, but block_table/seq_lens/query_start_loc
are read host-side around it).
R4 flip support_static_graph_mode + wire capture + measure replay tok/s
(integration; the payoff that justifies R1-R3: replay warm tok/s
>= 12.5).
R1-R3 parallel-claimable; R4 integrates. The R1 eager regression (12.5->
10.7) is acceptable ONLY because R4 recovers it; R1 must gate on
support_static_graph_mode() so non-capture runs keep the 12.5 baseline.
The trace-runner row's "next" now points here instead of to a vague
prefill-capture note: the host-free forward is the real unblock.
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Maki:glm-5.2 [Maki]
…ture answer The experimental chain that un-no-go's the trace-runner decision. Measured on real Blackhole P150, env-gated (VT_TT_HOST_FREE_DECODE) and inert by default (21/21 TT tests, 814/814 assertions unchanged). Sequential bisection of every blocker between "capture aborts" and "capture runs into the layer ops." Complete blocker map: R1 RmsNorm/RoPE host thresholds at T=1 -> flip (done) R2 Backend::Copy host readback -> device->device copy (done) R3 ttnn program-cache warm-up -> enable + eager-warm (done) R3b Backend::Memset/DBuf::Zero host write-> device zero-fill (done) 5 per-op device write/upload -> ARCHITECTURE ANSWER below R1-R3b are landed and measured: capture now enters the forward and reaches the layer ops (CastBf16/RmsNorm fire during capture). The bisection instrumentation (TT_OP_TRACE macro + EnsureHostBytes/DownloadToHost prints + a tt_capture_active() file-scope flag) is kept, all gated on VT_TT_TRACE_DEBUG, inert by default. Item 5 (the remaining "Writes are not supported during trace capture" fatal) was investigated upstream. tt-metal issue #13690 (filed by Tenstorrent for vLLM, fixed in f0b2483) relaxes the ALLOCATOR guard during a live trace -- but our fatal is the separate ENQUEUE_WRITE guard (fd_mesh_command_queue.cpp:760), which #13690 did NOT touch. So bumping won't help. The real answer is architectural, from reading the official tt-metal vLLM plugin (tt/vllm/plugins/vllm-tt-plugin/.../model_runner.py): the captured ForwardLayers region must reference only pre-allocated, pre-populated persistent device tensors, with per-step inputs written to stable device buffers BEFORE ReplayGraph (never inside capture). The plugin uses ttnn.copy_host_to_device_tensor (= C++ copy_to_device -> enqueue_write_tensor) only at warmup-populate and before each replay, never during capture -- because copy_to_device itself hits the same enqueue_write fatal. This is the same pattern CUDA's decode graph uses (SizeSlot::Refresh writes host buffers that a captured async-copy re-reads). Path forward (bounded port, not a new subsystem, not an upstream fix): make the TT decode-graph slot hold persistent device tensors for inputs, populate them before capture/replay via copy_to_device, and ensure captured ops read those device tensors without any internal from_vector/to_vector. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
Post-fold record repair before review: - BACKEND-TENSTORRENT-HOST-FREE-FORWARD matrix row: Tests/evidence cell now carries the measured R1-R3b outcome (was the stale "plan spec only; no code yet"); state SPIKE -> ACTIVE (R1-R3b landed, item-5 port open). - The plan spec (tenstorrent-host-free-forward.md) now meets the ACTIVE spike-gate contract (Scope / Upstream chain / Our baseline / Port map / Tests to port / Dependencies / Work breakdown) with the honest content: the port map is the tt-metal vLLM plugin technique table. - The TRACE-RUNNER claim tail records the measured outcome + the mudler#694 supersession (PR mudler#541 closing in its favor). - Dropped tests/parity/goldens/qwen3_greedy_0_6b/our_ids_tenstorrent.i32 — the raw VT_DUMP_IDS bootstrap artifact (untracked-noise class per the handoff; the .npy pair is already on main). It was swept into the fold unintentionally. - Post-rebase verification: default TT suite on the rebased tree on real P150 is 22/22 cases / 826/826 assertions — the env-gated host-free paths are inert by default. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
Fresh static + mutation review (POL-REVIEW-FRESH) returned CHANGES-REQUESTED with two merge-blockers and a set of non-blocking findings. All addressed: BLOCKER 1 (records contradiction): the live coordination claim said SPIKE/"no implementation yet" while the same branch promoted the matrix row to ACTIVE. Claim row now ACTIVE with the landed R1-R3b state + the open item-5 port. The spec's Work breakdown also contradicted the landed R-numbering (it still defined R2=QkvSplit/RAC, R3=PA metadata from the original plan); restructured to the post-investigation truth: landed R1-R3b, open item 5 (the payoff port), queued-behind-item-5 for the original items, plus a "known constraints" section carrying the env-gated-only issues (ignored bytes, stale dev_rows/dev_cols, bytes-only dtype check, enable_program_cache placement, non-exception-safe capture-flag clear, SlotMutex TOCTOU, mid-capture shadow drop) into the item-5 spec. BLOCKER 2 (M1/M4 mutations stayed green — inertness unguarded): added a dedicated default-path inertness guard test. Two same-shaped outputs each carry a CURRENT device shadow (via device Matmul), so the env/capture gate is the only thing that can make CopyDeviceDeviceIfCapture / MemsetDeviceIfCapture decline; CHECK_FALSE on both (plus the value!=0 decline), and a host-path correctness anchor. MUTATION-PROVED on card: removing the gate (M1) turns the suite 22/23 RED; reverting restores 23/23, 830/830. NITs: device.h comment now matches the code (ttnn::zeros, not ttnn::full); SharedMeshDevice() moved after the no-shadow bail; matrix tests cell 23/23 with the guard noted. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
…ll-device 64-token runs on real P150 correct two earlier smoke numbers: - The 4-token smoke (12.5 tok/s) is not steady state: at 64 tokens the same default config sustains 7.3 tok/s. Per-token cost grows with context, so the handoff's ~12.3 over-reports too. 7.3 is the honest capture reference. - The all-device eager cost at steady state is ~0.4 tok/s (~6%), not the ~1.8 tok/s (~14%) the 4-token smoke suggested — capture's break-even bar is lower than the spike framing assumed. (All-device runs need VLLM_CPP_QWEN3_DENSE_DECODE_GRAPH=0, else the flag's support_static_graph_mode flip makes the framework attempt capture and abort on item 5.) Capture/replay tok/s — the actual payoff — remains unmeasurable until item 5 lands; the numbers above bound it (must beat 7.3, eager floor 6.9). Mistral-7B cross-ref: 4.26 tok/s warm (tenstorrent-mistral.md). FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
…isection Two item-5 fixes landed, both env-gated and inert by default (23/23 TT tests, 830/830): 1. ttnn::zeros/full is NOT capture-safe (creation.cpp full_impl host-fills and to_device()s = enqueue_write). Replaced the R3b zero-fill with a persistent zero-tensor cache: entries created OUTSIDE capture, primed during the eager warmup by EnsureDevice2D (flag-gated), applied in-region via ttnn::copy(zero, shadow) — a capturable device->device program. Cache-miss during capture is a hard VT_CHECK. 2. Verified QkvSplit's existing device path (ttnn::slice) fires under MatmulBT's shadow — earlier suspicion wrong. Instrumented all 16 from_vector sites with capture-gated [TT-UP] prints (UploadRows incl. ptr+shape). Measured frontier: capture now runs deep into layer 0 (CastBf16 -> RmsNorm -> MatmulBT -> QkvSplit -> qk-norms) and dies at a precisely-identified site — the RoPE cos/sin table upload ([16,64], BuildCosSinFromPositions host-build inside RopeNeoxKernel). That is the plugin per-step-input case: persistent device cos/sin buffer populated before capture/replay. Remaining sites after rope: RAC KV writes, PA metadata, logits path — same pattern, rope is the template. Item 5 is now a scoped multi-site port, not an unknown; replay tok/s stays blocked behind the remaining sites. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
…n card) Three fixes working together: 1. Persistent cos/sin device cache keyed by (T*H, rot/2); entries created or content-refreshed OUTSIDE capture; replayed in-region. Stale content during capture is a hard VT_CHECK (never silent corruption). 2. Driver warm hook WarmRopeCosSin called from the decode-graph Refresh slot (qwen3.cpp), warming the UNPADDED T-row positions (what rope sees), not ppos — the first attempt used ppos and always missed. 3. Byte-exact content: the default rope route (RopeFromCache) reads the per-step bf16 CACHE table, so warm content must round-trip f32->bf16 (0.540302 f32 vs 0.539062 bf16 — f32 warm never matched). Measured on P150: rope cache HIT during capture for both q (16x64) and k (8x64); capture proceeds PAST rope. Also documented: the dense decode path routes rope through RopeFromCacheKernel by default (not RopeNeox), which the mis-placed debug print exposed. New frontier: ReshapeAndCache — next fatal is a to_vector readback inside RAC right after rope. Then PA metadata, then logits. RAC carries a replay-semantics question (replayed KV appends against fixed slots — the CUDA-graph solution) alongside the mechanical fix. Default-path safety: 23/23 cases, 830/830 assertions after all changes. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
The agent-record CI job flags production env vars read from src/ that are undocumented. Both Tenstorrent host-free investigation vars are now in docs/ENVIRONMENT.md: the capture-mode gate under Deployment knobs (with the incomplete/byte-identical caveat), the bisection traces under Diagnostic. check-env-doc: OK, 339 vars. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
MUSIC3 W6 (aa3643b) placed the anon-namespace SpeechRegistry() helper — returning vllm::multimodal::SpeechRegistry& — INSIDE the extern "C" block (vllm_c.cpp:488-1955). Clang rejects a C-linkage function returning a C++ reference (-Werror,-Wreturn-type-c-linkage); every clang build, including the Tenstorrent one, is broken on that commit (MSVC/Windows compiled, which is why CI's windows jobs went green). Minimal fix: hoist the helper (with its anon namespace) to just before the extern "C" block opens. No behavior change — the function is C++ and always was; the linkage was accidental. Also on this commit: the rebase record repairs (BACKEND=84 with the landed MISTRAL row + HOST-FREE-FORWARD; dup-row cleanups) and the post-rebase benchmark confirmation in the host-free spec (7.13/7.23 default, 6.68/6.80 all-device — consistent with pre-rebase). FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
…apped FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
…ed cache TryReshapeAndCacheDeviceDecode + WarmRacIdx driver warm hook implemented. Shape-keyed idx cache (same content-refresh pattern as rope). Warm fires correctly; content matches (slot0=32 warmup, slot0=33 capture, idx matches). The device branch bails because the paged-KV device shadow was never created (k=0 v=0) — EnsurePagedKvTtnn runs inside PA's device path, which doesn't fire during eager warmup. Fix: eagerly prime the shadow from WarmRacIdx. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
…xecutes WarmPagedKvShadow primes both k and v shadows (fixed KvSlice offset bug). TryReshapeAndCacheDeviceDecode EXECUTES during capture (verified: "RAC device->device update (capture-safe)" printed on card). The paged_update_cache call hits "Writes not supported" because it's not program-cache-warm — the eager forward's RAC bailed to host (shadow didn't exist at eager time). Warm attempt from WarmRacIdx hangs (paged_update_cache deadlocks from the warm-hook context, likely a device-state issue). NEXT: move shadow priming BEFORE the eager forward so the eager RAC takes the device branch and warms paged_update_cache naturally (same CQ context). The framework's flow: eager step -> Refresh -> capture step; priming must happen before the eager step, not at Refresh. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
…not capture-safe RAC device branch now executes on cold + capture steps. Fixed en route: - shadow priming for ALL layers (was only layer 0; each attn_kv[l] has its own .data pointer) - used=block+1 (off-by-one: shadow must cover block+1 blocks) - idx tensor dtype INT32 (ttnn requires INT32, not UINT32) - input sharding (paged_update_cache requires height-sharded input) - embedding ids reverted to UINT32 (global replace collateral damage) But paged_update_cache itself triggers "Writes are not supported during trace capture" — the op allocates a new output tensor internally, which is an enqueue_write forbidden during capture. This is NOT a program-cache issue (the cold step compiled the program). It is a ttnn API limitation: paged_update_cache returns a new tensor rather than updating in-place. Options: (a) upstream ttnn change for in-place update; (b) pre-allocate result + different scatter op; (c) skip RAC from capture (but PA reads the KV cache inside the captured region, so it can't move out). FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
…padded Key discovery: paged_update_cache::create_output_tensors returns cache_tensor (in-place, no allocation). The Writes fatal was from build_padded's ttnn::to_memory_config (sharding allocates) + other helper ops, NOT from paged_update_cache itself. Attempted pre-built sharded zero in warm + ttnn::copy in build_padded. Crashed (segfault 139) on the cold step — shape/lifetime mismatch on the pre-built sharded tensor. Root cause clear; fix is to make build_padded use only capture-safe ops against pre-warmed persistent tensors, or take approach (b) — replace paged_update_cache with manual ttnn::copy into a pre-sliced cache region. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
… idx) The segfault was caused by WarmRacIdx running BEFORE WarmPagedKvShadow in the driver Refresh slot, so the shadow loop found 0 entries and the sharded_zero was default-constructed (empty) — ttnn::copy then crashed on the empty tensor's memory_config(). Fixed by swapping the order: prime the shadows first, then build the RAC idx tensors (which use the shadows' geometry for the sharded input). The Writes fatal persists: paged_update_cache is in-place (no allocation), but build_padded's ttnn::copy between TILE and height-sharded memory configs triggers an implicit layout conversion (a write). NEXT: approach (b) — manual ttnn::copy into a pre-sliced cache region, no sharding. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
Skip RAC during capture (mark shadow stale, return true). Capture proceeds PAST RAC to TryPagedAttentionDeviceDecode, which hits Reads-not-supported (the read guard at :807, not the write guard at :760). PA is doing a to_vector readback (likely EnsurePagedKvTtnn re-uploading the stale shadow or EnsureHost on query metadata). The real RAC fix: move it OUT of the captured ForwardLayers region — do the KV write at the driver Refresh slot (before BeginCapture), same as the plugin's per-step pattern. The captured region starts after RAC, not at ForwardLayers. Remaining sites: PA metadata, PA shadow re-upload, PA output, logits. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
…uploads During capture, skip EnsurePagedKvTtnn and use the cached shadow (primed by WarmPagedKvShadow). Capture passes PA KV shadow check, reaches the page_table + cur_pos from_vector uploads (the same persistent-buffer pattern as rope cos/sin). FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
lu-zero
force-pushed
the
row/BACKEND-TENSTORRENT-HOST-FREE-R1
branch
from
August 14, 2026 22:07
413b274 to
b90cb15
Compare
… bail) PA metadata (page_table + cur_pos) persistent device tensors warmed at the Refresh slot. Capture uses cached meta: "PA using cached meta (pt+cp)". But sdpa_decode hits "Cannot load new binaries" — the cold step's PA device path bails before sdpa (q_from_device OK cap=0 never prints). Debugging the bail point next. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
lu-zero
force-pushed
the
row/BACKEND-TENSTORRENT-HOST-FREE-R1
branch
from
August 14, 2026 22:07
b90cb15 to
812ac3e
Compare
The cold step's PA device path was bailing because KvSlice returns a non-contiguous strided view that EnsurePagedKvTtnn's VT_CHECK rejects. Fixed by using the cached shadow (from WarmPagedKvShadow) on BOTH cold and capture steps. The cold step now runs sdpa_decode on all 28 layers (program compiled); the capture step hits the program cache. Measured on real Blackhole P150 (Qwen3-0.6B, 4-token smoke): run 1 (cold+capture): 18.5s, 0.22 tok/s run 2 (warm replay): 0.046s, 86.5 tok/s run 3 (warm replay): 0.051s, 77.9 tok/s ~12x speedup over the 7.3 tok/s eager baseline. EXIT=0. Default-path safety: 23/23 cases, 831/831 assertions. Caveats: RAC is skipped during capture (stale KV); the real fix is moving RAC out of the captured region. The 4-token smoke over-reports (64-token steady-state measurement pending). FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [Maki]
RAC now runs INSIDE the captured region. paged_update_cache is in-place (verified) and capture-safe. The sharded input uses the pre-built sharded_zero. Capture completes: EXIT=0, 83/77 tok/s replay. The k/v copy into the sharded buffer fails during capture (ttnn::zeros + ttnn::concat are writes, caught by try/catch, falls back to zeros = stale KV). Fix: pre-build the padded k/v at warm time using only capture-safe ops (ttnn::copy from device shadow into the pre-allocated sharded buffer). FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Maki:glm-5.2 [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.
What
The experimental chain that un-no-go's the trace-runner decision. Measured on real Blackhole P150, env-gated (
VT_TT_HOST_FREE_DECODE) and inert by default — now test-proven, not just inspected. No shipped behavior change unless the env flag is set.The complete blocker map (each measured by sequential bisection)
Backend::Copyhost readbackttnn::empty+ttnn::copy)enable_program_cache+ eager-warm)Backend::Memset/DBuf::Zerohost writettnn::zeros)R1–R3b are landed and measured: capture now enters the forward and reaches the layer ops (CastBf16/RmsNorm fire during capture).
Item 5 — the architecture answer
f0b2483) relaxes the allocator guard. But our fatal is the separate enqueue_write guard (fd_mesh_command_queue.cpp:760), which #13690 did not touch. Verifiedf0b2483IS in our build — bumping won't help.model_runner.py): the capturedForwardLayersregion must reference only pre-allocated, pre-populated persistent device tensors, with per-step inputs written to stable device buffers beforeReplayGraph(never inside capture —copy_to_deviceitself hits the sameenqueue_writefatal). This mirrors CUDA's decode-graph pattern (SizeSlot::Refresh).Path forward (bounded port, not a new subsystem, not an upstream fix): persistent device input tensors in the decode-graph slot + populate before capture/replay via
copy_to_device. The plugin-technique → vllm.cpp-TT mapping table is in the spec's Port map; the env-gated-only constraints of the investigation code are carried in the spec's "Known constraints" section.Review
Fresh static + mutation review returned CHANGES-REQUESTED; both blockers fixed in
37696af0:CHECK_FALSEon both helpers + thevalue!=0decline + a host-path correctness anchor. Mutation-proved on card: M1 (gate removed) → 22/23 RED; revert → 23/23, 830/830.Non-blocking findings (ignored
bytes, staledev_rows/dev_cols, bytes-only dtype check,enable_program_cacheplacement, non-exception-safe flag clear, SlotMutex TOCTOU, mid-capture shadow drop) are recorded in the spec as "Known constraints" for the item-5 port to address.Gates
Files
.agents/specs/tenstorrent-host-free-forward.md(spike-gate contract: Scope/Upstream/Our baseline/Port map/Tests to port/Dependencies/Work breakdown + Known constraints).agents/specs/tenstorrent-host-free-r1.md(the measured investigation record).agents/specs/tenstorrent-trace-runner.md(the closed spec(tenstorrent): trace-runner feasibility spike — NO-GO for T=1 decode #541's record)src/vllm/platforms/tenstorrent.cpp,src/vt/tenstorrent/{ops,device.h,backend}.cpp— all env-gated, inert by defaulttests/vt/test_tenstorrent_backend.cpp— the inertness guard test