Repository navigation
Conversation
…ier (mutual help), and the elastic control lines (opt-in) --peer-link FILE --peer-link-role 0|1: two engine processes, one GPU each, share a small link file. Each publishes which experts its card holds and computes the other's rows for them on its own card (a service thread, the same q8_1 + native_expert_grouped kernels as the --peer-device tier, zero-copy through the mapped file, a high-priority stream). Only the owner touches its card: it holds the link while it rewrites or unmaps slots (adaptive swaps, the prompt path's loan, VRAM) and publishes the residency once the copies have landed; an expert planned a moment too late is copied in from the owner's RAM copy for that request. Role r fills its cache from every second rank of the profile, so the two caches hold different experts. Serve-loop control lines (stdin, any time; answered CTL ...): PEER_DETACH (between verify windows: the --peer-device tier and its prompt buffers are freed; with --peer-link the other engine's card becomes the tier), PEER_ATTACH (between requests), POOL_HOLD n (the last n CPU workers sit out), LINK_SERVE 0|1. --peer-link may run beside --peer-device (the lead of the elastic pair). VRAM (Niko1221#533) accepts a linked engine. Without --peer-link nothing of this runs: the release's output is unchanged (gates: Q4_K_XL layer split and IQ3_S one GPU, word for word against v0.1.40.3).
…lper card turns into its own engine on a second request serve/elastic.py: two engine processes behind one server. One request runs on the lead exactly as with --peer-device; a second request wakes the helper (lead PEER_DETACH + POOL_HOLD, helper thawed with cuda-checkpoint if configured, its Niko1221#533 cache grown back, LINK_SERVE 1) and both run, each the other's expert tier through --peer-link. After hold_s idle the helper gives its cache back and sleeps, the lead re-attaches. Routing: the idle lane holding the longest prefix. serve/test_elastic.py: the routing and the transition order with fake engines. docs/MULTI_GPU.md: how to set it up, measured numbers (2x RTX 3090, IQ3_S: one request unchanged, two at once 151-160 t/s vs 99 one after the other) and where it does not help (Q4_K_XL on a layer split).
|
Thanks for the thorough PR and for the honest 'where it does not help' row. We cannot test this: it needs two cards that can share an expert arena (you used NVLink 3090s) and our only pair is 2x A4000 without a link; it also no longer applies to the current main (conflicts in serve/server.py and generate.cpp). One request for a rebase: please add the numbers of --batch 2 with a layer split on the same two cards next to --peer-device, because that is the other way to serve two requests at once on two cards. |
|
Reopen or comment with these when you can: (1) a rebase on current main (it conflicts in serve/server.py and generate.cpp); (2) numbers for --batch 2 with a layer split on the same two cards, next to --peer-device, at the same prompt and settings; (3) a run with the flag off showing the same token ids as stock. We cannot test it ourselves: our only pair has no NVLink. |
Follow-up to #723 and #531. As you asked there: opt-in, off by default, gated like the peer pair, numbers first.
What it does. With
--peer-deviceone engine uses both cards for one conversation and a second request waits.The elastic pair lets the second card turn into its own engine when a second request arrives, and back into the
first engine's peer tier after it has been idle for a while. While both run, each engine computes the other's rows
for the experts its card holds (
--peer-link, "mutual help"), so neither falls back to the CPU for the experts theother card has. Still one sequence per engine process: two processes, one card each, one shared expert arena.
--peer-device: card 2 is its tier (as without the pair)--vram-elastic), optionally frozen withcuda-checkpointPEER_DETACHbetween two verify windows,POOL_HOLD nLINK_SERVE 1, takes the requesthold_sPEER_ATTACHafter its request: card 2 is its tier againThe two numbers you asked for (2x RTX 3090 + NVLink, Ryzen 9 3950X, 121 GB DDR4, IQ3_S, 262K, kv int8)
1. Single request speed unchanged. Stock v0.1.40.3
--peer-device 1vs this PR's elastic pair serving onerequest, 3 alternating restarts x 2 runs (n = 6, medians, sampled, cached 54K coding prompt for decode):
--peer-device 1(the lead IS a
--peer-deviceengine while it is alone; the only difference is the cache split between the cards:every second profile rank, close to the default
STRATA_PEER_HOT0.45.)2. The cost to the first card when the second request starts (streamed, 3 runs, B starts 8 s into A):
B's first token comes 4.7 s plus its prompt after it was sent.
Aggregate, two 1,000-token requests at once (cached prompts, 5 runs): 151-160 t/s (median 155) vs 99 t/s
for the same two one after the other on
--peer-device(+55 %). Two one-card engines without the mutual help: 136-139.Exactness
Without
--peer-linkand without the"elastic"block nothing of this runs. Gates (greedy, word for word, thedeterministic gate configs:
--expert-cache 3000 --adapt-swaps 0 --pcie-frac 0 --prompt-cache 0 --no-prefill-borrow,STRATA_IQ_MT_MIN=1): stock v0.1.40.3 saved, stock repeated (PASS), this PR checked:--layer-split 24: chat/code/math + 20K-token long prompt x2 + one image: PASSWith the link on, a linked expert is computed with the same kernels (q8_1 +
native_expert_grouped) from the sameactivations as on the requester's own card, so its rows are the same; as with any cache, which experts sit in VRAM vs
on the CPU still decides the last bits.
Where it does NOT help (honest row)
unsloth UD-Q4_K_XL on the same cards: a
--layer-splitalready spends both cards' memory bandwidth on oneconversation (95 t/s); the pair gives 87-88 t/s for two together (75-79 for one request on the peer pair). Q4's dense
weights + head (~2.9 GB) are read per verify window per engine, so two engines read them twice. The pair pays off
where
--peer-deviceleaves the second card mostly idle (as a peer tier alone it measured 11-18 % busy here).How it is built
--peer-link FILE --peer-link-role 0|1(engine): a small shared-memory file. Each engine publishes its card'sresidency; a service thread computes the other engine's rows on its own card (zero-copy through the mapped file,
high-priority stream). Only the owner ever touches its card: it holds the link while it rewrites or unmaps slots
(adaptive swaps, the prompt path's loan, VRAM) and publishes after the copies landed; an expert planned a moment
too late is copied in from the owner's RAM copy for that request (counted in the log line). No CUDA IPC, no P2P
between processes, no MPS.
PEER_DETACH,PEER_ATTACH,POOL_HOLD n,LINK_SERVE 0|1(answeredCTL ...).serve/elastic.py: the desk (twoStrataEngines behind oneService,batch = 2), routing to the idle lane withthe longest prefix;
serve/test_elastic.py(fake engines, no GPU);docs/MULTI_GPU.md: setup + these numbers.Limits / follow-ups
sched_setaffinity); two cards (a pair). The shared arena must fit its tmpfs (/dev/shmis half of RAM by default).
long prompt, the helper wakes after that prompt (measured: 11.5 s into a fresh 54K prompt).
Measured with
bench/q4pair.py-style scripts (one-shot, not part of the PR); happy to add them undertools/if useful.Development of this PR (design, engine code, desk, measurements) was heavily supported by Opus 5.5.