Skip to content

arena: pin by need (STRATA_PIN_BY_NEED=1) and a PCIe share floor (STRATA_PCIE_MIN1=1), +5-7 % prompt read on Windows - #1905

Closed
RubenTorney wants to merge 1 commit into
Niko1221:mainfrom
RubenTorney:pr/pin-by-need
Closed

RubenTorney wants to merge 1 commit into
Niko1221:mainfrom
RubenTorney:pr/pin-by-need

Conversation

@RubenTorney

Copy link
Copy Markdown
Contributor

Title

Issue: #1903

Summary

On Windows cudaHostRegister of the whole arena fails (WDDM shared budget, about half the RAM) and the sliced fallback
registers the first ~30 GiB, layers 0-31 for Qwen3.8-Flash-Next IQ3_S: layers 32-47 never get a PCIe share, while ~10 GiB of the
registered prefix hold GPU-resident experts that DMA never reads. With STRATA_PIN_BY_NEED=1 the arena gives every blob a
page-aligned stride (+0.15 % RAM) and registers the expert profile's non-GPU-resident experts by rank, up to
STRATA_ARENA_PIN_GIB (29 GiB by default on Windows, to leave the shared segment some room). STRATA_PCIE_MIN1=1 sends one
miss over PCIe as soon as a layer has two (the share (nmiss * pcie_num) >> 8 rounds to 0 at 1-2 misses). Both opt-in.

RTX 3090 + Ryzen 5 5600X: 28.96 GiB registered = 92 % of the non-resident experts, all 48 layers get a PCIe share.
Prompt read +5-7 % at 56K-117K tokens (2,172 → 2,313 and 2,091 → 2,208 tok/s). Decode: level on the blocking tier
(x0.999 cold, 6/12), x1.02-1.03 (10 of 12 prompts faster) on top of --adapt-async 1 (the other PR) with --pcie-frac 0.6.

What changed

  • src/core/pinned.cu, include/strata/core/pinned.hpp: a PinnedArena constructor that registers a list of byte ranges
    in order before any page is touched, stops at the cap or the first refusal, and unregisters them in the destructor;
    PadMap, the per-blob placement used by the loaders (load_experts_ranges, load_experts_gguf) when the layout is padded.
    STRATA_ARENA_PIN_GIB=auto applies to this path too.
  • src/platform/memory.cpp, .hpp: lock_resident_ranges keeps the holes between the ranges resident (one working-set raise).
  • src/core/expert_source.cpp, .hpp (ArenaExpertSource): arena_off() (one offset function for blob, blob_at,
    pinned, device_alias, the ranges); set_pin_priority; pinned() / device_alias() / pcie_layer() by ranges (a blob is
    pinned only if it lies wholly inside one registered range); STRATA_PCIE_MIN1 in the PCIe share.
  • src/program/generate.cpp: before the arena is loaded, the profile's pairs past the estimated GPU-resident count (from
    --expert-cache or the free VRAM, as the cache's auto computes it) are ranked into ranges; one log line
    pin by need: N ranges, X GiB pinned, Y % of the non-resident experts.
  • docs/DETAILS.md, bench/results/2026-10-11-community-rtx3090-5600x-arena-pin-by-need/.

How the default path was checked

  • Without STRATA_PIN_BY_NEED the layout, the loaders and the sliced pin are untouched. Bit-exact mode
    (STRATA_IQ_MT_MIN=1 --pcie-frac 0 --adapt-every 0), greedy, 6 prompts: on the 5 prompts that v0.1.42 reproduces between
    two of its own fresh engines, this branch without the variables gives the same answers byte for byte (sha256). 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; pinned.cu and expert_source.cpp
    are shared with those builds (the new code is host code behind the variable).

Extra Notes

  • The padded layout reads experts.bin through the buffered loader (a contiguous unbuffered read cannot land strided); load
    speed was unchanged here (6.7 GiB/s).
  • STRATA_VERIFY_ARENA's checksum is over the padded layout when the variable is on (not comparable with the packed one).
  • The resident count is estimated before the cache exists; an error costs DMA-registered bytes on resident experts or skips a
    few hot ones, never correctness (STRATA_PIN_BY_NEED_SKIP=N sets it).
  • Independent of the other PR (no shared code); the decode gain shows only with it.

🤖 Generated with Claude Code

https://claude.ai/code/session_015oFHMEgBzk7kQSqUVWoc67

