Skip to content

adaptive tier: --adapt-async 1 in the arena mode (one GPU, copy-in only), x1.07-1.11 cold decode on an RTX 3090 + Ryzen 5 5600X - #1904

Closed
RubenTorney wants to merge 1 commit into
Niko1221:mainfrom
RubenTorney:pr/async-adapt-arena
Closed

RubenTorney wants to merge 1 commit into
Niko1221:mainfrom
RubenTorney:pr/async-adapt-arena

Conversation

@RubenTorney

Copy link
Copy Markdown
Contributor

Title

Issue: #1903

Summary

In the arena mode the blocking adaptive tier creates and joins a thread every verify window and the next window waits for its
copies: ~3.2 of 25.9 ms per window on this machine (the interval #1863 found outside the timing breakdown). This lets
--adapt-async 1 run in the arena too. The arena holds every expert in RAM, so a round is a copy-in only: no copy back, no RAM
move, no exchange buffers. Opt-in; without --adapt-async 1 (and in the resident RAM mode, and on several GPUs) nothing changes.

Cold A/B against the v0.1.42 tag (12 distinct prompts x 2 rounds, fresh server per arm and round, paired): x1.07, faster on
12 of 12 prompts
(97.2 → 107.7 tok/s; x1.11 in an earlier run the same day, when the learned profile was younger and the tier
swapped more). One cold window: 25.9 → 21.6 ms, adapt wait 0.41 ms, join 0.

What changed

  • src/program/generate.cpp
    • The --adapt-async gates accept the arena (srcp == &arena_src) on one GPU; a layer split stays on the blocking tier.
    • The asynchronous tier's state machine, arena case: exchange = false, the source blob is ArenaExpertSource::blob_at(),
      the RAM-move steps are skipped. The evicted slot is marked non-resident and res_upload runs before the round's copies are
      posted; an expert becomes resident only once its copy's event has completed.
    • STRATA_ADAPT_ASYNC_WAIT=1 (default in the arena): the tick after a round waits for that round, so the window in which an
      expert turns GPU-resident does not depend on timing. STRATA_ADAPT_PICK_INLINE=1 (default in the arena): the pick runs on
      the host thread, one round per window. STRATA_ADAPT_ASYNC_BATCH=N caps the copies posted per tick (default 0 = all;
      16 measured 16 % slower here). Arena rounds are as large as the blocking tier's (--adapt-swaps, not capped at 96).
    • Fences: every K/V grow (kvg_ensure's quiesce lambdas), kv_quiesce and the VRAM command now land the round in flight
      (adapt_quiesce) — they only called apply_pending(true), so a swap still copying could write a slot just lent to the K/V.
      This also applies to the resident mode's asynchronous tier. A failed round still drains its copies before slots move.
    • STRATA_DECODE_TIMING=1: the serial --serve line adds adapt wait X join Y, Z swaps/window (part of fix: Complete the synchronous decode timing breakdown #1863).
    • --adapt-async help text.
  • src/core/expert_source.cpp, include/strata/core/expert_source.hpp: ArenaExpertSource::blob_at(), blob() without the
    read counter (it runs on the job thread).
  • docs/DETAILS.md: the arena paragraph next to --adapt-async 1.
  • bench/results/2026-10-11-community-rtx3090-5600x-arena-async/: method, numbers, raw data (sha256 of every answer), harness.

How the default path was checked

  • Bit-exact mode (STRATA_IQ_MT_MIN=1 --pcie-frac 0 --adapt-every 0), greedy, 6 prompts, two fresh v0.1.42 engines and one of this branch:
    on the 5 prompts that v0.1.42 reproduces between two of its own fresh engines, v0.1.42 and this branch without
    --adapt-async give the same answers byte for byte (sha256), with --expert-cache auto and with a fixed size. The 6th
    prompt differs between two runs of v0.1.42 itself in that mode on this machine.
  • Built: CUDA only (Windows, VS 2022 17.14, CUDA 12.8, sm_86). Not built: HIP, SYCL (no such hardware here); the change is
    host code in generate.cpp / expert_source.cpp, no backend-specific path.

Extra Notes

  • Like the resident mode's asynchronous tier, answers are not bit-exact from run to run with the option on (which window first
    computes a swapped-in expert on the GPU). On this machine greedy answers already differ between two fresh engines of the
    unpatched build (the CPU/GPU split of the misses depends on timing).
  • After a long prompt the gain shrinks: the prompt itself has moved its experts into VRAM (hit rate 97-99 %), the tier has
    little left to swap: level with v0.1.42 at 56K and 117K tokens (95.3 / 97.3 and 97.0 / 96.1 tok/s).
  • Robustness (full test branch: this change plus other opt-in switches): 63K-token prompt then 4 follow-up turns, K/V grown
    to 65,536 cells during the conversation, no ERR.
  • Reviewed for fence correctness before the A/B (VRAM command after STOP, K/V grow, request start drain, error path).

🤖 Generated with Claude Code

https://claude.ai/code/session_015oFHMEgBzk7kQSqUVWoc67

In the arena mode the blocking adaptive tier creates and joins a thread every verify window and the next window
waits for its copies (Niko1221#1863: the time outside the decode timing breakdown). --adapt-async 1 now also runs with the
arena on one GPU, not only in the resident RAM mode. The arena holds every expert, so a round is a copy-in only:
no copy back, no RAM move, no exchange buffers.

- gates for the arena; ArenaExpertSource::blob_at() reads a blob from the job thread without the read counter
- the evicted slot is marked non-resident and uploaded before the round's copies are posted; an expert becomes
  resident only once its copy's event has completed
- STRATA_ADAPT_ASYNC_WAIT=1 (arena default): the next tick waits for the round, so the window an expert turns
  GPU-resident does not depend on timing; STRATA_ADAPT_PICK_INLINE=1 (arena default): one round per window;
  STRATA_ADAPT_ASYNC_BATCH=N caps the copies per tick (default 0 = all); arena rounds are not capped at 96
- adapt_quiesce(): every K/V grow, kv_quiesce and the VRAM command land the round in flight (they only called
  apply_pending(true)); a failed round still drains its copies. This also covers the resident mode's async tier.
- STRATA_DECODE_TIMING: "adapt wait X join Y, Z swaps/window"; --adapt-async help text

RTX 3090 + Ryzen 5 5600X, cold prompts: x1.07-1.11 decode, faster on 12 of 12 prompts; 25.9 -> 21.6 ms per window
(bench/results/2026-10-11-community-rtx3090-5600x-arena-async, docs/DETAILS.md).

Opt-in (--adapt-async 1, default 0); the default path is that of v0.1.42. With the tier on, answers are not
bit-exact run to run (which window first computes a swapped-in expert on the GPU).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015oFHMEgBzk7kQSqUVWoc67
fragtion pushed a commit to fragtion/StrataPool that referenced this pull request Oct 12, 2026
…ode (one GPU, copy-in only, opt-in)

In the arena mode the blocking adaptive tier creates and joins a thread every verify window and the next window
waits for its copies (Niko1221#1863: the time outside the decode timing breakdown). --adapt-async 1 now also runs with the
arena on one GPU, not only in the resident RAM mode. The arena holds every expert, so a round is a copy-in only:
no copy back, no RAM move, no exchange buffers.

- gates for the arena; ArenaExpertSource::blob_at() reads a blob from the job thread without the read counter
- the evicted slot is marked non-resident and uploaded before the round's copies are posted; an expert becomes
  resident only once its copy's event has completed
- STRATA_ADAPT_ASYNC_WAIT=1 (arena default): the next tick waits for the round, so the window an expert turns
  GPU-resident does not depend on timing; STRATA_ADAPT_PICK_INLINE=1 (arena default): one round per window;
  STRATA_ADAPT_ASYNC_BATCH=N caps the copies per tick (default 0 = all); arena rounds are not capped at 96
- adapt_quiesce(): every K/V grow, kv_quiesce and the VRAM command land the round in flight (they only called
  apply_pending(true)); a failed round still drains its copies. This also covers the resident mode's async tier.
- STRATA_DECODE_TIMING: "adapt wait X join Y, Z swaps/window"; --adapt-async help text

RTX 3090 + Ryzen 5 5600X, cold prompts: x1.07-1.11 decode, faster on 12 of 12 prompts; 25.9 -> 21.6 ms per window
(bench/results/2026-10-11-community-rtx3090-5600x-arena-async, docs/DETAILS.md).

Opt-in (--adapt-async 1, default 0); the default path is that of v0.1.42. With the tier on, answers are not
bit-exact run to run (which window first computes a swapped-in expert on the GPU).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157fjixQD2nRxvCvtXq66DD
Niko1221 added a commit that referenced this pull request Oct 13, 2026
Niko1221 added a commit that referenced this pull request Oct 13, 2026
@Niko1221

Copy link
Copy Markdown
Owner

Thanks for this, and for the report: the method (fresh server per arm, distinct prompts, paired, order reversed) is the one we use ourselves, and the fence fix in kvg_ensure / kv_quiesce / the VRAM command is a real fix that also covers the resident-RAM async tier.

We ported it to the 0.1.44 branch with your authorship kept, as an opt-in (--adapt-async 1), with the default path unchanged. What we measured (RTX 5070 12 GB, Windows, Q2_0 arena, 5 interleaved pairs, three batches): decode +4.2 to +5.2% (13 of 15 pairs faster), with the gain on code and document prompts (+7 to +10%) and the story prompt level; on an R9700 (HIP, 20K cache slots) the tier is level except on a cold prompt (+12.9%), which matches your note that the gain follows how much the tier swaps. Default-path gates are byte-identical (10 prompts + 4K/20K residual dumps on CUDA sm_86 in the arena and in the resident mode, and on HIP), and it builds on CUDA, HIP, SYCL and Metal.

One thing we did on top, in the combination with your other PR: blob_at has to follow the padded layout of STRATA_PIN_BY_NEED, otherwise the async tier would copy the wrong blob. Answers with the tier on differ from the blocking tier (deterministic run to run, but not equal to it), so it stays opt-in until we have a decode KL with the tier running; we did not manage a meaningful one yet. It will be in the 0.1.44 notes as opt-in, with your name.

@Niko1221 Niko1221 closed this Oct 13, 2026
fragtion added a commit to fragtion/StrataPool that referenced this pull request Oct 13, 2026
Upstream 0.1.44: Apple Silicon (Metal), Qwen3.6 family, NVFP4 experts, K-quant pool rows
(libstrata_kq), an image generation tab, prefill head alias, pin by need (Niko1221#1905) and
--adapt-async in the arena (Niko1221#1904, which we carried: upstream's copy kept, ours dropped).

Conflicts:
- Niko1221#1822 (gate/up MMQ reading experts in place, -0.7% read time) dropped: it patched
  llama.cpp's mmq.cuh and does not combine with NVFP4's tails and w4a4 activations.
  moe_mmq.{hpp,cu} and the group gather in prefill.cpp are upstream's.
- Niko1221#1368 kept: quantize_scatter for q8_1 activations, the per-row quantizer for NVFP4 w4a4.
- expert_source: load_experts_gguf takes the pool range and pin by need's PadMap; pin by
  need only for a whole pack (a pool range keeps the packed, biased layout); generate.cpp
  turns it off on a pool PC.
- verify.cpp: upstream's HostGraphLaunch inside our Niko1221#1120 prelaunch guard (an optional
  launcher: a prelaunched window has none).
- iq_kernels: our IQ1_S (19) beside NVFP4 (40) and the Qwen3.6 downs.
- generate.cpp: tail skip keeps from_coord and own layers beside NVFP4's default; batch
  graph limit is upstream's 64 for --batch-mtp and the pool worker's wide rows.
- server.py / web: health has image_generation and pool_role; images and pool routes; tabs
  chat, images, monitor, pool, about.
- the --prefill-help help text's "+~30% on" was a printf conversion (%%).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157fjixQD2nRxvCvtXq66DD
giridhart pushed a commit to giridhart/Strata that referenced this pull request Oct 15, 2026
…rena and Niko1221#1905 pin by need, as opt-ins) into f-144-rel
giridhart pushed a commit to giridhart/Strata that referenced this pull request Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants