|
Download offload/dspark/worker/TP2.md from ProprietaryLegal/dsv41-offload: direct link, hf CLI and curl.
- Browser
- Download file 21.8 kB
-
https://huggingface.co/ProprietaryLegal/dsv41-offload/resolve/main/offload/dspark/worker/TP2.md
- Command line
-
hf download hf://ProprietaryLegal/dsv41-offload/offload/dspark/worker/TP2.md
-
curl -L -o TP2.md https://huggingface.co/ProprietaryLegal/dsv41-offload/resolve/main/offload/dspark/worker/TP2.md
21.8 kB
| # The two-card (TP = 2) DSpark worker on x86host's NVLink pair (lane E) | |
| **In short.** `tp2.py` runs the same drafter as the one-card worker (same checkpoint tensors, same contract, same | |
| server, same port) split over the NVLink pair `GPU-c4e7a371` (rank 0) + `GPU-7744b44b` (rank 1), selected with | |
| `--tp 2` / `DSPARK_WORKER_TP=2`; one card stays the default. Each card does half of the | |
| bandwidth-bound work (vocabulary head + Markov, routed and shared experts, wq_b / attention / wo_a / wo_b) and | |
| replicates the small rest; the halves meet in 6 all-reduces and one argmax exchange per drafted position per step, | |
| through lane E's own NVLink peer-to-peer kernels inside the CUDA graphs. **Measured on the pair** (2026-10-23 | |
| 00:32-00:42Z, both cards at NDM's 325 W cap): the draft step takes 1.94 ms at 1 request (one card: 2.94 ms, -34 %) and | |
| 3.17 ms at 4 requests with n_out 4 (one card: 4.89 ms, -35 %); a whole STEP over lane O1's RDMA transport takes | |
| 2.34 ms at 1 request (one card: 3.14 ms) and 3.67 ms at 4 requests (one card: 5.01 ms). The real pair computes the | |
| same bits as the single-card emulation, and its drafts agree with the Spark's own drafter exactly as often as the | |
| one-card worker's do (93.4 % of live blocks; every difference a numeric near-tie). | |
| ## Layout | |
| | | rank r owns (r = 0, 1) | replicated on both | | |
| |---|---|---| | |
| | head + Markov bias | vocabulary columns [0, 64768) / [64768, 129280) (253 / 252 tiles of 256: the head's kernel tile; rank 0 holds the lower ids so a tie of the global argmax goes to the lower id, as on one card) | Markov embedding (by previous token), token -> cached-row map | | |
| | attention | wq_b columns (heads 32r..32r+31), the heads' sinks, block attention, wo_a groups 4r..4r+3 (FP8 bytes + scales), wo_b input rows 4096r.. | embedding, mHC mixes / collapse / post, norms, wq_a, wkv, RoPE, the window-KV ring (inserted on both), block KV | | |
| | FFN | routed experts: intermediate channels 1152r.. (Exl3Bank tp_size 2, the Spark's own cut); shared expert w1 / w3 columns, w2 rows | routing (gate, top-3) | | |
| | other | | main_proj + main_norm (context insert), confidence head (rank 0 only) | | |
| Every cut is at a 128-channel boundary of the EXL3 Hadamard blocks, so a shard's decoded weights are the full tensor's | |
| decoded weights bit for bit (`tests/test_tp2.py`: 98 matrices incl. experts of every stage; FP8 wo_a bytes, scales, | |
| sinks; Markov head rows and cached bias rows == the one-card model's columns). | |
| Per step: per stage one all-reduce of the attention partials (wo_b) and one of the FFN partials (routed + shared), | |
| fp32 sums rounded to bf16 — identical bits on both ranks because a + b is commutative, so the replicated state never | |
| diverges; per drafted position one exchange of (shard max, index) folded into the Markov finish kernel | |
| (`xchg_markov`); with WANT_MARGIN a (top1, top2, index) exchange. | |
| ## Collectives (`p2p/p2p.cu`) | |
| One process drives both GPUs (`CUDA_VISIBLE_DEVICES=GPU-c4e7a371...,GPU-7744b44b...`, peer access enabled both ways). | |
| One-shot push: each rank writes its payload straight into the peer's receive buffer over NVLink (UVA stores), signals | |
| with `st.release.sys` on the peer's flag, waits with `ld.acquire.sys` on its own, then combines. Epoch counter per | |
| rank (read at kernel start, published by the last block), double-buffered by epoch parity, per-block flags. Waits are | |
| bounded by `%globaltimer` (default 2 s, `DSPARK_TP_TIMEOUT_MS`): a timeout sets the rank's error word (mirrored into | |
| the peer's), the STEP fails loudly (INTERNAL, slots resynced), both devices are synchronized and both states reset. | |
| No allocation, static arguments: captured inside the CUDA graphs. All kernels are launched once at start-up | |
| (`p2p.preload`): CUDA lazy loading may synchronize a context on a kernel's first launch, which deadlocks against a | |
| collective spinning on the same device (CUDA programming guide, lazy loading / concurrent execution). Test: | |
| `tests/test_p2p.py` (single-card mode: both ranks on one card, two streams; two-card mode: the pair). | |
| ## Execution | |
| Single controller: every op is issued for each active rank in turn under that rank's device, so an eager step is | |
| lockstep and each collective's two halves are issued back to back. CUDA graphs are captured per rank by running the | |
| same code with only that rank active (capture records, nothing runs, so a collective needs no partner while | |
| capturing); a step replays rank 1's graph, then rank 0's (rank 0's outputs and its error word come back in one D2H). | |
| Graphs: (B 1-4) x (n_out 1-5) x (plain, WANT_MARGIN), inserts of 8-128 rows, per rank. | |
| Modes for development on one card: emulation (both ranks on one device, collectives as torch adds / compares — the | |
| same combine bit for bit), solo (one rank alone, collectives replaced by local stand-ins: one rank's compute time), | |
| tp = 1 (the same code unsplit on one card: separates what the second card adds from what the code adds). | |
| Every EXL3 GEMM runs with a forced (shape, num_sms) from `tp2_kernels.json` (`tools/tp2_tune.py`, timed on the 3090): | |
| exllamav3's timing-based autotuner could pick different kernels on the two cards (breaking the bit-identity of the | |
| replicated activations) and cannot run inside a capture. The head keeps one kernel for 1-16 rows (n_out bit-identity, | |
| CONTRACT 1.3). | |
| Small replicated chains fused in `fused_e.py` (tested against the ops they replace, `tests/test_fused_e.py`): routing | |
| in 2 launches (was ~10; same expert sets on 195/195 test rows, weights within 3.5e-7), SwiGLU -> fp16, norms with | |
| fp16 outputs, shared expert through the coop kernel's `sh_out` (bit-identical). Tried and kept off: | |
| `Exl3Pair` (wq_a + wkv and shared w1 + w3 in one `exl3_mgemm` launch): -70 to -100 us per step, but its rounding is | |
| 0.12-0.13 % from fp64 instead of 0.10 % and draft agreement with the engine on the captures fell from 93.4 % to | |
| 91.3 % of blocks (`results/e/e6_parity_emulated_v3_fused_pairs.json`, ~1.8 standard errors). The drafter is ~3-8 % of | |
| a decode round, so 0.1 ms of drafter time is worth less than 1-2 points of acceptance; `DSPARK_TP_FUSE_PAIRS=1` turns | |
| it on. | |
| ## Timing on one card (solo shards; `results/e/e3_bench_solo_v3.json`) | |
| RTX 3090 `GPU-7744b44b`, 325 W cap (NDM), P2, SM 1965 MHz / mem 9501 MHz at the end of the run; graph replay p50 over | |
| 300 replays, inputs as O2's D5 bench (full 128-row windows, real English anchors, synthetic taps). "one card" = O2's | |
| deployed model.py on this card; "E tp=1" = this code unsplit on this card; rank 0 / rank 1 = one rank's share of the | |
| TP = 2 step with the collectives replaced by local stand-ins (rank 1's Markov and expert times are inflated by the | |
| stand-in: its local argmax feeds different previous tokens / routing than a real pair would). Bold = the live | |
| config's n_out. | |
| | requests, n_out | one card (O2) | E tp=1 | TP2 rank 0 | TP2 rank 1 | | |
| |---|---|---|---|---| | |
| | 1, 5 | **2.94 ms** | 2.83 | **1.76** | 1.85 | | |
| | 1, 4 | 2.89 | 2.75 | 1.70 | 1.79 | | |
| | 2, 5 | **3.67** | 3.50 | **2.23** | 2.32 | | |
| | 2, 4 | 3.57 | 3.43 | 2.16 | 2.26 | | |
| | 3, 5 | 4.19 | 4.05 | 2.51 | 2.66 | | |
| | 3, 4 | **4.10** | 3.97 | **2.45** | 2.59 | | |
| | 4, 5 | 5.71 | 5.62 | 3.31 | 3.37 | | |
| | 4, 4 | **4.89** | 4.78 | **2.87** | 2.97 | | |
| Where the time goes at 1 request (rank 0, kernel us): experts 383 (one card 812), head 310 (651), dense K5 455 (656: | |
| 21 GEMMs with a fixed ~15 us each, so halving their bytes saves little), Markov 147 (202), wo_a 91 (153), mHC 114 | |
| (replicated), attention 44, norms / RoPE / KV 44, routing 33, other 79. The context insert is replicated: 286 us for | |
| 1-6 rows on each card (incl. the taps' upload), the same as one card. | |
| ## Pair (2026-10-23 00:32-00:42Z, `results/e/e7_*`) | |
| Conditions: both cards at 325 W (NDM), P2 / SM 1965-1980 MHz / mem 9501 MHz at the end, the live one-card worker | |
| idle on GPU-c4e7a371 (8.3 GiB, no traffic) during the window; power watchdog on the coordinator's 1 s power log (no | |
| anomaly; both pair cards reached their 325 W cap, at most 710 W for all four); NVLink replay / recovery / CRC errors | |
| 0 before and after. "one card" = O2's model.py on this card model at 325 W (`e3_bench_solo_v3.json`); TP2 = the real | |
| pair (`e7_bench_pair.json`; the repeat `e7_bench_pair_run2.json` is within 0.5 %). Graph replay p50 over 300 replays, | |
| rank 0's CUDA events (input copies of both ranks + both replays); bold = the live config's n_out. | |
| | requests, n_out | one card | TP2 pair | change | TP2 host wall | rank 0 / rank 1 kernel time | of which collectives (rank 0) | | |
| |---|---|---|---|---|---|---| | |
| | **1, 5** | 2.94 ms | **1.94** | -34 % | 1.99 | 1.80 / 1.81 | 0.07 | | |
| | 1, 4 | 2.89 | 1.92 | -33 % | 1.91 | 1.77 / 1.78 | 0.07 | | |
| | **2, 5** | 3.67 | **2.42** | -34 % | 2.42 | 2.28 / 2.29 | 0.09 | | |
| | 2, 4 | 3.57 | 2.37 | -34 % | 2.36 | 2.23 / 2.24 | 0.09 | | |
| | 3, 5 | 4.19 | 2.72 | -35 % | 2.73 | 2.59 / 2.60 | 0.12 | | |
| | **3, 4** | 4.10 | **2.67** | -35 % | 2.66 | 2.53 / 2.54 | 0.13 | | |
| | 4, 5 | 5.71 | 3.60 | -37 % | 3.60 | 3.50 / 3.51 | 0.14 | | |
| | **4, 4** | 4.89 | **3.17** | -35 % | 3.17 | 3.09 / 3.10 | 0.14 | | |
| O2's estimate (PERF.md "two-card variant") was -38 to -42 % of kernel time; measured -33 to -37 % of graph time: | |
| the 21 dense GEMMs per step keep their fixed ~15 us each at half the bytes, and the replicated mHC / norms / | |
| routing / attention-side work does not shrink. | |
| **Collectives** (p2p.cu inside CUDA graphs, 20 back-to-back per replay, `collective_bench`): all-reduce of | |
| 5 / 10 / 15 / 20 rows x 5120 fp32 (100-400 KB each way) 12.9 / 15.2 / 19.6 / 22.5 us; argmax exchange 9.2 us for 1-4 | |
| requests. In the step they cost 0.07 ms (1 request) to 0.14 ms (4 requests) of rank 0's timeline: 3.6-4.7 % of the | |
| step. NVLink traffic of one bench run: 9.3 GB each way. | |
| **Context insert** (both ranks, incl. the taps' upload): 395 us for 1-6 rows (one card 276 us), 478 us for 20 rows, | |
| 1,026 us for 128 rows (one card 843). Slower than one card although it is the same replicated work: rank 0's graph | |
| waits while the host issues rank 1's small copies and replay (no collective in the insert, so the order is free); | |
| packing each rank's inputs into one upload and replaying rank 0 first should take ~0.1 ms off (not done yet). | |
| **A whole STEP through the server** (`tools/server_selftest.py ... 2`, `e7_selftest_pair.json`; ofl server on | |
| 10.202.28.1:18627 and client on x86host, i.e. the NIC's loopback; contract checks: identity / config refusal, 41/41 rounds | |
| bit-identical to direct model calls, n_out prefix, rollback, partial window, bad entry, stale generation, RDMA + TCP): | |
| | STEP | TP2 in-process handle | TP2 RDMA round trip p50 (p99) | TP2 TCP | one card RDMA (PERF.md) | | |
| |---|---|---|---|---| | |
| | 1 request, 6 rows | 2.25 ms | **2.34** (2.55) | 2.40 | 3.14 | | |
| | 2 requests | 2.64 | | | ~3.73 | | |
| | 3 requests, n_out 4 | 3.01 | | | ~4.24 | | |
| | 4 requests, n_out 4 | 3.46 | **3.67** (3.79) | 3.74 | 5.01 | | |
| | 4 requests, n_out 5 | 3.88 | 4.09 (4.21) | 4.16 | 5.74 | | |
| **Memory per card** (bench process, `nvidia-smi`): 5,726 MiB on GPU-c4e7a371, 5,050 MiB on GPU-7744b44b (torch | |
| reserved 5,316 / 4,656 MiB; all CUDA graphs captured in 1.5 s); the one-card worker: 8,152 MiB on one card. | |
| **Correctness on the pair**: `test_pair_equals_emulation` (real pair == emulation bit for bit: drafts, confidences, | |
| margins, base logits, head hidden; graph == eager); capture parity on the pair (`e7_parity_pair.json`): TP2 vs the | |
| engine's drafter 92.6 % (A) / 93.4 % (B) of live blocks exact — the emulated numbers exactly; all 53 live first | |
| mismatches against the one-card reference numeric near-ties. (The one-card reference itself scored 92.6 / 91.9 % here | |
| instead of 91.5 / 93.4 % on 7744b44b alone: model.py autotunes its EXL3 kernels by timing per card, so the same model | |
| picks other kernels on another card or in another process and flips other near-ties. TP2 forces its kernels and gives | |
| the same bits on either card.) | |
| ## When two cards beat one (decision data) | |
| * The draft step is 34-37 % shorter at every live batch size (1.0 ms at 1 request, 1.7 ms at 4 requests). | |
| * A whole remote round (rows in, drafts out, over RDMA): -0.80 ms at 1 request (2.34 vs 3.14 ms, -25 %), -1.34 ms | |
| at 4 requests with n_out 4 (3.67 vs 5.01 ms, -27 %). That is under the Spark's own ~3.6 ms local drafter (upstream | |
| profile at 1 request) at 1 request, at 2-3 requests too (in-process 2.64 / 3.01 ms + ~0.1 ms transport; their round | |
| trips were not measured) and level with it at 4. | |
| * What that is worth end to end (estimate; the coordinator's in-engine runs decide it). The production | |
| configuration (dsv41.env: capacity policy, synchronous rounds) prices a round as the verify forward | |
| (REMOTE_ROUND_MS: 43.68 ms at depth 5, 41.56 ms at depth 4) plus the remote draft (REMOTE_DRAFT_MS 4.4 ms, calibrated on | |
| one card). 0.80 ms less at 1 request is ~1.7 % more rounds per second (48.1 -> 47.3 ms); 1.34 ms less at 4 requests is | |
| ~3 %. Against the upstream single-stream profile (38.9 ms with the local drafter) the same savings are 2-3.5 %. That | |
| holds if acceptance is unchanged, and the parity shows it is: same agreement with the engine as the one-card worker. | |
| Against NDM's promotion threshold (>= ~5 %, repeatable) TP2 alone is below it at 1 request and short of it at 4. | |
| * With TP2 the depth policy's REMOTE_DRAFT_MS (4.4 ms, one card) overprices the draft by ~0.8 ms at 1 request, so a | |
| TP2 configuration should be recalibrated (or the policy keeps choosing as if the drafter were slower). | |
| * Cost: the second 3090 fully (5 GB, ~260 W under load, both cards reach the 325 W cap in sustained benches), the | |
| pair cannot also host a second independent worker, and the failure surface doubles (a dead card stops the worker; | |
| the collectives fail fast and the worker exits for systemd after 3 failed steps in a row). | |
| * Accuracy beats small speed wins here: the drafter is ~3-8 % of a round, so a 1-point drop in acceptance costs more | |
| than 0.1 ms of drafter time saves (why `Exl3Pair` stays off). | |
| ## Open items | |
| * Insert host overhead on the pair (above, ~0.1 ms per round). | |
| * S10 P-3 mixed-model build/capture qualification: a process-wide implicit capture stream on the wrong device was | |
| reproduced and fixed by passing model.py's owning stream explicitly. Fixed full/swapped/recreated pair lifecycles | |
| pass. Native peer state also removes dependence on Torch's runtime expandable allocator. The original illegal-address | |
| kernel remains unlocalized: corrected baseline/fixed cross-device allocator controls and a fresh full pair/emulation | |
| comparison are still pending an authorized RX pair window. Do not mark P-3 closed from capture sensitivity alone. | |
| * Only the coordinator can measure the Spark-side effect (in-engine test). | |
| * --max-model-len (main's 88a31ed, ported in E18; the TP2 unit runs 1048576 like the production unit): the RoPE | |
| table is replicated, 269 MB per rank at 1,048,576 (84 MB at 327,680), so both cards hold ~0.18 GB more than the pair | |
| numbers above (measured at 327,680). test_tp2's agreement test at positions near 900,000 has not run on a GPU yet. | |
| ## Memory per card in detail (rank shards, MiB) | |
| | item | rank 0 | rank 1 | one card (O2) | | |
| |---|---|---|---| | |
| | embedding (bf16, replicated) | 1,262.5 | 1,262.5 | 1,262.5 | | |
| | head shard (EXL3 K6) | 237.3 | 236.4 | 473.7 | | |
| | routed experts (EXL3 K2, half the channels) | 1,633.8 | 1,633.8 | 3,256.3 | | |
| | wq_b / wo_b / shared shards + wq_a / wkv / main_proj | 106.9 + 16.5 + 46.9 | same | 230.2 + 46.9 | | |
| | wo_a FP8 shard | 48.2 | 48.2 | 96.4 | | |
| | Markov embedding + head shard + cached rows | 63.1 + 31.6 + 1,012.5 | 63.1 + 31.5 + 1,008.5 | 126.2 + 2,020.5 | | |
| | RoPE, norms, windows | 80.5 + 18.8 + 3.4 | same | 80.5 + 18.8 + 3.4 | | |
| | torch allocated (weights + state) | 4,772 | 4,767 | 7,639 | | |
| ## Parity (`tools/tp2_parity.py`, live captures A-capture-20261022, 453 rounds, 542 live request-blocks) | |
| Emulated TP2 (identical bits to the pair: `test_pair_equals_emulation`) against O2's one-card worker and the Spark's | |
| drafter, blocks exact (all 5 drafts equal), `results/e/e6_parity_emulated_v2.json`: | |
| | window | TP2 vs one card | one card vs engine | TP2 vs engine | | |
| |---|---|---|---| | |
| | A (captured main_x) | 93.9 % | 91.5 % | 92.6 % | | |
| | B (main_x from taps: production) | 96.1 % | 93.4 % | 93.4 % | | |
| Every first mismatch between TP2 and the one-card worker (54 live) is a numeric near-tie (the two tokens within | |
| max(2 bf16 steps, 3 sigma of the cross-model logit noise) in one model's logits); no routing cascade, nothing | |
| unexplained. TP2 agrees with the engine exactly as often as the one-card worker does. | |
| ## Files | |
| `tp2.py` (model + `TP2Backend`), `p2p/` (collectives), `fused_e.py`, `tp2_kernels.json`, `server.py` (`--tp`), | |
| `tools/{tp2_tune,tp2_bench,tp2_parity}.py`, `tools/server_selftest.py` (tp argument), `tests/{test_p2p,test_tp2, | |
| test_fused_e}.py`, `run/{dsv41-dspark-worker-tp2.service,start_tp2.sh,stop_tp2.sh,status_tp2.sh,e_job.sh}`, | |
| `requirements.e.lock`. Results: `offload/results/e/`. | |
| ## RX-TP2SAFE collective safety revision (2026-10-23) | |
| The worker's arithmetic and channel split are unchanged. Each push now publishes a per-block consumption acknowledgment | |
| only after all consumer reads and mirrored error stores finish. A successful block waits for that acknowledgment before | |
| completing. Together with double buffering and stream order this prevents reuse before consumption; it also makes a bad-id | |
| refusal on rank 1 visible to the rank-0 STEP check in the same round (S10 P-2). Both data publication and acknowledgment use | |
| system-scope release/acquire ordering. Timers compare only a local GPU timer against its own start time. | |
| Error bits (both headers): 1 local timeout, 2 peer timeout, 4 invalid received ID (mirrored), 8 cancellation, 16 epoch | |
| exhaustion. Epoch zero is never published: at uint32 exhaustion the operation fails closed, with no peer payload access. | |
| The old claim that exact-equality alone makes wrap safe was incomplete: an unused block's initialized zero flag could match | |
| wrapped epoch zero. A drained reset clears flags/acks and starts a fresh generation. Cancellation uses pre-created independent | |
| streams, so it can interrupt a collective parked on another stream. Outputs of failed/cancelled operations are refused by | |
| STEP and must never be interpreted as complete sums or proposals. | |
| `P2PPair.headers/reset` track every stream used by eager collective launches as well as current per-device streams. They wait | |
| for bounded idle completion before reading/clearing state; a timed-out idle wait or CUDA exception permanently poisons the | |
| pair. A poisoned pair refuses launches, graph replay and reset; `TP2Backend.healthy` reports false, causing the existing fatal | |
| worker exit/restart policy. This host latch never clears a CUDA sticky error. The native peer-enable path checks pending CUDA | |
| errors before handling the benign AlreadyEnabled status. Graphs retain the same state pointers throughout their lifetime; | |
| callers must keep the drafter alive until its graph replays and peer accesses drain. | |
| Debug oracle: `worker.p2p.reference.all_reduce` uses supported PyTorch device copies with explicit producer events, FP32 | |
| addition and optional BF16 rounding. It is eager-only and does not use the push flags or raw peer pointers. `test_p2p.py` | |
| compares both implementations bit for bit, including delayed streams, varying block counts and mixed collective types. | |
| `python -m worker.p2p.lifecycle --full --cycles 2` isolates the S10 mixed-model build/capture order, checks eager/graph IDs | |
| and confidence equality, then destroys/recreates. `--swapped` reverses logical devices; `--legacy-capture` reinstates the | |
| implicit one-card capture stream for diagnosis. Run each failure reproduction in a fresh process. | |
| Lane-only runner: `worker/p2p/run_owned.sh solo|pair LOG COMMAND...` enforces UUIDs and both locks, caps execution at 14 minutes, | |
| records per-card telemetry, and terminates its own test process group if coordinator `PAIR_OK` stops naming RX-TP2SAFE. It | |
| never creates PAIR_OK or changes a service, power limit or clock. Production launchers remain the coordinator's responsibility. | |
| Peer state now uses an owning native `cudaMalloc` tensor with a guarded `cudaFree` deleter, allocated outside capture on | |
| its exact device. It is independent of Torch's caching/VMM allocator: exllamav3's Python import can enable expandable | |
| segments through a runtime setter without changing either allocator environment variable. `cudaDeviceEnablePeerAccess` | |
| does not establish access to arbitrary VMM allocations. The small native peer blocks therefore remain conventional CUDA | |
| allocations even when the model uses expandable segments. At max_words=163840 each state is 1,314,816 bytes (about | |
| 1.254 MiB/card), outside Torch memory accounting. `test_runtime_expandable_allocator_cannot_change_peer_state_allocation` | |
| checks that native state pointers lie outside all Torch-managed segments and runs randomized exact collectives after the | |
| runtime allocator toggle. cudaMallocAsync remains explicitly unqualified/refused. A failed native free warns and requires | |
| process restart; it never clears a poisoned context. | |
| The model-free lifecycle's pair capacity matches `tests.test_p2p.MAXW` (163840 words), so every randomized payload fits; | |
| `test_pair_lifecycle_capacity_covers_sequence` pins that contract. `--allocator-import` imports the installed exllamav3 | |
| Python package, allocates a probe and reports Torch segment types before constructing either peer state. Compare fresh | |
| baseline processes with and without `EXL3_EXPANDABLE_SEGMENTS=0`, then the native-allocation implementation. Single-card | |
| mode checks lifecycle/arithmetic only; cross-device raw-pointer mappings require the authorized pair. Use asynchronous | |
| launches for successful collective controls: `CUDA_LAUNCH_BLOCKING=1` serializes the controller's rank launches and can | |
| prevent the peer from joining a spin-wait collective. | |