…TRATA_PCIE_MIN1, opt-in)

On Windows cudaHostRegister of the whole arena fails (the WDDM shared budget is about half the RAM) and the
sliced fallback registers the first ~30 GiB: with Qwen3.8-Flash-Next IQ3_S layers 0-31, so layers 32-47 never
get a PCIe share, while part of the registered prefix holds GPU-resident experts that DMA never reads.

STRATA_PIN_BY_NEED=1 (the arena on one GPU):
- every blob gets a page-aligned stride (PadMap in pinned.hpp, ArenaExpertSource::arena_off, +0.15 % RAM), so any
  set of blobs can be registered exactly; the loaders place each blob at its padded offset
- the expert profile's pairs past the estimated GPU-resident count are merged into ranges by rank and registered
  before any page is touched, up to STRATA_ARENA_PIN_GIB (29 GiB by default on Windows) or the first refusal
- pinned() / device_alias() / pcie_layer() work on the ranges (a blob is pinned only if wholly inside one);
  lock_resident_ranges() keeps the holes between them resident
- one start-up line: "pin by need: N ranges, X GiB pinned, Y % of the non-resident experts"

STRATA_PCIE_MIN1=1: the PCIe share (nmiss * pcie_num) >> 8 rounds to 0 at 1-2 misses per layer; with two or more
misses one goes over PCIe (when its blob is pinned).

RTX 3090 + Ryzen 5 5600X: 28.96 GiB registered = 92 % of the non-resident experts, all 48 layers with a PCIe
share; prompt read +5-7 % at 56K-117K tokens, decode level alone and +2-3 % with --adapt-async 1 and
--pcie-frac 0.6 (bench/results/2026-10-11-community-rtx3090-5600x-arena-pin-by-need, docs/DETAILS.md).

Both opt-in; without them the layout, the loaders and the sliced pin are those of v0.1.42.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015oFHMEgBzk7kQSqUVWoc67
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, this is a good diagnosis of the WDDM budget problem (prefix pinning that spends the budget on layers whose experts the GPU already holds).

We ported it to the 0.1.44 branch with your authorship kept, as an opt-in, with a few changes of ours (separate commits):

  • On a 12 GB RTX 5070 / Windows / Q2_0 (arena 31.6 GiB, the default sliced pin already covers 30 GiB of it) the version as submitted made the prompt read 9.5% slower (0 of 5 pairs): by the time of the prompt the adaptive tier has changed the cache, so a tenth of what the prompt streams was not pinned. We now put the pairs taken as GPU-resident into a second tier of ranges that only gets what the cap leaves over. Result: prompt read +4.4% (5 of 5 pairs), decode +0.9%; with the pin budget capped at 16 GiB the prompt read is +8.5% (5 of 5).
  • blob_at (from the other PR) follows the padded layout; no padded layout with --shared-expert-arena.
  • load_experts_ranges with the PadMap is an overload instead of a default argument: the SYCL build has its own pinned.dp.cpp and failed to link with the extra parameter.
    STRATA_PCIE_MIN1 did not help on our card (async + pin + MIN1 was slower than async alone), so it stays a separate switch. Gates: answers identical with the switches on and off (pcie-frac 0, 10 prompts + 4K/20K on CUDA sm_86, HIP gfx1201, and the 5070), builds on CUDA, HIP, SYCL, Metal. It is CUDA/HIP only and aimed at Windows, so it stays opt-in; we will name it in the 0.1.44 notes 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
DarthCY-K pushed a commit to DarthCY-K/Strata that referenced this pull request Oct 20, 2026
…k (default single-GPU path unchanged)

From two read-only audits of rel/0.1.44 (port-044/LOG.md, "3b"):
- the PCIe share's pick under STRATA_ADAPT_FETCH takes only pinned misses: upstream's Niko1221#1905 STRATA_PIN_BY_NEED pins a
  PCIe layer in part, and an unpinned pick was skipped later, leaving its share slot unused (prefix pinning, the
  default, pins a PCIe layer whole: the same picks);
- the arena keeps loading on its own thread when STRATA_PIN_BY_NEED is set without --expert-profile (pin by need
  turns itself off then);
- upstream's --prefill-head-alias (opt-in, one GPU) turns the elastic K/V off: it lends from slot 1, so the K/V's
  start failed and mapped the whole window from VRAM the cache had been sized with (an exit at start on a full card);
