Repository navigation
multi-GPU: second GPU as an opt-in expert-cache tier (--peer-device), resubmit of #229 on v0.1.36 - #531
multi-GPU: second GPU as an opt-in expert-cache tier (--peer-device), resubmit of #229 on v0.1.36#531q8atnight wants to merge 3 commits into
Conversation
A second visible CUDA GPU (N >= 1) holds an adaptive expert cache beside CUDA0's, filled with the ranked pairs the primary does not hold. Activations and result rows cross NVLink or another P2P path; the peer also computes its experts' rows for prompt chunks (MMQ; the fused path is not combined yet). Knobs: --peer-device N, --peer-reserve-mib, --peer-slots, --peer-adapt-swaps, --peer-prefill-rows; sizing stays manual until Niko1221#216's buffer lending lands. Default output unchanged: without the flag byte-identical to v0.1.36, with it byte-identical under the exactness gate. cudaHostAllocPortable only with a peer. docs/SECOND_GPU.md documents the flags and refused combinations. Measured on 2x RTX 3090 + NVLink, IQ3_S: prefill +15/+25/+25 %, decode +19/+14/+16 %.
…device1..3 (both would use the same second card); --help lines for the --peer-* flags
…peer declines the prompt path (#531 follow-up) fused_ring() turns the fused ring sizing off for any --peer-device session (core::peer_portable), but the layer's fused choice tested the peer's prompt-path state (m.pp), which is null when set_peer declines (no P2P, the buffers do not fit, --peer-prefill-rows 0). Both now follow the flag, so a peer session's prompt path is MMQ with the MMQ ring and buffers it was sized for. Without --peer-device nothing changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0154bwiad8gY7wRehCXy27pN
peer_experts.cpp calls cudaDeviceCanAccessPeer, cudaDeviceEnablePeerAccess and cudaMemcpyPeerAsync and tests cudaErrorPeerAccessAlreadyEnabled; the HIP shim had none of them, so the Windows HIP build stopped there. Mapped to their hip* names. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0154bwiad8gY7wRehCXy27pN
…l's expert count (#531 follow-up) #531's peer-aware lookup indexed the residency table with the 512-expert constant NE instead of the model's own count. The Coder has 256 experts per layer, so from its second layer a resident expert was looked up in the wrong row, and a prompt long enough for the streamed walk stopped with "an expert is neither resident, streamed nor on the peer" (found by the 57K real-use check). The 512-expert models index the same row as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0154bwiad8gY7wRehCXy27pN
|
Released in 0.1.38 as an opt-in ( |
Upstream v0.1.38: faster prompts (one gather per expert group Niko1221#372, the first chunk's PLE rows beside layer 0 Niko1221#374, DeltaNet three heads per thread Niko1221#413), --kv q4_0 prompts on tensor cores Niko1221#452, Q5_0 experts on the GPU Niko1221#473, IQ4_XS on AVX-2 Niko1221#415, unbuffered expert loading on Windows Niko1221#357/Niko1221#362, the --peer-device second-GPU expert tier Niko1221#531, a 6 GB card starts Niko1221#496, the Host header / DNS-rebinding checks, PR batch. Our side: the AVX1 floor (STRATA_ISA_FLOOR, cpu_avx1_ok, the relaxed gate, the scalar router fallback), the AVX1 router kernel (kq_avx1.cpp), the retune patch + setup hooks.
|
Thanks — great to see it in 0.1.38, and the three follow-ups are the right calls. The We rebased our own tree onto 0.1.38 tonight (our peer-tier superset kept, your follow-ups taken): the 0.1.38 single-GPU path still matches our 0.1.36 gate reference bit for bit on the 3090s, and the dual path passes the same gate. Heads-up on the follow-up we have queued: the prompt path can split every GDN layer's heads across the two GPUs on top of the peer tier (opt-in |
|
I'm sure this is a very stupid question, but how do we disable Edit: the fix appeared within a minute of my comment. That is some fast work! :) |
With several GPUs in the config's "gpu" the server appended --layer-split auto, which the engine refuses beside --peer-device (Niko1221#531), so the peer tier could not be started through the server at all: strata generate: --peer-device cannot be combined with --layer-split (use one or the other) The split (and --split-skip-if-fits) is now added only when --peer-device is not among the args; the start line names the peer tier instead of a layer split. Tests in serve/test_server.py (PeerDevice), docs/SECOND_GPU.md says how to configure it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Not a stupid question, and on a stock install there is genuinely no way to switch it off — that is why every combination you tried failed:
Still true on 0.1.39 (checked today's Workaround that works today (this is how we run it on 2× RTX 3090): keep the config's "args": ["--peer-device", "1", "--peer-reserve-mib", "1850", "…"]The peer card is picked from the args, so it does not need to be in the fks's #665 makes this unnecessary by skipping the automatic |
|
Thank you for the explanation! I pulled in #665 manually and rebuilt, and it's working fine now as you described. I'll mention in passing that for my rig -- a 5090+3090 with 256 GB of RAM running the Unsloth Q4 quant -- this flag noticeably bumped performance, and I'm seeing ~1275 t/s prefill and ~90 t/s generation, which is frankly astonishing. |
…peer declines the prompt path (#531 follow-up) fused_ring() turns the fused ring sizing off for any --peer-device session (core::peer_portable), but the layer's fused choice tested the peer's prompt-path state (m.pp), which is null when set_peer declines (no P2P, the buffers do not fit, --peer-prefill-rows 0). Both now follow the flag, so a peer session's prompt path is MMQ with the MMQ ring and buffers it was sized for. Without --peer-device nothing changes.
peer_experts.cpp calls cudaDeviceCanAccessPeer, cudaDeviceEnablePeerAccess and cudaMemcpyPeerAsync and tests cudaErrorPeerAccessAlreadyEnabled; the HIP shim had none of them, so the Windows HIP build stopped there. Mapped to their hip* names.
…l's expert count (#531 follow-up) #531's peer-aware lookup indexed the residency table with the 512-expert constant NE instead of the model's own count. The Coder has 256 experts per layer, so from its second layer a resident expert was looked up in the wrong row, and a prompt long enough for the streamed walk stopped with "an expert is neither resident, streamed nor on the peer" (found by the 57K real-use check). The 512-expert models index the same row as before.
…peer declines the prompt path (Niko1221#531 follow-up) fused_ring() turns the fused ring sizing off for any --peer-device session (core::peer_portable), but the layer's fused choice tested the peer's prompt-path state (m.pp), which is null when set_peer declines (no P2P, the buffers do not fit, --peer-prefill-rows 0). Both now follow the flag, so a peer session's prompt path is MMQ with the MMQ ring and buffers it was sized for. Without --peer-device nothing changes.
peer_experts.cpp calls cudaDeviceCanAccessPeer, cudaDeviceEnablePeerAccess and cudaMemcpyPeerAsync and tests cudaErrorPeerAccessAlreadyEnabled; the HIP shim had none of them, so the Windows HIP build stopped there. Mapped to their hip* names.
…iko1221#452), Q5_0 experts on the GPU (Niko1221#473), IQ4_XS on AVX-2 (Niko1221#415), unbuffered loads on Windows (Niko1221#357 Niko1221#362), --peer-device (Niko1221#531), a 6 GB card starts (Niko1221#496), PR batch; setup requires it
…l's expert count (Niko1221#531 follow-up) Niko1221#531's peer-aware lookup indexed the residency table with the 512-expert constant NE instead of the model's own count. The Coder has 256 experts per layer, so from its second layer a resident expert was looked up in the wrong row, and a prompt long enough for the streamed walk stopped with "an expert is neither resident, streamed nor on the peer" (found by the 57K real-use check). The 512-expert models index the same row as before.
…peer declines the prompt path (Niko1221#531 follow-up) fused_ring() turns the fused ring sizing off for any --peer-device session (core::peer_portable), but the layer's fused choice tested the peer's prompt-path state (m.pp), which is null when set_peer declines (no P2P, the buffers do not fit, --peer-prefill-rows 0). Both now follow the flag, so a peer session's prompt path is MMQ with the MMQ ring and buffers it was sized for. Without --peer-device nothing changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0154bwiad8gY7wRehCXy27pN
peer_experts.cpp calls cudaDeviceCanAccessPeer, cudaDeviceEnablePeerAccess and cudaMemcpyPeerAsync and tests cudaErrorPeerAccessAlreadyEnabled; the HIP shim had none of them, so the Windows HIP build stopped there. Mapped to their hip* names. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0154bwiad8gY7wRehCXy27pN
…iko1221#452), Q5_0 experts on the GPU (Niko1221#473), IQ4_XS on AVX-2 (Niko1221#415), unbuffered loads on Windows (Niko1221#357 Niko1221#362), --peer-device (Niko1221#531), a 6 GB card starts (Niko1221#496), PR batch; setup requires it Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0154bwiad8gY7wRehCXy27pN
…l's expert count (Niko1221#531 follow-up) Niko1221#531's peer-aware lookup indexed the residency table with the 512-expert constant NE instead of the model's own count. The Coder has 256 experts per layer, so from its second layer a resident expert was looked up in the wrong row, and a prompt long enough for the streamed walk stopped with "an expert is neither resident, streamed nor on the peer" (found by the 57K real-use check). The 512-expert models index the same row as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0154bwiad8gY7wRehCXy27pN
With several GPUs in the config's "gpu" the server appended --layer-split auto, which the engine refuses beside --peer-device (#531), so the peer tier could not be started through the server at all: strata generate: --peer-device cannot be combined with --layer-split (use one or the other) The split (and --split-skip-if-fits) is now added only when --peer-device is not among the args; the start line names the peer tier instead of a layer split. Tests in serve/test_server.py (PeerDevice), docs/SECOND_GPU.md says how to configure it. Ported by hand onto the 0.1.40 engine_args (layer_split_value); the start banner now says the peer tier is in use when the split is not. (PR #665)
With several GPUs in the config's "gpu" the server appended --layer-split auto, which the engine refuses beside --peer-device (Niko1221#531), so the peer tier could not be started through the server at all: strata generate: --peer-device cannot be combined with --layer-split (use one or the other) The split (and --split-skip-if-fits) is now added only when --peer-device is not among the args; the start line names the peer tier instead of a layer split. Tests in serve/test_server.py (PeerDevice), docs/SECOND_GPU.md says how to configure it. Ported by hand onto the 0.1.40 engine_args (layer_split_value); the start banner now says the peer tier is in use when the split is not. (PR Niko1221#665)
Resubmit of #229 part 1, rebased on v0.1.36, per your closing comment there.
--peer-device Nputs an adaptiveexpert cache on a second visible GPU beside CUDA0's, holding the ranked pairs the primary cache does not hold.
Activations and result rows cross NVLink or another P2P path; the peer computes its experts' rows for decode
windows and prompt chunks. Without the flag everything is exactly as released.
Checklist from #229
the peer tier is rebased on top and keeps them intact.
cudaHostAllocPortableonly with a peer:core::set_peer_portable(o.peer_device >= 1)runs once after argparsing; verify.cpp, mtp.cpp and the prefill group table add the Portable flag only then. No layer-split or
single-GPU run pays for it.
--layer-split/--expert-cache-device1combinations are refused:--peer-devicenow refuses both--layer-split(a different second-GPU mode, use one or the other) and--expert-cache-device1..3(both would fillthe same card with an expert cache), with a one-line message each; plus the existing refusals (no
--expert-profile/expert cache, device not visible). Documented in docs/SECOND_GPU.md and in
--help(new lines for the--peer-*flags).--peer-reserve-miband--peer-slotsare the manual knobs; the docs say so and name Multi-GPU: the session carve and the per-stage prompt loans #216 as the follow-up.
Numbers (2x RTX 3090 + NVLink, IQ3_S, v0.1.36)
--peer-device 1 --peer-reserve-mib 1850)Our private build with the follow-ups (GDN head split etc.) reaches 2657 / 3118 / 3010.
Exactness
Gate:
STRATA_IQ_MT_MIN=1+ our local debugSTRATA_MMQ_NO_STREAMK=1,--adapt-swaps 0, same expert set(single
--expert-cache 6000vs dual--expert-cache 3000+--peer-slots 3950).--peer-device== v0.1.36 single (flashbench gate + 19.5K-token long gate PASS): default unchanged.--peer-device 1== v0.1.36 single (both gates PASS): byte-identical.Independent data
--peer-slots 256 --peer-reserve-mib 1536workaround prefill is at parity and decode is -11-14 %, and thex8 link kills the gain — auto sizing needs Multi-GPU: the session carve and the per-stage prompt loans #216's lending first.
--peer-deviceautomates.Limits / follow-ups
(fused ring sizing switches off when a peer is configured).
--peer-reserve-mib,--peer-slots) until Multi-GPU: the session carve and the per-stage prompt loans #216's buffer lending lands.announced on Multi-GPU on 2x RTX 3090 (NVLink): the peer expert tier (#110) vs the layer split — measurements #204; the branch is already on this fork (
preview/gdn-split).