Skip to content

[Benchmark] 2x RTX 4090 (PCIe 4.0 x16, TRX40): UD-Q4_K_XL at 307K - tuned-config metrics plus a Gen4/Gen3 x chunk/layer-major matrix #4573

Closed

Description

@udqe

TL;DR

Results only, measured 2026-10-28/29 (JST) on one machine. Unsloth UD-Q4_K_XL on 2x RTX 4090 at 307,200 configured context, engine 0.1.52, Terminal 0.2.7.

  • Production steady state (chunk-major prefill, expert cache hot): decode 122-127 tok/s at 110-120K context (burst to ~240 on 82%-acceptance draft windows), cold 185,013-token read at 3,239.7 tok/s, expert-cache hit 97.6-98.0%, prompt reuse 99.6-99.9%, STALL reports 0 in 24 h.
  • The new part: a single-variable link matrix. Only the BIOS per-slot PCIe speed changed (Gen4 x16 vs Gen3 x16, same machine, same config, same salted prompts): layer-major (walk) prefill is +16.1% over chunk-major at 150K on Gen4 but net -1.7% on Gen3; decode is unchanged by either link or path.
  • How we made the walk engage at all (first time on this box after 47 engine starts with zero engage lines): --vram-reserve-later-mib 972 -> 1500 - the last card's reserve, not the global margin. Full audit and the correction this produced: [Engine] Layer-major prefill on a CUDA layer split: 32K prompts 2.0x faster, 0.47x drive reads (Qwen3.8-Flash-Next, v0.2.4.2) #3976 (our comments 6473012700, edited, and 6484683205).

Setup

Component Value
GPU 0 (helper, layers 0-21) RTX 4090 24 GB, PCIe 4.0 x16 CPU-attached (auto split K=22)
GPU 1 (main, layers 22-47 + draft head) RTX 4090 24 GB, PCIe 4.0 x16 CPU-attached
Link probe line engine reports CUDA0 link 26.4 GB/s / CUDA1 link 26.4 on Gen4, 13.2 / 13.2 on Gen3
CPU / RAM Threadripper 3970X (32C), 128 GiB
OS / driver Windows 11 ja-JP, 610.88
Model Qwen3.8-Flash-Next Unsloth UD-Q4_K_XL, --max-context 307200 --kv int8 --kv-resident 32768
Spec / split --spec 4 --mtp rt --pipeline-windows 2 --spec-min-p 0.70, --layer-split auto (K=22), --vram-reserve-mib 1728 --vram-reserve-later-mib 972
Env STRATA_ROUTE_PRIOR=1.0 STRATA_HC_Q8=1 STRATA_PIN_BY_NEED=1 STRATA_ARENA_PIN_GIB=24 STRATA_MTP_STAGE0=1 STRATA_PREFILL_LM_MARGIN_MIB=96

Production steady-state metrics (2026-10-28 08:00-22:30 JST window + 10-29 morning)

Metric Value
Cold fresh-prefix read 185,013 tok @ 3,239.7 tok/s (10-28); 154,478 tok @ 3,155.5 tok/s (10-29)
Reused-prefix resume 114,157 tok read 403 (1.8 s); 115,452 read 115 (1.1 s); reuse 99.6-99.9%
Decode @ 110-120K 122-127 tok/s typical; 157 burst (1,210 of 1,475 drafts accepted, 82%)
Expert-cache hit 97.6-98.0% (after ROUTE_PRIOR=1.0; 86.7% before)
KV stream VRAM hit 96.7-97.0%
GPU free, steady CUDA0 ~1,214 / CUDA1 ~1,143 MiB
Warnings one #2065-style STALL/day, 0 stalls overnight; 46-49 engine starts total in log

Link matrix (the actual benchmark here)