- a session file's identity names the K/V rotation the session runs (this fork rotates int8 K/V by default,
  STRATA_KV_ROT=0 does not); upstream's reading of STRATA_KV_ROT=1 gave both the same identity, so a disk-parked
  conversation could come back in the wrong basis after the variable changed;
- Niko1221#2090's earlier loan start never reaches the slots the grown elastic K/V holds ([kvg.lo, kvg.top)): the chunk is
  halved until its loan starts at kvg.top (a small card whose auto chunk is near small_max, after a long
  conversation; on the 5090 the auto chunk's loan is the largest and starts at kvg.top);
- the VMM stubs of the builds without CUDA VMM (HIP, SYCL, Metal) take the device argument vmm.hpp passes since
  26ca4526 (an undefined reference there).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio
DarthCY-K pushed a commit to DarthCY-K/Strata that referenced this pull request Oct 20, 2026
…d allocation is refused, so the PCIe share works

FileExpertSource::pin_cache_complement with `pin`: when cudaHostAlloc of the whole copy was refused (a Q8_0 pack's
100 GiB copy on a 128 GB PC), the copy was malloc'd and only locked in the working set: pinned() was false for every
expert, pcie_layer() false, so the PCIe share (pcie_frac 0.30 logged) fetched nothing and every miss went to the CPU.
Now three paths, each logged ("page-locking path 1/2/3 ... X of Y GiB pinned"):
- path 1: the one cudaHostAlloc, as before (byte-identical).
- path 2: refused (or no device alias) -> the copy is VirtualAlloc'd (posix_memalign elsewhere) and registered with
  cudaHostRegister(Portable|Mapped) slice by slice before anything writes it (the WDDM rule), slices ~ one layer of
  the pack cut at expert starts (page-aligned where possible), each with its own device alias; stops at the first
  refusal. pinned()/device_alias() look the slice up (an expert in no slice whole is the CPU's); pcie_layer() true.
  By default only up to the GPU adapter's shared-memory budget less what the process uses there and the margin
  (shared_pin_limit_for, extras/wddm-pin's per-adapter helper; STRATA_PIN_MARGIN_GIB, 4 GiB): pinned until the driver
  refused (99.61 GiB + 1.3 of a 101.0 GiB budget) a 32K first prompt took 18.7 / 20.5 s against 10.9 / 10.3
  unpinned; at the cap (94.63 GiB) 9.2 / 8.5 s. STRATA_COMPLEMENT_PIN_GIB: "all" until the driver refuses, N GiB,
  0 = path 3.
- path 3: no slice pinned -> pageable + locked, as before. The STRATA_PARTIAL_PIN=1 budget prefix keeps its own path;
  STRATA_EXCHANGE_ROTATE stays off with slices (it needs one alias).
RTX 5090, 128 GB, Q8_0 pack --low-ram (0.1.42 measurement), interleaved 3+3: decode (4 chats) 45.9 / 47.5 / 47.4 ->
56.0 / 52.4 / 53.4 tok/s, PCIe 9-12% of the routed experts, a 1238-token prompt 5.0 -> 2.55 s, start 37 -> 29 s.
Teacher-forced KL against the release equals the placement noise; the copy's checksum is the release's; with
STRATA_COMPLEMENT_PIN_GIB=0 the logits are the release's bit for bit. GPTQ pack (path 1) --low-ram: deterministic
greedy tokens identical. G2b (0.1.43.1): the slower state some path-2 runs showed is a full card (0-173 MiB free
at serve start, WDDM paging) - the next commit keeps VRAM free in the low-RAM mode.

Port to rel/0.1.44 (from fix/lowram-slice-pin-043 6a41b80d): pinned.cu / pinned.hpp are rel/0.1.44's as they are -
extras/wddm-pin already made shared_pin_limit public and added shared_pin_limit_for(dev); the slices' cap uses the
latter for the device that reads the copy, and on a layer split (the portable slices are mapped into every context,
WDDM charges every adapter) the smallest room of all devices. Upstream's STRATA_PIN_BY_NEED (Niko1221#1905) pins the expert
arena; this pins the --mmap-experts RAM copy (the low-RAM mode), whose order is already hottest first by the
profile - the two never run together (the low-RAM mode has no arena); STRATA_PIN_BY_NEED with --mmap-experts now
says it is off instead of being ignored silently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio
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