Repository navigation
Conversation
…eline-windows (Niko1221#859) layer split: overlap the stages' hand-off (STRATA_SPLIT_OVERLAP=1, opt-in) Each verify window of a layer split runs its stages strictly in turn: stage 1 finishes, the host syncs its stream, then stages and launches stage 2's graph. Under HIP the sync and the graph launch both sit on the critical path of every window. With STRATA_SPLIT_OVERLAP=1 the hand-off carries a ready word (mapped, one per hand-off buffer): - the writing stage's graph ends with handoff_publish: the payload with volatile stores (RDNA keeps plain stores to mapped memory in L2), a system fence per thread, and the last block raises the flag; - the reading stage's graph starts with wait_flag_ge(flag, 1); - Verifier::run captures the next stage's window on this thread, then stages and launches it on a helper thread (prelaunch) while this thread serves its own layers; it then serves the next stage's layers directly and syncs the earlier stage once the chain is done; - release_gpu_waits also raises the hand-in flag; an early return after the prelaunch releases the next stage. Batch windows and --split-device 0 keep the serial order. Off by default. Measured on 2x RX 7900 XTX (ROCm, Linux), Swift 1.5 IQ3_XXS, 512-token greedy answers, median of 8: decode 53 -> 70.1 tok/s (code prompt), 45 -> 55.9 tok/s (prose prompt); greedy output byte-identical with the flag at 0 and 1; quiz 47/51 both ways. Not measured on NVIDIA. layer split overlap: bound the GPU-side hand-off wait (STRATA_SPLIT_WAIT_MS) Review on Niko1221#936 (justxiami): the reading stage's wait_flag_ge spun with no bound, so a lost publish would leave that stream spinning. - wait_handoff: the reading stage's graph now waits with a bound (default 30 s, STRATA_SPLIT_WAIT_MS, 0 = unbounded, GPU clock as in gpu_stamp). On timeout it sets a mapped error word and returns, so the graph completes; the host checks the word after the window and fails it with an error that names the setting, instead of using a stale hand-off. - The host's per-layer wait (20 s, Niko1221#267) still normally ends a stalled window first; release_gpu_waits already raises the hand-in flag. - Test hook STRATA_TEST_HANDOFF_DROP=N: the first stage's N-th window withholds its flag (a lost publish), to exercise both paths. Only active with STRATA_SPLIT_OVERLAP=1; the default path is unchanged. layer split overlap: the hand-off publish reads the test word only with STRATA_TEST_HANDOFF_DROP set Without the hook the window graph passes no word, so the publish kernel does no extra read of mapped memory per window. layer split overlap: off beside --pipeline-windows (Niko1221#859) --pipeline-windows runs its own overlapped decode loop with its own hand-offs, so STRATA_SPLIT_OVERLAP now switches itself off when it is set (one log line says so). Rebased onto main 1735d64: the early launch of the next stage keeps Niko1221#871's graph variant (the next stage's refresh_ar() runs before its capture and prelaunch, and not again in its run() when it was prelaunched), resets the plan error word like run()'s launch, and the chain path checks that word after its sync as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157fjixQD2nRxvCvtXq66DD
|
Another data point, from NVIDIA with two identical Ada cards: no change, and nothing broke. Rig: 2x RTX 4060 Ti 16 GB (sm_89), PCIe 4.0 x8 each, layer split Build and method: this PR ( The engine log of the B arms confirms the hand-off ran overlapped (
Every difference is within the run-to-run spread of the same arm, so the overlap makes no measurable difference here. That matches the 2x and 4x RTX 5080 reports: on these NVIDIA cards the host sync and graph launch between stages are already cheap compared with the window. No errors or stalls in any arm. I did not check byte-identical output: greedy runs already differ from run to run on this rig (GPU/CPU rounding), so a token compare would not tell the two arms apart. Measured on my machine, with an AI coding assistant helping on the scripts and the write-up. |
…-support Upstream now carries these of our picks, in its own versions: Niko1221#1237 (pinned stage buffers, now with fences and the STRATA_STAGE_PIN switch), Niko1221#1332, Niko1221#1359, Niko1221#1372, Niko1221#1370, Niko1221#1251, Niko1221#1190 and Niko1221#1283. CPU help for short prompts: Strata's own CPU prefill share (0.1.41: on by default below 1,024 tokens on one NVIDIA GPU; -20..-34% prompt time at 512-1,000 tokens on an RTX 3060 in upstream's measurement) replaces architectds' CPU assist, which measured +11% at ~560 tokens on our desktop. prefill.cpp is upstream's, with the pool's hooks (remote_next to the workers, a headless worker stage) and Niko1221#1368's quantize-once put back. A pool PC runs its own layers alone (no local stages), so the share's default applies on every PC. Pipelined decode: upstream's N-stage loop (front stages + the last) with the pool's remote stage as the last (pl_remote via stage_ver_p, pl_ns = 2 on a pool coordinator), and architectds' verified-path-first yield, the lookup carried across windows and the pool head's penalty rows ported onto it. Batch: upstream's batch_step (the adaptive tier in batch windows, per-stage timing, Niko1221#879 non-finite logits) with Niko1221#1290's per-slot row width and the pool head's slot histories; the pool's batch groups keep their PoolLink stage. Also: the conversation cache's eviction keeps both the pool-borrowed entry and pinned prefixes (evict_oldest); the verifier's adaptive-tier gate (STRATA_ADAPT_OVERLAP) also guards the overlapped split's prelaunch (Niko1221#1120); the server's read-once body takes upstream's size limit; replicas and the pool roles both start engines; serve/pool.py keeps the engine's newer spawn tuple; the worker's parking reads available_host_bytes(); setup tests that need a ready-made engine are skipped (StrataPool compiles its own). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157fjixQD2nRxvCvtXq66DD
b99e905 to
977b87a
Compare
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
…t-in) Each verify window of a layer split runs its stages strictly in turn: stage 1 finishes, the host syncs its stream, then stages and launches stage 2's graph. Under HIP the sync and the graph launch both sit on the critical path of every window. With STRATA_SPLIT_OVERLAP=1 the hand-off carries a ready word (mapped, one per hand-off buffer): - the writing stage's graph ends with handoff_publish: the payload with volatile stores (RDNA keeps plain stores to mapped memory in L2), a system fence per thread, and the last block raises the flag; - the reading stage's graph starts with wait_flag_ge(flag, 1); - Verifier::run captures the next stage's window on this thread, then stages and launches it on a helper thread (prelaunch) while this thread serves its own layers; it then serves the next stage's layers directly and syncs the earlier stage once the chain is done; - release_gpu_waits also raises the hand-in flag; an early return after the prelaunch releases the next stage. Batch windows and --split-device 0 keep the serial order. Off by default. Measured on 2x RX 7900 XTX (ROCm, Linux), Swift 1.5 IQ3_XXS, 512-token greedy answers, median of 8: decode 53 -> 70.1 tok/s (code prompt), 45 -> 55.9 tok/s (prose prompt); greedy output byte-identical with the flag at 0 and 1; quiz 47/51 both ways. Not measured on NVIDIA. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…AIT_MS) Review on Niko1221#936 (justxiami): the reading stage's wait_flag_ge spun with no bound, so a lost publish would leave that stream spinning. - wait_handoff: the reading stage's graph now waits with a bound (default 30 s, STRATA_SPLIT_WAIT_MS, 0 = unbounded, GPU clock as in gpu_stamp). On timeout it sets a mapped error word and returns, so the graph completes; the host checks the word after the window and fails it with an error that names the setting, instead of using a stale hand-off. - The host's per-layer wait (20 s, Niko1221#267) still normally ends a stalled window first; release_gpu_waits already raises the hand-in flag. - Test hook STRATA_TEST_HANDOFF_DROP=N: the first stage's N-th window withholds its flag (a lost publish), to exercise both paths. Only active with STRATA_SPLIT_OVERLAP=1; the default path is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…th STRATA_TEST_HANDOFF_DROP set Without the hook the window graph passes no word, so the publish kernel does no extra read of mapped memory per window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--pipeline-windows runs its own overlapped decode loop with its own hand-offs, so STRATA_SPLIT_OVERLAP now switches itself off when it is set (one log line says so). Rebased onto main 1735d64: the early launch of the next stage keeps Niko1221#871's graph variant (the next stage's refresh_ar() runs before its capture and prelaunch, and not again in its run() when it was prelaunched), resets the plan error word like run()'s launch, and the chain path checks that word after its sync as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
977b87a to
df7ed7f
Compare
|
Rebased onto 0.1.44.1 (296b59d). Only the first commit changed: upstream's What it changes and leaves alone: STRATA_SPLIT_OVERLAP=1 is still opt-in and the Measured on 2x RX 7900 XTX (gfx1100, ROCm 7.15, Linux), Ryzen 9 9900X, Swift 1.5
Not tested: CUDA (the HIP build only; the CUDA branch of the launch block was merged |
|
The default path looks untouched (no env var, no hand-off flags). One build issue, from reading (no Mac here): |
|
Thanks, and thanks to the people who tested it on other cards. Ported with your authorship; one conflict was ours to resolve (0.1.44 moved the graph launch into a HostGraphLaunch object, so the early-launched window keeps no launcher). On 2x RTX A4000 the layer split with the overlap on is neutral (x1.001 over 5 pairs) and the answers are bit-identical (4/4 slots, exact settings); the default path is unchanged (identity gate). We have no RDNA3 two-card box, so the gain you measured on 2x 7900 XTX is not reproduced here; it stays opt-in and the docs name it for RDNA3. |
Upstream 0.1.45 took in ten of the PRs we carried: Niko1221#567 (prompt encoder), Niko1221#1164 (sibling borrow), Niko1221#1166, Niko1221#1368, Niko1221#1367, Niko1221#1120 (split overlap), Niko1221#1912, Niko1221#1888, Niko1221#1949, Niko1221#1305. Their versions are kept and our copies dropped: verify.cpp's duplicate prelaunch/handoff block, verify_kernels.hpp, the qsa prompt kernels, gr_upmix_parity, the tokenizer and its tests (tools/test_strata_tokenizer_upstream.py is gone: upstream's test file is the one now), the second quantize_scatter declaration. README credits updated. Pool parts kept on top: - conversation cache: upstream renamed the borrow guard (pin -> guard); clear() resets it. - prefill: the remote_next hand-off beside upstream's hand-off pieces/worker (a pool stage has no pieces: the whole chunk goes to the workers). - generate.cpp: --pool-affinity measure over a node's own layers; the node's dense-range load beside STRATA_HC_REQ8; the carve on primary_device over the pool range; checkpoints keep pool_id (STRATA_CKPT_POOL/THREADS' parallel save is skipped on a pool PC); the workers' retain after eviction and after a resume; the borrow/restore of parked conversations on the workers with upstream's --conversation-cache-park-gain; Niko1221#652's emit count in upstream's merged windows. - expert_source: our budget retry beside upstream's lend budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157fjixQD2nRxvCvtXq66DD
|
Thanks both. Ran the interleaved comparison Niko asked for in #936, on v0.1.45 (main, 477ac13), 2× RX 7900 XTX (gfx1100, ROCm 10.1), Swift 1.5 IQ3_XXS, layer split auto. Method: 10 rounds, four arms, run order rotated every round so each arm is first/second/third/last equally often, 200 tokens, 3 runs per cell, median per round, then median across rounds.
So Mac/Metal link failure (@qiuzan2001): a small separate PR is coming with stubs for |
|
@qiuzan2001 , can you please try #3002 for the stubs. Thanks |
|
Thanks for the pull request. Your change is already in If part of your work did not make it in and you want it considered, open a new pull request on top of |
Resubmitted from #936, which GitHub closed when main's history was rewritten. Same 4 commits, cherry-picked onto the new main (no conflicts). The review discussion and test results from others are in #936.
On 2x RX 7900 XTX the layer split decoded slower than one card. The cause is the order inside every verify window: stage 1 finishes, the host syncs its stream, then stages and launches stage 2's graph. Under HIP both the sync and the graph launch are expensive, and they sit on the critical path of every window while both cards are idle.
With STRATA_SPLIT_OVERLAP=1 (off by default; stages on different devices only):
handoff_publish: the payload with volatile stores (RDNA keeps plain stores to mapped memory in L2, same reason as doorbell_ring_kernel), a system fence, and the last block raises the flag;Verifier::runlaunches the next stage's window on a helper thread while it serves its own layers, and syncs the earlier stage only once the chain is done;release_gpu_waitsalso raises the hand-in flag, and an early return after the early launch releases the next stage (Windows: the verify-window stall wedges the GPU into an unrecoverable "device lost" state (manual power cycle required), with no TDR logged #267).Changes since #936 (from the review there):
Measured on 2x RX 7900 XTX (gfx1100, ROCm 10.0.0, Linux), Ryzen 9 9900X, 52 GB RAM, Swift 1.5 IQ3_XXS, 128K context, 8-bit KV, split 0-25 / 26-47, 512-token greedy answers: