TriAttention Native C++ Worklog
Date: 2026-04-16
Commands, temporary artifact paths, benchmark numbers, and hypotheses on this page are evidence from the dated TriAttention bring-up. They are not current setup instructions and are not automatically revalidated on later revisions. Use current Qwen descriptors, tests, and CLI help for the live contract.
Purpose
This worklog records how TriAttention was brought from an upstream research runtime into the native C++ TensorRT runtime in this repo, what design choices were made along the way, which debugging hypotheses were wrong, and what finally produced an end-to-end result with both accuracy and throughput benefit.
This document replaces the older point-in-time superpowers spec notes for this feature. Those snapshots were useful during implementation, but the website now keeps the durable feature narrative here.
Final outcome
The final native implementation is a real C++/CUDA runtime path, not a Python debug shortcut:
- bundle build embeds TriAttention metadata and stats
- runtime compaction is handled by
TriAttentionKvCache - selection and repack run natively in C++/CUDA
- the same TRT decoder engine continues after compaction
- runtime KV allocation can be set at launch time with
--kv-cache-size
On the corrected Qwen3-8B AIME25 pilot slice used for parity checks, native TriAttention now matches dense TRT on answer accuracy while remaining faster.
Matched 6-sample long-budget slice:
- dense answers:
tmp/qwen3_dense_completed6.jsonl - TriAttention answers:
tmp/qwen3_tri_v14_completed6_long.jsonltmp/qwen3_tri_v14_sample5_completed6_long_gpu2.jsonltmp/qwen3_tri_v14_sample6_completed6_long_gpu3.jsonl
Answer parity on that slice:
aime25_1 -> 70aime25_2 -> 588aime25_3 -> 16aime25_4 -> 117aime25_5 -> 279aime25_6 -> 504
Throughput on that same slice:
- dense:
64.16 tok/s - TriAttention:
86.12 tok/s
That is about 1.34x faster while preserving the dense answers on the matched
pilot.
Current reproducible PR-tip validation:
- TriAttention bundle:
artifacts/triattention/qwen3-8b-nonflash/qwen3-8b-tri12288-b3072-r128-dynkv-fp16-manual-current.bundle - dense control bundle:
artifacts/triattention/qwen3-8b-nonflash/qwen3-8b-dense12288-dynkv-fp16-manual-samefam.bundle - runtime overrides:
TRTMC_TRIATTN_OVERRIDE_KV_BUDGET=6144TRTMC_TRIATTN_OVERRIDE_DIVIDE_LENGTH=1024TRTMC_TRIATTN_RUNTIME_BUCKET_ROWS=32 - validation outputs:
artifacts/triattention/policy-sweeps/2026-04-17-current-bundle-override-validation/
Checked results from that current-tree validation:
aime25_2: TriAttention answer588, dense answer588aime25_3: TriAttention answer16aime25_2no-stop throughput: TriAttention56.658344 tok/sdense same-family control42.614745 tok/s
That is 1.3295x on the fair same-family dense baseline while preserving the
checked hard answers.
High-level design choices
1. Treat TriAttention as a runtime/cache policy, not an attention-kernel swap
The first important conclusion from upstream was that TriAttention is not "replace TensorRT attention with a new attention op." It is a cache scoring, selection, and compaction policy with runtime metadata and lifecycle handling.
That drove the integration point:
- builder persists TriAttention config and calibration stats into the bundle
- runtime owns cache state, position tracking, selection, and compaction
- the TRT engine stays the decoder, but the cache semantics change underneath it
This was the right choice. It kept the project feasible and matched how the upstream runtime is actually structured.
2. Move to native C++ instead of a Python evaluation shim
The first repo-local enablement path used a Python debug runner to validate the idea. That was useful for initial understanding, but the user explicitly did not want a Python shortcut. The implementation was therefore moved fully into the native runtime.
This forced the real project decisions:
- native state object under
IInferenceState - native bundle config parsing
- native selector and compaction logic
- native tests and native benchmarks
3. Prefer a single dynamic-KV engine over multi-engine switching
There were two plausible ways to reduce real attention work:
- bucketed engine switching
- a single engine with dynamic KV row count
After reviewing TensorRT local docs and the repo's existing dynamic-shape use, the single-engine path was chosen. That proved sufficient as long as the decode graph was built with dynamic KV input shapes and the runtime rebound the live row count.
Why this choice was correct:
- less engine-management complexity
- no explicit engine migration cost
- cleaner runtime model
- consistent with TensorRT's intended dynamic-shape usage
4. Separate physical cache limit from logical TriAttention budget
One important quality issue was prompt-time overflow. Using a tiny physical window and a tiny logical budget caused compaction to happen during prompt prefill, which damaged the prompt-state before decode started.
The fix was architectural:
- physical limit large enough to hold the prompt cleanly
- logical TriAttention budget smaller and enforced during decode
This is why configurations like tri256-b128 were materially better than
tri192-b128 on overflow prompts.
5. Make runtime KV allocation configurable in bytes
Dynamic shapes remove the fixed exact KV length at runtime, but not the build upper bound. The runtime therefore needed a user-facing way to choose the actual allocation below that upper bound.
The implemented runtime model is:
- build with a large dynamic upper bound
- allocate only the requested runtime amount
- expose that through
--kv-cache-size
That became a must-have feature because otherwise large build-time bounds would force wasteful memory allocation at startup.
6. Keep GPU work on GPU
The first native implementation was functionally correct enough to prove the idea, but it was slow because it copied cache rows to host, converted them on CPU, scored on CPU, and repacked with many tiny D2D row copies.
The final runtime moved the hot pieces to GPU:
- GPU candidate scoring
- GPU gather-style compaction
- host only for small normalization/combine work where still acceptable
This was essential to recovering throughput.
What was implemented
The final feature spans four layers.
Bundle/build layer
- TriAttention stats export into bundle JSON sections
- TriAttention runtime config persisted in bundle config
- dynamic-KV-capable decoder engine build
Runtime state layer
TriAttentionKvCacheowns:- cache tensors
- per-head position tracking
- compaction trigger logic
- reserved/recent/prefill protection
- keep-index selection
- compaction and cache rebound
CUDA helper layer
- GPU scoring kernel
- GPU KV gather/repack kernel
CLI/runtime config layer
- runtime KV byte budget
- dynamic external KV binding without eager allocation of the full engine max
Debugging chronology
Phase 1: understand upstream correctly
The first upstream reading established two things:
- TriAttention is a runtime policy, not a TRT graph replacement.
- Upstream's best performance story is tied to long reasoning workloads, GPU scoring, and serving-runtime cooperation.
This prevented an early wrong turn: trying to "just add a new attention layer" in TRT.
Phase 2: native C++ port with a shared-row approximation
The first native version worked as a C++ cache compaction path and proved that:
- compaction fired in native code
- the runtime could continue generation after compaction
- retained-memory prompts behaved better than undersized dense baselines
But this version still used a simplified shared-row interpretation compared to the stronger upstream per-head behavior.
Phase 3: sanity checking with retrieval-style prompts
The first correctness checks used marker/retrieval prompts. These were useful because they had deterministic expected answers, but they were not persuasive as general quality evidence.
That led to a better evaluation discipline:
- use retrieval-style prompts only for deterministic cache sanity checks
- present normal conversational prompts when discussing visible output quality
Phase 4: performance regression investigation
The first native C++ compaction path was dramatically slower than dense once compaction engaged.
The key finding from profiling was simple:
- pre-compaction decode was already fine
- nearly all slowdown came from the compaction event itself
The cost breakdown pointed at:
- host copy + dtype conversion
- CPU scoring
- thousands of tiny D2D repack copies
That led directly to the GPU scoring and GPU gather/repack implementation.
Phase 5: dynamic-KV path and runtime KV sizing
Reducing cache residency alone is not enough if the engine still binds and processes the full physical window. The runtime was therefore extended to bind a live KV row count into a single dynamic-shape engine.
This was paired with runtime byte-budget control so the actual allocation could be chosen at launch time instead of being fully materialized at bundle max.
Phase 6: overflow bug during prompt prefill
One of the harder early failures was long-context overflow on normal prompts. The issue was not initially obvious because the selector appeared reasonable.
The actual problem was compaction during prefill in too-small a physical cache.
Fix:
- use physical slack during prompt fill
- decouple physical capacity from logical keep budget
- ensure compaction starts after decode crosses the physical threshold, not while prompt state is still being built
Phase 7: evaluation recipe was wrong
A separate accuracy confusion came from the benchmark recipe itself. Early AIME results looked poor for dense, TriAttention, and HF because the decode recipe was throughput-oriented:
- greedy or deterministic decode
- poor stopping behavior
- simplistic extraction
- non-chat prompting in some earlier runs
After switching to the corrected Qwen reasoning recipe, dense TRT recovered to HF parity on the pilot slice. This mattered because it separated "bad eval recipe" from "TriAttention bug."
Phase 8: remaining TriAttention accuracy gap after compaction
At that point dense and HF were aligned, but TriAttention still failed hard reasoning samples once real compression activated.
Several debugging hypotheses were tested.
Hypothesis: sampled-head mapping was being handled incorrectly
This was partly true.
The runtime had to preserve actual sampled_heads mappings instead of assuming
uniform contiguous grouping. Fixing this materially improved results and made a
previously bad AIME sample return the correct answer.
But it was not sufficient by itself.
Hypothesis: the benchmark stats file was sparse sampled-head-only
This turned out to be wrong for the actual Qwen3-8B AIME25 calibration file.
Inspection showed that the file effectively carried full per-attention-head
stats for all 36 * 32 = 1152 attention heads.
That changed the interpretation of the runtime semantics:
- the selector should behave like grouped per-head reduction over dense attention-head stats
- sparse sampled-head fallback logic alone was not the real upstream match
Hypothesis: the remaining gap was purely in per-head compaction layout
This was plausible, but still not the first blocker.
The decisive remaining mismatch was the scorer formula.
Phase 9: final root cause - native scorer used the wrong formulation
Upstream's current vLLM/Triton runtime does not score by unrotating keys using
cached token positions. It scores directly on stored K_rot, where key
position is already baked into the rotated key, and only the query-side phase
term remains.
Our native runtime was still using the older formulation:
- recover key position
- unrotate key
- reapply phase with cached positions
That was the wrong match for the upstream runtime being emulated.
The fix was to change both the host selector and the CUDA kernel to the direct
K_rot formulation:
- use stored rotated key components directly
- compute
Q_mean * conj(K_rot)directly - apply only the query-side trig term
- keep the additive MLR term on
|K_rot|
After that fix, the previously bad hard samples flipped to the correct answers.
Why the final fix worked
The direct-K_rot scorer mattered because it matched the runtime semantics of
the upstream implementation we were actually trying to reproduce.
It also removed two fragility points:
- dependence on exact cached position bookkeeping for scoring correctness
- extra numeric reconstruction work not present in the upstream runtime path
Once the native scorer matched upstream, the remaining native compaction path was good enough to preserve quality on the corrected pilot.
Evidence that the feature now works
Native tests
ctest --test-dir build --output-on-failure -R test_triattention_kv_cache
Hard-sample recovery
Correct after scorer alignment:
tmp/qwen3_tri_v14_sample1_5000_gpu1.jsonltmp/qwen3_tri_v14_sample2_5000_gpu1.jsonl
Corrected 6-sample pilot at shorter budget
tmp/qwen3_tri_v14_completed6.jsonl
This run showed 6/6 correct with materially higher tok/s than dense.
Corrected 6-sample pilot at matched long budget
Artifacts:
- aggregate partial:
tmp/qwen3_tri_v14_completed6_long.jsonl - per-sample completions:
tmp/qwen3_tri_v14_sample3_completed6_long_gpu1.jsonltmp/qwen3_tri_v14_sample4_completed6_long_gpu1.jsonltmp/qwen3_tri_v14_sample5_completed6_long_gpu2.jsonltmp/qwen3_tri_v14_sample6_completed6_long_gpu3.jsonl
Dense comparison:
tmp/qwen3_dense_completed6.jsonl
Result:
- same extracted answers as dense on all 6 samples
- higher average throughput than dense
Important non-obvious lessons
1. Accuracy debugging needed a matched evaluation recipe first
Until dense and HF agreed, TriAttention debugging was underdetermined. Fixing the benchmark recipe first was mandatory.
2. Upstream drift matters
The upstream runtime semantics had moved to direct K_rot scoring. Matching an
older formulation would have kept the implementation "reasonable" but still
wrong.
3. Cache semantics and attention-work reduction are related but distinct
There were really two separate projects:
- make compaction correct
- make compaction useful for throughput
Both had to be solved independently.
4. Physical and logical cache sizes should not be conflated
That one design choice explained a large part of the early overflow and prompt quality failures.
Final apples-to-apples check
The final fair comparison used the upstream plain-prompt recipe and a true full-KV dense baseline:
- dense full-KV bundle:
artifacts/triattention/qwen3-8b-nonflash/qwen3-8b-dense32768-dynkv-fp16-manual-fullkv.bundle - TriAttention bundle:
artifacts/triattention/qwen3-8b-nonflash/qwen3-8b-tri12288-b3072-r128-dynkv-fp16-manual-current.bundle
Prompt recipe:
- plain upstream math prompt, no chat template
temperature=0.6top_k=20top_p=0.95min_p=0.0seed=1234
The raw probe inputs and outputs were generated under the ignored
artifacts/triattention/ tree. The checked-in record keeps only the summarized
numbers below so benchmark outputs do not become source files.
Dense full-KV results:
aime25_2no-stop: answer588,38.89729 tok/saime25_3stop-on-answer: answer16,53.068426 tok/s
TriAttention results:
aime25_2no-stop: answer588,61.263541 tok/saime25_3stop-on-answer: answer16,68.298592 tok/s
Derived result:
aime25_2no-stop speedup versus dense full-KV:1.5750x
Why this is still fair even though the TriAttention bundle is tri12288
For these probes, the physical max length difference is inactive.
The prompt lengths are:
aime25_2:690tokensaime25_3:164tokens
The runtime only begins compaction once
cache_length_ >= compaction_trigger_length(), and
compaction_trigger_length() is kv_budget + divide_length = 3200 for this
bundle configuration; see
src/runtime/models/<family>/triattention_kv_cache.cpp.
Because both prompts are far below 3200, the tested runs never depend on a
physical cache capacity larger than 12288. After compaction starts, the live
cache stays near the logical TriAttention budget, again far below 12288.
So on these probes a hypothetical tri32768-b3072 bundle would follow the same
runtime path, and the comparison against dense32768 is apples-to-apples for
the actual operating point being measured.
Remaining limits
The current state is strong enough for native feature support, but there are still follow-up opportunities:
- rerun a broader AIME25 sweep under the corrected recipe, not just the matched pilot slice
- revisit true KV-head-native cache layout for GQA models to eliminate the expanded query-head representation
- recover a more optimized long-window attention path where native TensorRT
IAttentionis unstable and the manual path is used instead - consolidate the split pilot artifacts into one aggregate output file for easier future regression checking
2026-04-17 Follow-up: full-benchmark regression and current root-cause status
After the earlier probe-level success, a full 30-sample AIME25 rerun exposed a real remaining problem:
- HF eager:
accuracy
0.700, wall15.96 tok/s - dense full-KV:
accuracy
0.667, wall38.08 tok/s - TriAttention:
accuracy
0.267, wall70.28 tok/s
Recorded summary:
tmp/qwen3_8b_aime25_fullkv_plain_summary.mdtmp/qwen3_8b_aime25_fullkv_plain_summary.json
That result was too large a quality loss to wave away as sampling noise, so the next debugging pass focused on whether the runtime selector/compactor was still wrong on the bad long-window cases.
Selector correctness is no longer the leading suspect
For aime25_23, native score-cache dumps were replayed through the local
upstream selector implementation in
artifacts/triattention/upstream/triattention/vllm/runtime/selector_hf.py.
For both compaction 1 and compaction 2:
- exact match on the selected rows for every sampled head
- mean Jaccard overlap
1.0
This ruled out the remaining "wrong keep-set" theory for that sample.
K-cache repack correctness is also no longer the leading suspect
For the same aime25_23 investigation, the post-compaction K cache was checked
against the pre-compaction cache by directly gathering the kept indices. The
result was exact:
max_abs_diff = 0.0- no bad layers
So the native path is not silently scrambling K rows after selection on that sample.
One old long-window artifact was proven invalid as an apples-to-apples control
The older artifact
artifacts/triattention/qwen3-8b-nonflash/qwen3-8b-tri32768-b3072-r128-dynkv-fp16-manual-apple.bundle
turned out not to be a valid same-family long-window baseline.
Even under conservative no-compaction settings and greedy decode, it diverged from the dense full-KV bundle before compaction and also ran much faster than dense in the pre-compaction region. That means it is not "the same engine path plus TriAttention policy"; it is a materially different engine/runtime combination. It must not be used for final parity claims.
This finding changed the next step of the investigation:
- stop trusting the stale
tri32768 ... manual-appleartifact for full-benchmark conclusions - rebuild a fresh same-family
dense32768control from the current code - rebuild the matching fresh
tri32768bundle from the current code - re-prove greedy no-compaction equivalence on the fresh pair before resuming full-benchmark tuning
At the time of writing this note, that fresh same-family rebuild is still in progress and the full-benchmark parity question remains open.
Same-bundle control proof: compaction is not the only remaining issue
After the stale-bundle baseline problem was identified, the next control was to
use the exact same tri32768 bundle in two modes:
TRTMC_TRIATTN_FORCE_ENABLE=0TRTMC_TRIATTN_FORCE_ENABLE=1
with compaction effectively disabled by runtime overrides:
TRTMC_TRIATTN_OVERRIDE_KV_BUDGET=16384TRTMC_TRIATTN_OVERRIDE_DIVIDE_LENGTH=2048TRTMC_TRIATTN_OVERRIDE_RECENT_WINDOW=256TRTMC_TRIATTN_RUNTIME_BUCKET_ROWS=32
On aime25_7, greedy 3000-token generation from the same bundle was
byte-identical between the forced-off and forced-on modes:
- outputs identical
- extracted answer identical
- only a small throughput difference from extra TriAttention state allocation
This was an important control:
- when compaction is disabled, the native runtime no longer shows a separate "TriAttention corrupts output before compaction" problem
- the old pre-compaction divergence against dense was indeed caused by using a non-matched bundle pair
But the same-bundle dense control still missed HF on long sampled decoding
The next focused test used that same tri32768 bundle with
TRTMC_TRIATTN_FORCE_ENABLE=0 on the full sampled stop-on-answer recipe for the
known bad sample aime25_2.
Result:
- same-bundle forced-off control extracted
5 - dense full-KV and HF reference both extracted
588
That ruled out another attractive but incorrect explanation:
- the remaining full-benchmark gap is not only "bad compaction keep-sets"
Because TRTMC_TRIATTN_FORCE_ENABLE=0 takes the runtime back to the normal
KvCache state in decoder_plugin.cpp, this miss must come from the engine
family/build side, not from the TriAttention runtime state object.
Dynamic-KV profile rows became the leading build-time suspect
The stale long-window bundles were then inspected directly. The dense full-KV
bundle and the tri32768 bundle did not even share the same dynamic-KV
optimization profiles:
- dense full-KV:
[32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768] tri32768:[3072, 6144, 12288, 24576, 32768]
Because the same-bundle force-off control already missed aime25_2, the
coarse TriAttention-specific profile schedule became the leading build-time
suspect for the remaining long-window drift. That led to the next experiment:
- add an explicit builder override for dynamic-KV profile rows
- rebuild a fresh
tri32768bundle with the dense-style full profile ladder - re-test sampled force-off control accuracy before returning to compaction tuning
Dense-engine hybrid bundle proved the profile-row issue was real
Instead of waiting for another full engine rebuild, a direct control bundle was
constructed by taking the exact dense full-KV engine plan and tokenizer assets
from
artifacts/triattention/qwen3-8b-nonflash/qwen3-8b-dense32768-dynkv-fp16-manual-fullkv.bundle
and grafting only the TriAttention metadata block plus
triattention_stats.json from the stale tri32768 artifact.
The resulting hybrid bundle was:
artifacts/triattention/qwen3-8b-nonflash/qwen3-8b-tri32768-b3072-r128-dynkv-fp16-manual-denseengine-hybrid.bundle
This gave a same-family long-window comparison point with:
- the dense engine plan
- the dense dynamic-KV optimization profile ladder
- the current native TriAttention runtime path
That hybrid bundle immediately recovered the long sampled control behavior that
the stale tri32768 bundle had lost.
On aime25_2, with TRTMC_TRIATTN_FORCE_ENABLE=0 and the full sampled
stop-on-answer recipe:
- hybrid force-off control extracted
588 - generated
8592tokens
So the dense-engine hybrid proved that the earlier long-window miss was not intrinsic to the native runtime. It was tied to the stale engine-family build.
Repaired-engine TriAttention then recovered the first hard cases
Using the same hybrid bundle with TriAttention enabled:
- default policy (
kv_budget=3072,divide_length=128) also extracted588onaime25_2 - conservative policy
(
TRTMC_TRIATTN_OVERRIDE_KV_BUDGET=6144,TRTMC_TRIATTN_OVERRIDE_DIVIDE_LENGTH=1024,TRTMC_TRIATTN_RUNTIME_BUCKET_ROWS=32) also extracted588
That meant the repaired engine family plus native TriAttention could preserve
the dense/HF answer on the previously bad sample aime25_2.
Conservative policy fixed the three focused long-reasoning regressions
The next focused validation used the repaired hybrid bundle on the previously
bad slice aime25_7, aime25_12, and aime25_23.
With the repaired hybrid bundle and the conservative runtime policy:
aime25_7 -> 821aime25_12 -> 510aime25_23 -> 610
Those are the same extracted answers as the dense/HF references for that focused slice.
This is the first point in the long-window investigation where all three of the known bad focus cases were simultaneously correct under the native runtime.
The exact conservative runtime recipe that achieved that state was:
TRTMC_TRIATTN_FORCE_ENABLE=1TRTMC_TRIATTN_OVERRIDE_KV_BUDGET=6144TRTMC_TRIATTN_OVERRIDE_DIVIDE_LENGTH=1024TRTMC_TRIATTN_RUNTIME_BUCKET_ROWS=32
That repaired-engine conservative recipe is now the right candidate for the next full 30-sample apples-to-apples benchmark:
- same hybrid bundle as dense control with
TRTMC_TRIATTN_FORCE_ENABLE=0 - same hybrid bundle as TriAttention run with the conservative policy above
- HF eager as the external reference
A benchmark harness bug was then found in the seed schedule
The first full hybrid-bundle benchmark replay later showed a new regression on
aime25_7, but that run exposed a benchmark bug rather than a clean model
regression.
HF reference generation in
python/tensorrt_model_connect/families/qwen/benchmark_qwen3_8b_aime25_vs_hf.py
already reseeded per sample:
torch.manual_seed(seed + row_idx)for the single-GPU case
But the TRT-side dataset benchmark binary was still reusing the exact same seed for every row in a multi-sample run.
That meant the earlier "full benchmark" comparison was not even using the same
sampling schedule across HF and TRT. The benchmark runner was corrected so that
the TRT side now also advances the configured base seed by sample_idx.
Seed fix changed the interpretation of the aime25_7 regression
After rebuilding trtmc_dataset_benchmark with the corrected per-row seed
schedule, the dense control was rechecked on aime25_7 as a standalone sample:
- seed
1234still produced821 - seed
1240also produced821
So the seed fix did not break the previous single-sample proof.
Then the crucial sequence-dependent focused replay was repeated with the rebuilt benchmark binary:
- dataset:
aime25_2, thenaime25_7 - dense control: same hybrid bundle with
TRTMC_TRIATTN_FORCE_ENABLE=0
Result:
aime25_2 -> 588aime25_7 -> 821
And that same focused replay stayed correct even with CUDA Graphs disabled:
TRTMC_DISABLE_CUDA_GRAPH=1- still
aime25_2 -> 588 - still
aime25_7 -> 821
This invalidated the earlier "CUDA Graph reuse is the remaining root cause" hypothesis. Once the benchmark seed schedule was corrected, the focused dense control reproduced the right answer path with and without CUDA Graphs.
Conservative TriAttention also passed the corrected focused replay
The same corrected focused replay (aime25_2, then aime25_7) was then run on
the native TriAttention path using the conservative runtime policy:
TRTMC_TRIATTN_FORCE_ENABLE=1TRTMC_TRIATTN_OVERRIDE_KV_BUDGET=6144TRTMC_TRIATTN_OVERRIDE_DIVIDE_LENGTH=1024TRTMC_TRIATTN_RUNTIME_BUCKET_ROWS=32
Result:
aime25_2 -> 588aime25_7 -> 821
At that point, both dense and conservative TriAttention were again aligned on the key focused pair under the corrected benchmark runner.
The next required step from that state is a fresh full 30-sample apples-to- apples rerun using:
- the corrected TRT benchmark binary with per-row seed advancement
- dense control = hybrid bundle with
TRTMC_TRIATTN_FORCE_ENABLE=0 - TriAttention = same hybrid bundle with the conservative runtime policy
- HF eager as the external reference
Prompt mismatch later invalidated part of the focused aime25_7 story
While chasing the remaining full-benchmark miss on aime25_7, I discovered
that the old standalone sample file used for several earlier spot checks did
not contain the same prompt string as the current benchmark dataset row.
The old standalone prompt began with:
You are given a math problem.
The current benchmark prompt begins directly with the problem statement and the final-answer instruction:
The twelve letters ...Please reason step by step, and put your final answer within \boxed{}
That means several earlier "standalone sample 7" checks were not strict apples-to-apples comparisons against the current benchmark harness. The dense and TriAttention results on those old prompt files are still useful as local signals, but they cannot be treated as proof for the current benchmark recipe.
Current-prompt standalone checks changed the interpretation again
After extracting aime25_7 directly from the current benchmark prompt file, I
reran the same-family dense control and conservative TriAttention as strict
single-sample standalone runs.
Dense control, current prompt:
- seed
1234->pred_answer = 1 - seed
1240->pred_answer = 41
Artifacts:
artifacts/triattention/investigation-2026-04-17/tri32768_hybrid_force0_sample7_currentprompt_seed1234_gpu2.jsonlartifacts/triattention/investigation-2026-04-17/tri32768_hybrid_force0_sample7_currentprompt_seed1240_gpu1.jsonl
This was an important correction. The dense current-prompt path is already
wrong on aime25_7 for these seeds as a standalone sample, so the later
aime25_6 -> aime25_7 replay that produced 41 is no longer evidence of a
sequence-state corruption by itself.
Conservative TriAttention still diverges further on the same prompt
Using the exact same current benchmark prompt row and the same hybrid bundle, the conservative TriAttention path produced:
- seed
1234->pred_answer = 68
Artifact:
artifacts/triattention/investigation-2026-04-17/tri32768_hybrid_tri_sample7_currentprompt_seed1234_gpu2.jsonl
So the current sharp parity statement is:
- dense current-prompt standalone at seed
1234gives1 - TriAttention current-prompt standalone at the same seed gives
68 - gold answer remains
821
That means the remaining problem is no longer "TriAttention loses parity while
dense stays correct" on this case. On the current benchmark recipe, both paths
are already off the gold answer on aime25_7, and TriAttention departs even
further from the dense answer on the exact same prompt and seed.
The unfinished seed-1240 TriAttention run only reached pipeline load and did
not produce a sample row, so it is not evidence either way:
artifacts/triattention/investigation-2026-04-17/tri32768_hybrid_tri_sample7_currentprompt_seed1240_gpu3.logartifacts/triattention/investigation-2026-04-17/tri32768_hybrid_tri_sample7_currentprompt_seed1240_gpu3.jsonl
Current working interpretation
At this point the investigation has two distinct questions:
- why the current-prompt dense long-window path misses
aime25_7under these seeds - why conservative TriAttention departs from that dense path on the exact same prompt and seed
The next valid parity step is therefore not another broad benchmark rerun. It is a same-prompt, same-seed runtime diff between dense and TriAttention on the current benchmark sample until the first real divergence is localized.
Same-prompt boundary runs localized the divergence to the first compaction
Using the current benchmark prompt row for aime25_7 and the same hybrid
bundle:
- dense force-off, seed
1234,max_new_tokens=7000 - conservative TriAttention, same seed,
max_new_tokens=7000
produced byte-identical text:
- same
pred_answer = 10 - same
generated_tokens = 7000 - exact string equality on the generated text
Artifacts:
artifacts/triattention/investigation-2026-04-17/tri32768_hybrid_force0_sample7_currentprompt_seed1234_gpu1_max7000.jsonlartifacts/triattention/investigation-2026-04-17/tri32768_hybrid_tri_sample7_currentprompt_seed1234_gpu2_max7000.jsonl
This ruled out any "prefill mismatch" or "early decode mismatch" theory for this sample. The dense and TriAttention paths are identical through 7000 generated tokens.
Then a first-compaction abort run with keep-dump enabled showed the exact first compaction point:
planned_prompt_length = 158prompt_end_position = 158- first compaction at
absolute_position = 7168 keep_count = 6144
So the first compaction happens after exactly:
7168 - 158 = 7010generated tokens
Artifacts:
artifacts/triattention/investigation-2026-04-17/sample7_currentprompt_seed1234_firstcompartifacts/triattention/investigation-2026-04-17/tri32768_hybrid_tri_sample7_currentprompt_seed1234_firstcomp_abort.log
That means the 7000-token equality checkpoint is only 10 generated tokens
before the first compaction.
Divergence appears immediately after the first compaction window
At max_new_tokens=7600, dense and TriAttention already diverge on the same
prompt and seed:
- dense
pred_answer = 10 - TriAttention
pred_answer = 10 - but text is no longer equal
- first textual difference is still the same
char=24807boundary observed in the full outputs
Since the exact 7000-token text length is 24690, the first difference
appears only:
24807 - 24690 = 117characters
after the last pre-compaction-equal checkpoint.
Artifacts:
artifacts/triattention/investigation-2026-04-17/tri32768_hybrid_force0_sample7_currentprompt_seed1234_gpu2_max7600_rerun.jsonlartifacts/triattention/investigation-2026-04-17/tri32768_hybrid_tri_sample7_currentprompt_seed1234_gpu2_max7600.jsonl
This sharply localizes the problem: the remaining divergence begins almost immediately after the first compaction, not thousands of tokens later.
GPU-specific selection/compaction is no longer a viable root cause
The same 7600-token current-prompt run was repeated with the entire GPU
TriAttention fast path disabled:
TRTMC_TRIATTN_DISABLE_GPU_SELECT=1TRTMC_TRIATTN_DISABLE_GPU_COMPACT=1TRTMC_TRIATTN_DISABLE_GPU_STATE=1
Result:
- host-only TriAttention and normal GPU TriAttention were byte-identical
at
7600tokens
Artifacts:
artifacts/triattention/investigation-2026-04-17/tri32768_hybrid_tri_sample7_currentprompt_seed1234_hostonly_max7600.jsonlartifacts/triattention/investigation-2026-04-17/tri32768_hybrid_tri_sample7_currentprompt_seed1234_gpu2_max7600.jsonl
So the remaining issue is not in:
- the CUDA selector kernel
- the CUDA compaction kernel
- GPU-only state bookkeeping
It is shared host/GPU TriAttention logic above that layer.
The first-compaction keep-set disagrees with the upstream selector on this sample
The exact first-compaction K-cache snapshot for aime25_7 was replayed through
the local upstream per-head selector implementation in:
artifacts/triattention/upstream/triattention/vllm/runtime/selector_hf.py
using both:
artifacts/triattention/qwen3_8b_aime25.ptartifacts/triattention/upstream/triattention/calibration/for_aime25_experiment/qwen3_8b.pt
Both produced the same result:
- exact head matches:
0 / 8 - mean Jaccard overlap:
0.778429853926756
The protected prefix and recent tail still agree, but the selected interior rows differ materially.
This is the first decisive proof that, on the current sample and compaction state, the native selector semantics are still not matching the upstream selector semantics.
So the working diagnosis is now:
- the first real divergence happens in the first compaction window
- host/GPU native paths agree with each other
- native keep-set does not agree with upstream keep-set on this sample
The next debugging step from here is score-level comparison:
- dump native per-layer / aggregated score values for the first compaction
- compare them directly against the upstream selector scores on the same snapshot
- identify whether the mismatch is in raw scoring, per-head grouping, or cross-layer aggregation
Later correction: selector and compaction are now cleared on the aggressive sample7 path
The previous diagnosis above was too pessimistic. After replaying the same
aime25_7 first-compaction snapshot more carefully, the remaining bug is no
longer in the selector itself.
First, the attempted "reduce stats to runtime KV heads" patch was the wrong
direction and was reverted. On the exact sample7 first-compaction snapshot,
the original native 32-score-head semantics match the official upstream
selector exactly:
- simulated original native vs upstream official selector: exact head matches
8 / 8 - mean Jaccard overlap:
1.0
That proof used the real dumped pre-compaction cache from:
artifacts/triattention/investigation-2026-04-17/sample7_currentprompt_seed1234_debugprobe_revert4.json.layer00.binthroughartifacts/triattention/investigation-2026-04-17/sample7_currentprompt_seed1234_debugprobe_revert4.json.layer35.bin
and the real runtime keep dump:
artifacts/triattention/investigation-2026-04-17/sample7_currentprompt_seed1234_debugprobe_revert4.json
Second, the real C++ runtime keep-set is also exact with respect to the native math on that same dump:
- runtime keep-set vs direct native-math replay: exact head matches
8 / 8 - mean Jaccard overlap:
1.0
So by this point the aggressive sample7 first compaction has:
- official upstream selector parity
- native runtime selector parity
Third, the post-compaction cache contents are also exact gathers of the selected rows.
Using the first-compaction dump with explicit pre/post K/V cache snapshots:
artifacts/triattention/investigation-2026-04-17/sample7_currentprompt_seed1234_debugprobe_revert5.json
all 36 layers passed exact gather checks for both:
KV
That means:
- selected indices are correct
Krepack is correctVrepack is correct
Fourth, the boundary localization still holds on the aggressive operating point:
- dense force-off and TriAttention are byte-identical through
max_new_tokens=3000 - at
max_new_tokens=3400, they diverge - first text difference is still at
char=11217
Artifacts:
artifacts/triattention/investigation-2026-04-17/tri32768_hybrid_force0_sample7_currentprompt_seed1234_gpu1_max3000_revert.jsonlartifacts/triattention/investigation-2026-04-17/tri32768_hybrid_tri_sample7_currentprompt_seed1234_gpu2_max3000_revert.jsonlartifacts/triattention/investigation-2026-04-17/tri32768_hybrid_force0_sample7_currentprompt_seed1234_gpu1_max3400_revert.jsonlartifacts/triattention/investigation-2026-04-17/tri32768_hybrid_tri_sample7_currentprompt_seed1234_gpu2_max3400_revert.jsonl
Finally, the current aggressive-path host-only replay still matches the normal
GPU TriAttention replay exactly at 3400 tokens:
artifacts/triattention/investigation-2026-04-17/tri32768_hybrid_tri_sample7_currentprompt_seed1234_hostonly_max3400_revert.jsonlartifacts/triattention/investigation-2026-04-17/tri32768_hybrid_tri_sample7_currentprompt_seed1234_gpu2_max3400_revert.jsonl
So the updated diagnosis is:
- the remaining
sample7divergence still begins in the first compaction window - but it is not caused by selector mismatch
- and it is not caused by incorrect K/V gather
- and it is not a GPU-only kernel bug
At this point the remaining explanations are narrower:
- genuine quality loss from the aggressive compression operating point itself
- or a downstream runtime effect outside selector/repack that still has not been identified
Files most directly responsible for the final result
src/runtime/models/<family>/triattention_kv_cache.cppsrc/runtime/models/<family>/triattention_kernels.cusrc/runtime/models/<family>/triattention_kv_cache.htests/builder/test_triattention_runtime.py
Short version
TriAttention only became accuracy-safe after the native scorer was aligned with
the upstream direct-K_rot semantics. Before that, the runtime was close in
structure but wrong in the core scoring math. Once that was fixed, the native
dynamic-KV C++ path achieved the intended state on the corrected pilot:
- dense-quality answers
- real native compaction
- real throughput gain
2026-04-18: later-compaction absolute-position bug isolated and fixed
The full 30-sample AIME25 rerun had shown a severe TriAttention collapse even
though the short hard probes were still correct. At this point the next step
was to stop sweeping policy knobs and go back to direct tensor-level diffs.
What remained true before the fix
On the sampled aime25_7 repro:
- first compaction trigger was at absolute decode position
3200 - sampled-token divergence happened before compaction
2 - compaction
1selection still matched the upstream selector exactly - compaction
1post-compactionK/Vcaches were still exact gathers of the pre-compaction cache
So compaction 1 had already been ruled out as a selector or gather bug.
The concrete later-compaction bug
While reviewing TriAttentionKvCache, both selector paths were found to be
precomputing trig phases from the current compacted row count instead of the
true absolute decode position:
select_keep_indices_host()select_keep_indices_gpu()
They used:
round_start = total_tokens
instead of:
round_start = absolute_position_
That mistake is silent on compaction 1 because, before any rows are dropped,
total_tokens == absolute_position_. But it becomes wrong on all later
compactions after the cache has already been compacted once.
The fix was to switch both host and GPU trig-prep paths to
absolute_position_.
Live proof after the fix
Using the current sampled aime25_7 repro and replaying the dumped score cache
through the upstream selector:
- compaction
2dump:artifacts/triattention/investigation-2026-04-18/profile_switch/sample7_compaction2_postfix_dump- absolute position
3328 - exact head matches
8 / 8 - mean Jaccard
1.0
- absolute position
- fresh compaction
3dump from the current binary:artifacts/triattention/investigation-2026-04-18/profile_switch/sample7_compaction3_currentfix_dump- absolute position
3456 - exact head matches
8 / 8 - mean Jaccard
1.0
- absolute position
The later-compaction cache gather was then checked directly on that same live
compaction-3 dump using the recorded keep_indices_by_head together with the
dumped pre/post packed K/V cache snapshots:
- layers checked:
36 / 36 - heads checked per layer:
8 / 8 - exact gather matches:
K: yesV: yes
So compaction 3 on the fixed path now also has:
- upstream selector parity
- exact native post-compaction
Kgather - exact native post-compaction
Vgather
This also explained a confusing intermediate result: the old artifact
artifacts/triattention/qwen3-8b-nonflash/diff/current_native_r128_compact3_afterpatch_postcheck.json
was stale from 2026-04-17, so its compaction-3 mismatch was no longer a
valid live signal after the absolute-position fix.
Updated diagnosis after the fix
By this point:
- compaction
1selector matches upstream exactly - compaction
1K/V gather matches exactly - compaction
2selector matches upstream exactly - compaction
3selector matches upstream exactly
So the major later-compaction selector bug is fixed. The remaining question is
how much of the full-benchmark accuracy collapse that bug was responsible for.
The next active step is a fresh full 30-sample TriAttention rerun on the
fixed binary, using the same upstream_plain prompt recipe and decode settings
as the prior dense/HF comparison.
Full-benchmark rerun after the absolute-position fix
The fixed 3072/128 native rerun was then launched on the same full
upstream_plain AIME25 benchmark recipe. By the time the investigation moved
back to targeted diff testing, the live artifact had reached:
- result file:
artifacts/triattention/qwen3-8b-aime25-vs-hf-fullkv-plain-e2e-2026-04-18-currentfix/tri_results.jsonl - rows completed:
29 / 30 - correct:
14 / 29 - partial accuracy:
0.4828 - mean decode throughput across completed rows:
77.33 tok/s
This is a real improvement over the earlier broken full run (0.267), so the
absolute-position fix mattered materially. But it is still far from dense/HF
parity, so there must be either another implementation issue or a remaining
policy gap.
Comparing the completed fixed native rows against the dense/HF full-benchmark reference, the following rows were dense-correct and HF-correct but still native-wrong:
aime25_7aime25_9aime25_10aime25_11aime25_12aime25_13aime25_14aime25_15aime25_18aime25_20aime25_21aime25_22aime25_25aime25_28aime25_29
The earliest clean failing probe after the fix is therefore aime25_7, and the
next independent failing probe is aime25_9.
Upstream control checks on failing rows
To separate native-runtime bugs from budget/policy limitations, exact-prompt upstream TriAttention worker probes were launched on the same benchmark prompt format.
On aime25_7 with the official upstream TriAttention worker at 3072/128:
- output file:
tmp/upstream_worker_sample7_tri_local/shard00/run000.jsonl - result: wrong
- predicted boxed answer:
271 - gold answer:
821
On aime25_12 with the official upstream TriAttention worker at 3072/128:
- output file:
tmp/upstream_worker_sample12_tri_local/shard00/run000.jsonl - result: also not correct
- gold answer:
510 - no clean final boxed answer was extracted
So aime25_7 and aime25_12 are not enough by themselves to prove another
native-only implementation mismatch at the aggressive 3072/128 operating
point. They are at least partly compatible with an upstream policy/quality
limit.
New sample9 compaction anomaly to resolve next
The next clean candidate for a native-only bug became aime25_9, because dense
and HF both solve it while the fixed native 3072/128 run does not:
- gold answer:
62 - fixed native current rerun answer:
2 - dense full-KV answer:
62 - HF eager answer:
62
Two targeted native sample9 probes were then started:
- a
5000-token native standalone run withTRTMC_TRIATTN_DUMP_KEEP_PATH=/workspace/tensorrt-model-connect/tmp/aime25_9_plain_compaction1_dumpandTRTMC_TRIATTN_ABORT_AFTER_DUMP=1 - a focused trace around positions
3190..3220
The first surprising result is that the 5000-token standalone native run
finished without ever writing the requested compaction dump:
- output file:
tmp/aime25_9_plain_compaction1_abort.jsonl - generated tokens:
5000 - predicted answer at cutoff:
3 - expected dump file:
tmp/aime25_9_plain_compaction1_dump - observed status: dump file absent
That is suspicious, because the same bundle reports live TriAttention init with:
kv_budget=3072divide_length=128count_prompt_tokens=1protect_prefill=1
and earlier sample7 traces from the same runtime clearly show the cache already
compacted to 3072 rows by the 3200-position window:
- trace file:
tmp/aime25_7_plain_tri_trace_3200_3600.jsonl - first traced row:
position_before = 3200rows_before = 3072rows_after = 3104
So the next concrete question is not “which config sweep next?” but:
- does native sample9 actually compact live KV at the expected threshold?
- if it does, why was the compaction dump hook not hit?
- if it does not, what runtime condition is preventing compaction on this row?
Sample9 compaction was then proven live
That sample9 compaction question was resolved by switching from the dump hook to the built-in compaction profile logs.
Using the same native current bundle on the exact benchmark prompt:
- bundle:
artifacts/triattention/qwen3-8b-nonflash/qwen3-8b-tri12288-b3072-r128-dynkv-fp16-manual-current.bundle - dataset row:
tmp/aime25_rescue_samples/aime25_09.jsonl - runtime flags:
TRTMC_TRIATTN_FORCE_ENABLE=1TRTMC_TRIATTN_PROFILE=1TRTMC_TRIATTN_DEBUG=1
The profile logs showed native compaction firing exactly on the expected schedule:
- compact
#1:abs_pos=3200,old_rows=3200,kept_rows=3072 - compact
#2:abs_pos=3328,old_rows=3200,kept_rows=3072 - compact
#3:abs_pos=3456,old_rows=3200,kept_rows=3072 - compact
#4:abs_pos=3584,old_rows=3200,kept_rows=3072
So the missing sample9 dump file was not evidence that compaction was disabled. Compaction is definitely occurring on sample9; the problem is elsewhere.
Sample9 diverges only after the first compaction step
The next check compared the exact same bundle in two modes on the sample9 prompt:
- TriAttention enabled:
TRTMC_TRIATTN_FORCE_ENABLE=1 - same bundle, force-off control:
TRTMC_TRIATTN_FORCE_ENABLE=0
Both runs used:
--max-new-tokens 3300--temperature 0.6--top-k 20--top-p 0.95--min-p 0.0--seed 1234
and recorded the same trace window:
TRTMC_TEXT_STEP_TRACE_START_POS=3190TRTMC_TEXT_STEP_TRACE_END_POS=3220
Trace artifacts:
- TriAttention trace:
tmp/aime25_9_tri_trace_3190_3220.jsonl - force-off trace:
tmp/aime25_9_force0_trace_3190_3220.jsonl
What that diff proved:
- positions
3190..3198: logits match exactly - position
3199:- argmax and top logits still match exactly
- only the cache-row count changes
- TriAttention:
rows_after = 3072 - force-off:
rows_after = 3200
- position
3200:- first actual logit drift appears
- argmax is still the same in both paths
- differences are small (
~1e-2scale in top logits)
- positions
3190..3220:- argmax stays identical for the whole traced window
So sample9 is not another “explodes immediately at compaction” failure. It is a much subtler case:
- native TriAttention matches the force-off control exactly until the first compaction boundary
- the first numerical drift begins one token after compaction
- that drift is initially small and does not immediately change argmax
That shifts the next investigation step again. The remaining question is no longer “does sample9 compact?” but instead:
- are the selected rows themselves still aligned with upstream on sample9?
- if yes, is the residual accuracy gap on sample9 also reproducible upstream at the same budget?
- if not, what still differs in native sample9 selection or repack semantics?
Fixed 3072/128 full benchmark completed at 14 / 30
The full post-fix aggressive rerun eventually finished:
- result file:
artifacts/triattention/qwen3-8b-aime25-vs-hf-fullkv-plain-e2e-2026-04-18-currentfix/tri_results.jsonl - completed rows:
30 / 30 - correct:
14 / 30 - final accuracy:
0.4667 - mean decode throughput:
77.18 tok/s
Wrong rows in the completed fixed rerun:
aime25_7aime25_9aime25_10aime25_11aime25_12aime25_13aime25_14aime25_15aime25_18aime25_20aime25_21aime25_22aime25_25aime25_28aime25_29aime25_30
So the absolute-position fix rescued a large chunk of the benchmark, but it did not restore full accuracy parity on the aggressive bundle.
Same-family 6144/1024 sample9 also fails in the live full run
The fair same-family run was already live on:
- bundle:
artifacts/triattention/qwen3-8b-nonflash/qwen3-8b-tri32768-b3072-r128-dynkv-fp16-manual-denseengine-hybrid.bundle - runtime overrides:
TRTMC_TRIATTN_OVERRIDE_KV_BUDGET=6144TRTMC_TRIATTN_OVERRIDE_DIVIDE_LENGTH=1024TRTMC_TRIATTN_RUNTIME_BUCKET_ROWS=32
and by the time the focused debugging resumed it had reached:
- result file:
artifacts/triattention/qwen3-8b-aime25-vs-hf-fullkv-plain-e2e-2026-04-18-override6144/tri_results.jsonl - rows completed so far:
9 - correct so far:
7 - partial accuracy so far:
0.7778
Crucially, sample9 is still wrong even on this more conservative same-family path:
aime25_9- predicted answer:
-3 - gold answer:
62 - generated tokens:
38912
- predicted answer:
So sample9 remains a live failure on both:
- the aggressive current bundle
- the same-family conservative
6144/1024hybrid run
Same-family 6144/1024 sample9 reaches first compaction cleanly
To inspect the fair path directly, sample9 was then rerun on the same-family
hybrid bundle with the same 6144/1024 overrides and a focused trace window:
- trace file:
tmp/aime25_9_hybrid6144_tri_trace_7150_7190.jsonl
That trace shows the first compaction exactly where it should happen:
- position
7150:rows_before = 7168rows_after = 7168
- position
7167:rows_before = 7168rows_after = 6144
- position
7190:rows_before = 6176rows_after = 6176
So on the fair hybrid path as well:
- compaction is definitely active
- the first compaction boundary is clean and on schedule
The next pending comparison is the same hybrid bundle with
TRTMC_TRIATTN_FORCE_ENABLE=0 over the same 7150..7190 window, to see whether
the first fair-path numerical drift is again only a small post-compaction
difference or something more severe.
Same-family 6144/1024 sample9 vs force-off: same pattern again
That pending force-off comparison was then completed on the exact same hybrid bundle and trace window:
- TriAttention trace:
tmp/aime25_9_hybrid6144_tri_trace_7150_7190.jsonl - force-off trace:
tmp/aime25_9_hybrid6144_force0_trace_7150_7190.jsonl
Diff result:
- first any difference:
- position
7167 - rows only
- TriAttention:
rows_after = 6144 - force-off:
rows_after = 7168 - logits still match exactly
- position
- first actual logit drift:
- position
7168 - argmax still the same in both paths
- top-id ordering still the same
- top-logit deltas are again small (
~1e-2to1e-1)
- position
- across the whole traced window
7150..7190:- argmax never changes between TriAttention and force-off
So the fair-path same-family result matches the earlier aggressive-bundle sample9 finding:
- TriAttention stays numerically identical up to the compaction step itself
- the first drift appears one token later
- the initial drift is small and does not immediately flip argmax
This again argues against a gross native compaction bug on sample9. The remaining miss is increasingly consistent with a long-horizon quality loss from compression at this budget, unless the pending upstream sample9 control proves otherwise.
Upstream sample9 3072/128 control also fails
To get a faster upstream control than the original shard worker, a dedicated single-sample upstream harness was added:
- script: local scratch harness
run_upstream_triattention_single.py
It loads the same local Qwen3-8B snapshot, applies the official
apply_triattention_patch(), and stops generation once a boxed answer appears
or the configured token cap is hit.
Running that harness on sample9 at the aggressive upstream point:
- sample file:
tmp/aime25_rescue_samples/aime25_09.jsonl - budget:
3072 - divide length:
128 - max new tokens:
8192 - output:
tmp/upstream_single_sample9_b3072_d128.json
Result:
- sample id:
aime25_9 - gold answer:
62 - predicted answer extracted:
"" - generated tokens:
8192 - no valid final boxed answer was extracted
So sample9 is not a clean native-only failure at 3072/128. Official
upstream TriAttention also fails to produce the correct answer for this row
under the same prompt recipe and a long boxed-answer run.
That pushes the interpretation further toward:
- sample9 is a real quality miss for this operating point, not a clear native implementation bug
The next active upstream check is therefore sample9 at the more conservative
6144/1024 setting, which is the operating point currently being compared
against the fair same-family native hybrid path.
Upstream sample9 6144/1024 control also fails
The same upstream single-sample harness was then run on sample9 at the more conservative operating point:
- sample file:
tmp/aime25_rescue_samples/aime25_09.jsonl - budget:
6144 - divide length:
1024 - max new tokens:
8192 - output:
tmp/upstream_single_sample9_b6144_d1024.json
Result:
- sample id:
aime25_9 - gold answer:
62 - predicted answer extracted:
"" - generated tokens:
8192 - no valid final boxed answer was extracted
So sample9 also fails in the official upstream Python patch at the same
6144/1024 point that is currently being used for the fair same-family native
hybrid run. This is a stronger discriminator than the earlier 3072/128
result:
- sample9 remains a real quality miss for TriAttention on this prompt recipe
even at the conservative
6144/1024setting - the native miss on sample9 is therefore still not sufficient evidence of a native implementation bug
Same-family 6144/1024 full run: early partial still breaks the same rows
The full same-family native hybrid benchmark at 6144/1024 is still running:
- output:
artifacts/triattention/qwen3-8b-aime25-vs-hf-fullkv-plain-e2e-2026-04-18-override6144/tri_results.jsonl
At the latest partial checkpoint:
- rows completed:
12 / 30 - correct:
8 / 12 - partial accuracy:
0.6667 - mean decode throughput so far:
69.14 tok/s
Wrong rows so far:
aime25_7-> predicted16, gold821aime25_9-> predicted-3, gold62aime25_10-> predicted70, gold81aime25_11-> predicted36, gold259
This is materially better than the old aggressive 3072/128 benchmark result,
but it still breaks the same early hard rows that motivated the deeper
investigation.
Next diff artifact in flight: native sample9 full token trace
To move from aggregate scores to token-level diffing, a dedicated native trace run was started for sample9 on the same-family hybrid path:
- bundle:
artifacts/triattention/qwen3-8b-nonflash/qwen3-8b-tri32768-b3072-r128-dynkv-fp16-manual-denseengine-hybrid.bundle - runtime overrides:
kv_budget=6144,divide_length=1024,bucket_rows=32 - trace target:
tmp/aime25_9_hybrid6144_fulltrace_0_7180.jsonl - benchmark output:
tmp/aime25_9_hybrid6144_trace_run.jsonl
That trace is intended to capture the exact native generated token ids from the
first decode step through the first 6144/1024 compaction boundary, so the
same token prefix can then be replayed teacher-forced through:
- dense HF
- upstream Python TriAttention
using the new helper:
- local scratch helper
trace_hf_teacher_forced.py
The goal of that replay is to decide whether the remaining gap is:
- a native-vs-upstream implementation divergence, or
- a true quality loss shared by both native and upstream TriAttention under this benchmark recipe.
Native sample9 8192/1024 still fails
To test whether sample9 would recover under a larger decode budget on the same-family native hybrid path, a dedicated native run was executed with:
- bundle:
artifacts/triattention/qwen3-8b-nonflash/qwen3-8b-tri32768-b3072-r128-dynkv-fp16-manual-denseengine-hybrid.bundle - runtime overrides:
kv_budget=8192,divide_length=1024,bucket_rows=32 - sample file:
tmp/aime25_rescue_samples/aime25_09.jsonl - output:
tmp/aime25_9_hybrid8192_stop.jsonl
Result:
- sample id:
aime25_9 - gold answer:
62 - predicted answer:
-1848 - generated tokens:
38912 - decode throughput:
45.04 tok/s
So sample9 is still wrong even at native 8192/1024. This does not prove
that larger budgets can never recover the row, but it does rule out the
simplest “just move from 6144 to 8192” explanation for the sample9 miss.
Sample9 boundary replay exposed a prompt-token accounting mismatch
The new teacher-forced replay helper:
- local scratch helper
trace_hf_teacher_forced.py
was first used to compare the native sample9 trace against dense HF and the
upstream Python patch around the first 6144/1024 compaction boundary:
- native trace:
tmp/aime25_9_hybrid6144_fulltrace_0_7180.jsonl - dense replay:
tmp/dense_teacherforced_sample9_trace_fast_7160_7180.jsonl - upstream replay:
tmp/upstream_teacherforced_sample9_6144_trace_fast_7160_7180.jsonl
The first replay attempt used count_prompt_tokens=True in the Python patch,
matching the current builder/runtime config export. That exposed an immediate
semantic mismatch:
- sample9 prompt length is
157tokens - dense HF raw cache length at position
7166is7323 - native
rows_beforeat the same step is7168
That 7323 - 7168 = 155 gap is essentially the prompt length (modulo bucketed
row rounding in the native trace), which means the native trigger is behaving
like decode-only accounting even though the exported config still says:
count_prompt_tokens = true
This makes sense after reading the native runtime code:
cache_length_only grows inappend_present_to_cache()- prefill tokens do not contribute to
cache_length_ - compaction triggering uses
cache_length_ >= compaction_trigger_length() - relevant code:
src/runtime/models/<family>/triattention_kv_cache.cpparound the append/cache-length and compaction-trigger paths
So the earlier Python replay with count_prompt_tokens=True was not an
apples-to-apples comparison against the native runtime behavior.
Aligned sample9 replay: native and upstream match through the boundary
The upstream replay was rerun with:
--no-count-prompt-tokens
using:
- aligned upstream replay:
tmp/upstream_teacherforced_sample9_6144_noprompt_trace_fast_7160_7180.jsonl - aligned hidden dumps:
tmp/upstream_teacherforced_sample9_6144_noprompt_hidden_fast_7160_7180
Key result on the window 7160..7180:
- native and upstream-no-prompt have no argmax mismatch in this window
- native and dense first differ on argmax at position
7177 - native and upstream-no-prompt stay argmax-identical through the whole window
Representative positions:
- position
7167(compaction step):- native argmax:
7196 - upstream-no-prompt argmax:
7196 - native rows:
7168 -> 6144 - upstream-no-prompt rows:
7324 -> 6144
- native argmax:
- position
7168(first post-compaction token):- native argmax:
356 - upstream-no-prompt argmax:
356
- native argmax:
- position
7177:- native argmax:
30 - dense argmax:
476 - upstream-no-prompt argmax:
30
- native argmax:
So for sample9, once prompt-token accounting is aligned, the native runtime matches the upstream Python patch at the token-logit level through the first post-compaction window.
Dense-vs-TriAttention hidden states stay identical until compaction, then drift slightly
Dense hidden dumps:
tmp/dense_teacherforced_sample9_hidden_fast_7160_7180
Aligned upstream-no-prompt hidden dumps:
tmp/upstream_teacherforced_sample9_6144_noprompt_hidden_fast_7160_7180
Comparing dense vs upstream-no-prompt hidden states:
- positions
7166and7167:- relative L2 difference is exactly
0.0across all dumped layers
- relative L2 difference is exactly
- position
7168:- first post-compaction hidden drift appears
- max sampled relative L2 across layers is about
1.05%
- position
7171:- max relative L2 is about
1.71%
- max relative L2 is about
- position
7177:- max relative L2 is about
2.83%
- max relative L2 is about
This is the cleanest sample9 proof so far:
- dense and TriAttention are numerically identical right up to the compaction boundary
- the first dense-vs-TriAttention hidden-state drift appears immediately after compaction
- the drift starts small
- native and upstream-no-prompt track each other on that same boundary window
So sample9 now looks much more like a shared quality loss from compression than a remaining native implementation bug.
Later correction: the teacher-forced replay harness was double-counting prompt tokens
The earlier "full native trace" HF/upstream replays were still contaminated by a bug in the replay harness itself.
The root cause was in:
- local scratch helper
trace_hf_teacher_forced.py
That helper always:
- prefills the prompt through HF, and then
- replays all rows from the native TRT trace as warmup/replay tokens
But the native TRT step trace already includes the prompt-side prefill steps. So for full traces like:
tmp/aime25_7_currentfix_fulltrace_0_3480.jsonltmp/aime25_9_hybrid6144_fulltrace_0_7180.jsonl
the prompt was being counted twice in the HF/upstream replay.
The symptom was obvious in the replay outputs:
- native sample9 trace position
7150 - replay dense/upstream rows_before
7307 - prompt length
157
That is exactly the prompt-length offset caused by replaying prompt rows after the prompt had already been prefetched.
The harness was corrected so that when a native trace starts at position 0,
the replay skips all trace rows below the prompt token count after the initial
HF prompt prefill.
Corrected sample9 replay: native, dense HF, and upstream all stay aligned
After fixing the local scratch helper trace_hf_teacher_forced.py, sample9
was rerun on the exact same native token trace and window:
- native trace:
tmp/aime25_9_hybrid6144_fulltrace_0_7180.jsonl - corrected dense replay:
tmp/dense_teacherforced_sample9_hybrid6144_promptfix_gpu3_trace_7150_7190.jsonl - corrected upstream replay:
tmp/upstream_teacherforced_sample9_hybrid6144_promptfix_gpu3_trace_7150_7190.jsonl - corrected dense hidden dumps:
tmp/dense_teacherforced_sample9_hybrid6144_promptfix_gpu3_hidden_7150_7190 - corrected upstream hidden dumps:
tmp/upstream_teacherforced_sample9_hybrid6144_promptfix_gpu3_hidden_7150_7190
Corrected result:
- native vs dense first argmax diff:
None - native vs upstream first argmax diff:
None - dense vs upstream first argmax diff:
None
Across the first compaction boundary:
- position
7167:- native argmax
7196 - dense argmax
7196 - upstream argmax
7196
- native argmax
- position
7168:- native argmax
356 - dense argmax
356 - upstream argmax
356
- native argmax
- positions
7177,7179,7180:- all three paths still have identical argmax decisions
Dense vs upstream hidden-state comparison is also exact on the sampled positions:
7150,7166,7167,7168,7176,7177,7179,7180all have relative L20.0
So the earlier sample9 claim that native/upstream diverged after compaction was an artifact of the replay bug, not a real runtime mismatch.
Corrected sample7 replay: the previously reported 3455 divergence also disappears
The same corrected replay was then applied to sample7:
- native trace:
tmp/aime25_7_currentfix_fulltrace_0_3480.jsonl - corrected dense replay:
tmp/dense_teacherforced_sample7_currentfix_promptfix_gpu3_trace_3440_3479.jsonl - corrected upstream replay:
tmp/upstream_teacherforced_sample7_currentfix_promptfix_gpu3_trace_3440_3479.jsonl - corrected dense hidden dumps:
tmp/dense_teacherforced_sample7_currentfix_promptfix_gpu3_hidden_3440_3479 - corrected upstream hidden dumps:
tmp/upstream_teacherforced_sample7_currentfix_promptfix_gpu3_hidden_3440_3479
Corrected result:
- native vs dense first argmax diff:
None - native vs upstream first argmax diff:
None - dense vs upstream first argmax diff:
None
Representative positions that had previously looked suspicious are now all aligned on argmax:
3444->437783455->43253456->65243479->387
Dense vs upstream hidden states are not exactly identical on this sample window (sampled relative L2 is a few percent on some positions), but the token-level argmax path remains identical across native, dense, and upstream throughout the entire dumped range.
So the earlier sample7 "native diverges at 3455 and snaps onto upstream" claim was also caused by the same prompt-double-count bug in the replay harness.
Current implication
The two strongest tensor-level diff targets checked after fixing the replay harness now say the same thing:
- sample7 currentfix window: native == dense == upstream on argmax
- sample9 hybrid
6144/1024window: native == dense == upstream on both argmax and sampled dense-vs-upstream hidden states
So the debugging target has moved again:
- the previously reported token-level divergence windows are no longer valid evidence of a native implementation bug
- any remaining benchmark-level misses now need either
- a later trace window on the true failing sample/path, or
- a fresh full-benchmark rerun on the trustworthy hybrid bundle after correcting the replay methodology
Exact current-prompt sample7 is not a valid native-only isolation target
To avoid mixing old prompt recipes with the current benchmark prompt file, a single-row repro was rebuilt directly from:
artifacts/triattention/qwen3-8b-aime25-vs-hf-hybrid-conservative-e2e-seedfix-2026-04-18/aime25_prompts.jsonl- sample id:
aime25_7 - injected
seed_index = 6 - seed:
1234 - effective sample seed:
1240 - same-family bundle:
artifacts/triattention/qwen3-8b-nonflash/qwen3-8b-tri32768-b3072-r128-dynkv-fp16-manual-denseengine-hybrid.bundle
Standalone results on that exact prompt/seed:
- force-off same-family control:
tmp/aime25_7_hybrid_force0_seed1234_stop.jsonl- predicted answer:
41 - gold answer:
821
- predicted answer:
- TriAttention
6144/1024:tmp/aime25_7_hybrid_tri6144_seed1234_stop.jsonl- predicted answer:
247 - gold answer:
821
- predicted answer:
So on the exact current benchmark prompt, aime25_7 is not a clean
TriAttention-only regression. The same-family dense control is already wrong,
which means sample7 is not a trustworthy tensor-diff target for isolating a
native TriAttention bug.
Exact current-prompt sample9: first live split is far after compaction
The next target was rebuilt from the same current prompt file:
tmp/aime25_9_hybrid_seedfix_seedidx8.jsonl- sample id:
aime25_9 - injected
seed_index = 8 - effective sample seed:
1242
Using the same-family hybrid bundle with:
- force-off control:
tmp/aime25_9_hybrid_force0_fulltrace_0_10000.jsonl - TriAttention
6144/1024:tmp/aime25_9_hybrid_tri6144_fulltrace_0_10000.jsonl
and matching 10000-token no-stop runs:
- force-off output:
tmp/aime25_9_hybrid_force0_run_10000.jsonl - TriAttention output:
tmp/aime25_9_hybrid_tri6144_run_10000.jsonl
The direct native-vs-native trace diff shows:
- first structural difference:
- position
7167 - compaction only
- force-off
rows_after = 7168 - TriAttention
rows_after = 6144 - logits still match
- position
- first live sampled-token split:
- position
7427 - force-off token id:
1430 - TriAttention token id:
1221
- position
- first live argmax split in those native traces:
- also position
7427
- also position
So on the exact current prompt, the first real live split is not at the
compaction boundary. The two native paths stay aligned for roughly 260 more
tokens after compaction before the first sampled-token deviation appears.
Current-prompt sample9 replay: no clean native-only tensor drift before the split
The exact current-prompt sample9 TriAttention trace was then replayed teacher-forced through:
- dense HF:
tmp/dense_teacherforced_sample9_seedfix_from_tri_7400_7460.jsonl - upstream Python TriAttention:
tmp/upstream_teacherforced_sample9_seedfix_from_tri_7400_7460.jsonl
using the native trace:
tmp/aime25_9_hybrid_tri6144_fulltrace_0_10000.jsonl
and the same current sample file:
tmp/aime25_9_hybrid_seedfix_seedidx8.jsonl
The only earlier argmax discrepancy reported by this replay window is at
position 7404, but that turns out to be a top-logit tie, not a meaningful
split:
- native dense and native tri both report:
- top-1
1477 - top-2
7942 - equal top logits at that step
- top-1
- dense HF replay and upstream replay report the same two tied tokens in the
opposite order:
- top-1
7942 - top-2
1477 - equal top logits at that step
- top-1
That is tie-breaking noise, not evidence of a semantic native/runtime drift.
On the last shared live prefix before the first sampled split (7426), all
four views remain aligned in substance:
- native dense live
- native tri live
- dense HF replay
- upstream Python TriAttention replay
At position 7426 they all still have:
- token id
323 - argmax token
400 - same top-id ordering at the head:
400,1430,1221,1490,1779
So the current exact-prompt sample9 replay again fails to expose a clean native-only tensor bug before the first live split. The evidence currently points toward a sampling-sensitive/shared compression effect, not an obvious native runtime mismatch.
Live upstream current-prompt sample9 also fails at 6144/1024
To check that conclusion outside the native TRT runtime, the official upstream Python TriAttention patch was run live on the exact same current prompt/seed:
- sample file:
tmp/aime25_9_hybrid_seedfix_seedidx8.jsonl - effective sample seed:
1242 - stats:
artifacts/triattention/qwen3_8b_aime25.pt - budget:
6144 - divide length:
1024 - output:
tmp/upstream_live_sample9_seedfix_6144_d1024_seed1242_10000.json
Result:
- sample id:
aime25_9 - gold answer:
62 - predicted boxed answer extracted:
"" - generated tokens:
10000 - no valid boxed final answer was produced in the capped run
So on the exact current benchmark prompt and seed, upstream Python
TriAttention also fails to produce a clean solved answer for sample9 at the
same 6144/1024 operating point.
That further weakens the case for a native-only runtime bug on this row. The current evidence stack for sample9 is now:
- same-family native dense and TriAttention stay aligned until long after the first compaction
- corrected teacher-forced dense HF and upstream replay do not expose a clean native-only tensor drift before the first live split
- live upstream TriAttention on the exact same current prompt/seed also fails
The most defensible interpretation at this point is still that sample9 is primarily a shared TriAttention quality loss on this benchmark recipe, not an isolated native TRT implementation bug.
Exact current-prompt sample10 shows the same delayed post-compaction split shape
To check whether sample9 was a one-off, the same exact-prompt trace cycle was run on the next independent failing row:
- sample file:
tmp/aime25_10_hybrid_seedfix_seedidx9.jsonl - effective sample seed:
1243 - same-family force-off trace:
tmp/aime25_10_hybrid_force0_fulltrace_0_8000.jsonl - same-family TriAttention
6144/1024trace:tmp/aime25_10_hybrid_tri6144_fulltrace_0_8000.jsonl
The direct native-vs-native trace diff shows the same broad shape as sample9:
- first structural difference:
- position
7167 - compaction only
- force-off
rows_after = 7168 - TriAttention
rows_after = 6144
- position
- first live sampled-token split:
- position
7477
- position
- first live argmax split:
- also position
7477
- also position
So sample10 also does not explode at the compaction boundary itself. It
stays aligned for roughly 300 more tokens after compaction before the first
sampled-token deviation appears. That further supports the interpretation that
the current failures are dominated by long-horizon compression effects rather
than a simple native compaction corruption bug.
Exact current-prompt sample9 improves materially at 8192/1024
Because the current evidence points more toward shared compression loss than a native-only bug, the next targeted check was whether a more conservative current-prompt operating point materially delays or removes the sample9 split.
Using the same exact current sample9 prompt and seed:
- sample file:
tmp/aime25_9_hybrid_seedfix_seedidx8.jsonl - effective sample seed:
1242 - force-off trace:
tmp/aime25_9_hybrid_force0_fulltrace_0_10000.jsonl - TriAttention
8192/1024trace:tmp/aime25_9_hybrid_tri8192_fulltrace_0_10000.jsonl - TriAttention
8192/1024short run:tmp/aime25_9_hybrid_tri8192_run_10000.jsonl
Compared with the old 6144/1024 result:
- at
6144/1024- first live sampled-token split:
7427 10000-token extracted answer:21
- first live sampled-token split:
- at
8192/1024- first structural difference:
9215(first compaction boundary) - first live sampled-token split:
9745 - no argmax split before that sampled-token deviation
10000-token extracted answer:062
- first structural difference:
So on the exact current prompt, raising the budget from 6144 to 8192
substantially improves sample9:
- the first split moves later by more than
2300positions - the
10000-token extracted answer matches the dense control's extracted value
This is the strongest evidence so far that the remaining misses may be recoverable by a more conservative TriAttention operating point, even though it is still not enough for full parity.
Exact current-prompt sample9 full run at 8192/1024 still misses late
The improved sample9 point was then rerun under the full benchmark-style capped decode:
- output:
tmp/aime25_9_hybrid_tri8192_stop_full.jsonl
Result:
- sample id:
aime25_9 - gold answer:
62 - predicted answer:
32 - generated tokens:
25104
So 8192/1024 materially improves the trajectory on current-prompt sample9,
but it still does not fully recover the row under the full long decode. That
keeps the main conclusion intact:
- the failure is now looking less like a native runtime bug
- and more like a quality-vs-budget tradeoff that still needs a better operating point for parity
Sample2 dense-vs-HF split was mostly a sampler bug, then a precision gap
The next debugging cycle switched from config sweeps to direct dense-vs-HF diff testing on the exact current-prompt sample2 repro:
- sample file:
tmp/aime25_2_seedfix_seedidx1.jsonl - dense trace:
tmp/aime25_2_densefullkv_torchsampler_trace.jsonl - captured HF token ids:
tmp/hf_sample2_seed1235_2400_ids.json
The first important finding was that the old native sampler bridge was still wrong even after moving to libtorch:
- CUDA
torch.multinomialover the full masked vocabulary is not equivalent to sampling over the compressed kept-slice with the same renormalized probabilities. - Direct container probes showed large mismatches for the same seed on the same
sparse distributions. For example on the exact step2-style two-way
distribution:
- full-vocab sparse tensor: token
419 - compressed kept-slice tensor: token
279
- full-vocab sparse tensor: token
That proved the bridge had to sample over a reusable full-vocab probability tensor, not the compressed kept subset.
The sampler was then fixed to:
- build the filtered kept set as before
- scatter kept probabilities back into a reusable full-vocab CUDA tensor
- call
at::multinomial(..., replacement=false, generator_)on that full tensor
Regression coverage was added to lock in the sampler's sparse full-vocab behavior.
After that fix:
- the first dense-vs-HF sampled-token split on exact sample2 moved from
generated token
2to generated token48 - the first
64native generated tokens became:2014, 11625, 279, 3491, ... , 29208, 32711, 1447, ...
The next question was whether the remaining split at generated token 48
(32711 vs earlier HF 29985) was still a sampler bug or real model drift.
That was answered with two direct probes:
-
Dense teacher-forced replay on the corrected no-prompt-last trace:
- trace:
tmp/aime25_2_densefullkv_torchsampler_trace_nopromptlast.jsonl - replay:
tmp/dense_teacherforced_sample2_torchsampler_653_704_nopromptlast.jsonl
Result:
- dense TRT and dense HF argmax/top-k still match through positions
653..704 - so there is no early native-only tensor corruption on this window
- trace:
-
Single-GPU HF generate probes at different dtypes:
- HF
bfloat16, eager, exact current prompt/seed:- generated token
48:29985 - processed probabilities at step
48:[0.47153, 0.25239, 0.16639, 0.10969]
- generated token
- HF
float16, eager, exact current prompt/seed:- generated token
48:32711 - first
64generated tokens match the native dense trace exactly
- generated token
- HF
So the remaining sample2 split after the sampler fix is not a TRT-only logic bug. It is explained by precision:
- native dense bundle is
fp16 - HF “golden” runs had been checked in
bf16 - on this exact row, HF
float16matches native dense while HFbfloat16samples a different token later in the run
This materially changes the interpretation of the dense-vs-HF gap:
- there was a real sampler bug in native
- after fixing it, the residual sample2 disagreement is best explained as
fp16vsbf16sampling sensitivity, not an obvious native runtime defect
Sparse exact CUDA sampler replaces the slow full-vocab torch bridge
The next step was to remove the large runtime cost of the temporary full-vocab torch bridge without giving up parity.
Two direct probes established the target semantics:
-
CUDA
torch.multinomial(..., replacement=False)for one sample matches the exponential-race form exactly:argmax(probs / exponential_(1.0))
-
For contiguous float vocab tensors on our current GPU and Qwen3-8B vocab size, PyTorch's CUDA
exponential_path is effectively:- one thread per vocab index
curand_uniform4(&state).xfor that absolute index- generator offset increment of
4per decode step
That led to a new exact sparse CUDA sampler:
- keep the existing host-side filter/top-p/top-k/min-p logic
- copy only the kept token ids and kept probabilities to GPU
- reproduce PyTorch's full-vocab RNG semantics for those absolute indices
- sample with the same exponential-race rule that PyTorch uses internally
The implementation is in:
src/runtime/models/<family>/sparse_multinomial_kernel.cusrc/runtime/models/<family>/sampler.cpp
New sampler regression coverage was added for:
- sparse full-vocab semantics
- offset progression across repeated draws
- a synthetic three-way case derived from the live sample2 first decode step
Seed-index correction for dataset benchmark repros
One false alarm in the first live validation came from the benchmark driver:
examples/trtmc_dataset_benchmark.cppadds each sample'sseed_indexto the CLI--seed
So for:
tmp/aime25_2_seedfix_seedidx1.jsonl
using:
--seed 1235
actually means:
- effective seed
1236
The correct base seed for the earlier HF sample2 reference is:
--seed 1234
which yields effective seed 1235 because seed_index=1.
Live sample2 validation after the sparse exact sampler
After correcting that seed-index mistake, the new sparse exact sampler was rerun on the exact current-prompt dense sample2 repro.
Artifacts:
tmp/aime25_2_densefullkv_sparseexact_seed1234_trace.jsonltmp/aime25_2_densefullkv_sparseexact_seed1234_out.jsonl
Result:
- the first
64generated native tokens exactly match the previously validated dense trace and the saved HFfloat16reference - the first dense-vs-HF
bfloat16split remains at generated token48(32711vs29985), exactly as before
So the sparse exact sampler preserves the already-proven dense parity behavior
while avoiding the expensive full-vocab scatter plus at::multinomial call.