Method: fresh prompts (a GUID-text corpus plus a per-request salt, so 0 reused every time) at ~32K/66K/150K nominal tokens (actual usage 33,1xx / 66,5xx / 150,9xx), max_tokens=32, temperature 0, two iterations per cell; reported rate = the engine log line prompt N tokens = 0 reused + N read in X ms (Y tok/s). Values are the mean of the two iterations. (Our earlier #3976 table used the lower of the pair for the Gen3 rows; differences are <= 25 tok/s and the conclusions are identical.)

Link Path 32K 66K 150K decode512 window
Gen4 x16 chunk-major (later 972) 2,597 3,083 3,369 124.7
Gen4 x16 layer-major (later 1500) 2,428 (-6.5%) 3,391 (+10.0%) 3,913 (+16.1%) 126.7
Gen3 x16 chunk-major 2,028 2,429 2,720 129.5
Gen3 x16 layer-major 1,661 (-18.1%) 2,294 (-5.5%) 2,673 (-1.7%) 126.2

Arm order in one morning: Gen3 chunk 08:54, Gen3 walk 09:13, Gen4 walk 09:27, Gen4 chunk 09:34 (BIOS restored between arms; pcie.link.gen.current verified 4/P2/99% util under load). Read-only observations: chunk-major itself loses ~16-22% going Gen4 to Gen3; decode never moved (124.7-129.5); walk engage confirmed by layer-major (layers 0..22) + (layers 22..48) lines in the walk arms and no VRAM for a super-chunk (734 MiB free) declines in the chunk arms.

Tuning A/B notes behind the production config (what helped, what did not)

1. STRATA_ROUTE_PRIOR=1.0 - the biggest single win. Hints the next turn's expert placement from the previous turn's routing. At the default 0 the box streamed over PCIe constantly (hit 86.7%); A/B at identical load 0 -> 0.5 -> 1.0: expert-cache hit 86.7% -> 97.6% (65 min / 196 requests window), decode median 127 tok/s (peak ~240 on full-accept draft windows), and no visible quality difference comparing output at the 4,995-token position (branch-free conversation). Downsides: placement can lag right after a hard context switch (but the hit floor stays at the old value, so it cannot break below baseline); the arena fills with the previous conversation's experts, so alternating between conversations dilutes the win (observed 88-97% band).

2. STRATA_PREFILL_LM_MARGIN_MIB=96 - a knob I believed in that turned out to be a no-op here (corrected 2026-10-29). I first credited it with making the 185K cold read run at 3,239.7 tok/s. The log audit says otherwise: the walk never engaged in 47 engine starts - the primary evaluator passes at margin 96, but the last stage declined every prompt (no VRAM for a super-chunk (734 MiB free), floor ~1.1 GiB), so every prompt ran chunk-major and 3,239.7 is chunk-major. What actually engaged the walk was the last card's reserve, --vram-reserve-later-mib 972 -> 1500 (matrix above; audit in #3976 comments 6473012700, edited, and 6484683205). Production stays at later 972 - walk deliberately off until the engagement rule lands.

3. --conversation-cache-mib 12288 --conversation-cache-slots 4 - the agent-workflow fix. Budget for parked K/V snapshots. Prompt reuse 99.6-99.9%: 403 of 114,157 tokens read (1.8 s), 115 of 115,452 (1.1 s), and it survives an engine restart (re-parking finished inside the timeout). Downsides: a parked conversation costs GB-class (about 6.5 GiB at 30K tokens) so 12 GiB is tight; the 5th conversation starts evicting, and under extreme memory pressure there is a path where re-parking an evicted conversation is refused (Windows-side evidence added to the #2253 thread).

4. --kv-resident 32768 + --kv int8. KV-stream VRAM hit 96.7-97.0%; a 115K conversation reads only ~1.7-2.7 GiB from RAM. Downside: residency eats GPU free (steady ~1,220 / ~1,143 MiB here); pushing it further shows up first as #2065-style STALL reports - tune it together with the two --vram-reserve flags.

5. --spec 4 --pipeline-windows 2 --mtp rt --spec-min-p 0.70. Pipeline speculation across the two cards (card 2 drafts the next window while card 1 verifies). Decode 121.9-126.5 tok/s at 115K context; window acceptance 48/67, 41/42, 72/101. Downsides: draft-head VRAM and pipeline-specific edge cases - a draft can cross the page table within 7 tokens of the context ceiling (reported as #4413, never hit in practice); --adapt-async 1 is off by design under layer-split + arena (blocking tier).

6. STRATA_HC_Q8=1 / STRATA_PIN_BY_NEED=1 / STRATA_ARENA_PIN_GIB=24. HC_Q8 reads the hyper-connection projections as Q8 - quality side; under layer split the bf16 copy is not evacuated either (#2427 open), so it is a judgement call of about 0.63 GiB wasted per card vs quality (took quality). PIN_BY_NEED + ARENA_PIN_GIB VirtualLocks exactly the arena bytes streaming needs (24 GiB), so OS pressure cannot shred the streaming rate; the only downside is simply less pageable RAM.

What this adds (link to #3976's rule discussion)

Limits we want on the record

One machine, one morning, two iterations per cell (no variance reported). The walk arms ran --vram-reserve-later-mib 1500, which costs 528 MiB of expert-cache headroom on the last card - the walk wins despite that. 32K cells are overhead-dominated (a small calibration prompt reads ~666 tok/s), so the -6.5%/-18.1% at 32K partly reflects super-chunk amortization, not link alone. Production config keeps later 972 - walk stays off here by the current guard, which is the designed behavior until the rule in #3976 lands.

(AI-assisted posting again: the model served by this repo's engine ran the measurements and the log audit; all numbers are local engine-log lines. No engine changes, results only.)

Activity

  1. udqe commented on Oct 29, 2026

    @udqe
    Author

    Follow-up from the reporter (same machine, same config as the issue body): we ran the repo's own calibration procedure for --prefill-pipe on this rig, because MULTI_GPU.md lists exactly this GPU pair as a negative case ("a fixed 1024 chunk read ... 22-36% slower on 2x RTX 4090 (community report)") - we wanted to know if the measured arm ever wins here.

    Method = tools/calibrate.py measure_pipe as written, driven through the serve API instead of the python class: PIPE_VALUES = 0, 1, 768, 1536, 3072, 6144, fresh salted prompts at 2K/8K/32K per value, one pass, then the best value against the default in 3 interleaved alternating rounds, keep-rule "faster in every round and median gain > 3%". Per-request value via strata_tune: {"prefill_pipe": v}; read rate summed from the engine log's own prompt ... read in lines (same quantity as prompt_ms/prompt_read the script uses).

    Pass 1 (aggregate tok/s over the three prompt sizes):

    b/a value 0 (off) 1 768 1536 3072 6144
    tok/s 3,520.9 3,496.2 3,484.6 3,531.5 3,506.5 3,574.6

    Best = 6144 at +1.5%. Confirmation (3 interleaved rounds, off / 6144):

    round off 6144 gain
    1 3,547.6 3,581.3 +0.9%
    2 3,581.0 3,559.2 -0.6%
    3 3,576.4 3,576.7 +0.0%

    Median gain 0%, minimum below zero - the keep-rule fails and the answer for this rig is OFF, which is what production now runs. Two honest notes: (a) nothing we measured came close to the -22-36% the doc records for a fixed 1024 chunk on this GPU pair (our worst was -1.0%), so whatever the community rig's penalty driver was, the per-request sweep on ours stays inside noise; (b) an earlier two-iteration launch-argument A/B at value 1 looked like +7.4% at 8K here, and it did not reproduce under the calibrated protocol (-0.7% at the same value) - we are retroactively calling that early number noise, and mention it only because it shows how well the interleaved keep-rule filters exactly this.

    (AI-assisted as before: the model served by this repo's engine ran the sweep and the audit; numbers are local engine-log lines.)

  2. Niko1221 commented on Oct 29, 2026

    @Niko1221
    Owner

    Thank you for measuring this and writing it up. We are building a Strata website with a page for community benchmarks, and results like these will be added there when it launches, so we are closing this here. If your numbers change with a new version, you are welcome to post them again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions