Dual-SSD streaming: read the model from two drives at once (COLI_MODEL_MIRROR) - #421
Conversation
β¦pies A second (read-only) copy of the model on another drive is registered as a per-shard read replica; expert loads are split between the drives by a deterministic (layer,eid) hash weighted by COLI_DISK_WEIGHTS=<primary>,<mirror> or, by default, by a startup bandwidth probe using the engine's own access pattern (parallel ~19 MB reads, O_DIRECT). Cold decode is disk-bound (~11 GB/token), so two NVMe queues add up. - st.h: mirror accepted per file only if size + safetensors header are byte-identical to the primary (identical data_offsets by construction, so every pread is valid on either copy); partial mirrors work (smaller second SSD holding only some shards); the mirror is never written β .coli_usage / .coli_kv stay on the primary. - glm.c: routing covers the coalesced slab pread, O_DIRECT, mmap/Metal and scale reads, plus the OMP-parallel pin/autopin warmup (streams from both drives). Deterministic routing keeps readahead/PILOT WILLNEED on the same drive as the demand read and avoids caching an expert twice. A mirror read error falls back to the primary (one warning, no crash). Per-drive bytes are reported in a MIRROR: stats line. - tests: test_st_mirror covers validation, read equality on both replicas, and the rejection paths (divergent header, size mismatch, missing file). Measured on 2x NVMe (GLM-5.2 int4, greedy, DRAFT=0, DIRECT=1, cold-ish cache): 0.42 -> 0.57 tok/s (+36%), expert-disk service 15.2s -> 10.3s, byte-identical output; probe chose a 48/52 split.
|
Rebase needed: today's merges into |
β¦ conflicts Combined resolution: mir_pread/st_prefetch_rep (this PR) now carry dev's DISK-CLASS accounting unwind (dc_wall_exit) and O_DIRECT prefetch skip (g_direct); direct-path keeps dc_direct=1 plus the mirror read counters. Verified: clean build, token-exact tiny models unchanged, test_st_mirror and test_st_pread pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Update: I took care of the rebase myself (maintainer edits) β pushed a merge of current |
JustVugg#421 committed the compiled tests/test_st_pread (mode 644, non-executable). On a fresh CI checkout make sees it as up-to-date and skips rebuilding, so run_tests.py hits 'Permission denied' exec'ing a non-executable file β the linux/macos 'make check' failure. Remove the binary and gitignore it next to the existing test_st_mirror rule. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Merged into Call for data β if you (or anyone with two drives) can run this on Setup: put a second copy of the model on a separate physical drive, then COLI_MODEL=/drive1/glm52_i4 COLI_MODEL_MIRROR=/drive2/glm52_i4 ./coli chatWhat we'd like to see:
Anyone running on a slow single disk today: this is the cheapest real speedup available β a second budget NVMe roughly halves your streaming wait. Please report back here. |
β¦acity aggregation) Search-path of extra directories, each holding a distinct subset of the .safetensors shards (no duplication). Each shard lives on one drive; demand preads hit whichever drive holds it, so concurrent expert loads parallelise across drives and combined capacity is used β a 400 GB container fits across two 250 GB drives the mirror (#421) can't. st_init kept as a back-compat wrapper. Verified locally: clean build, no regression on the default path (token-exact tiny models unchanged), and the split path itself validated token-exact (shard on a separate dir, metadata in primary -> 32/32 vs oracle). Composable with the #421 mirror. CI 8/8. Thanks @mohamedmastouri2000-boop.
β¦ttern Compiled test binaries had been committed to c/tests/ (mode 100644), so on a fresh CI checkout make sees them as up-to-date, skips rebuilding, and run_tests.py fails to exec a non-executable file β every PR's CI was dying at test_e8_kernel (EACCES / Exec format error on macOS). Removes the 12 binaries and adds a pattern-based ignore (c/tests/test_* with negations for .c/.cu/.mm/.py) so sources stay tracked and no future binary can slip in β more robust than the per-file ignore added during #421. CI 9/9. Thanks @ZacharyZcR for spotting that it was breaking everyone, not just one PR.
β¦m upstream JustVugg/colibri JustVugg#469 COLI_MODEL_DIRS: st_init -> st_init_multi(snap, extra_dirs); st_scan_dir dedups shards by basename (first-listed dir wins), so a model split across N drives is indexed as one set with no duplication β concurrent expert loads parallelise across drives and combined capacity is used (a 400 GB container fits across two 250 GB drives). st_init kept as a back-compat 1-dir wrapper. JustVugg#421 COLI_MODEL_MIRROR: st_mirror_init registers a second read-only copy as a per-shard replica, accepted only if size + safetensors header are byte-identical to the primary (partial mirrors OK; never written). st_fd_rep/st_direct_fd_rep/ st_prefetch_rep/st_read_raw_rep expose replica routing with primary fallback + per-replica byte counters (st_mirror_stats). glm.c call site upgraded to st_init_multi(COLI_MODEL_DIRS). tests/test_st_multi.c: shard split + dedup, mirror acceptance, routed-read equality, and the size-mismatch rejection path. dsv4 read routing + glm mirror wiring land with the dsv4 integration commit.
β¦4 from upstream JustVugg/colibri JustVugg#403 measured-RSS guard: at the decode safe point (no moe in flight), every ~16 tokens, if measured RSS (/proc/self/status VmRSS β not ru_maxrss, so it reflects frees) exceeds the budget (RSS_GUARD_GB | RAM_GB | 0.88*MemAvailable), evict the coldest LRU expert slots per layer: free the six FP4 buffers (g4/u4/d4 + gs/us/ds), eid=-1, clk=0 (no compaction β PIPE workers hold pointers across preads), and lower ecap[L] so the cache cannot regrow. slot_load's realloc re-allocates a freed slot on next miss, so pages (>128 KB) return to the kernel and RSS actually drops. Pinned slots are never touched. Victim selection/free under g_pp.mx when PIPE runs. JustVugg#421 mirror routing wired into the disk-bound path: mirror_route(L,e) deterministically assigns all six reads of one expert to the same drive (readahead-coherent, no double cache), weighted by COLI_DISK_WEIGHTS=p,m (default 1:1). slot_load/mtp_slot_load read via st_read_raw_rep; expert_prefetch via st_prefetch_rep; [MIRROR] I/O split logged at shutdown. Dense FP8/FP4 loads stay on the primary (one-shot at startup). JustVugg#469: dsv4 load_model calls st_init_multi(COLI_MODEL_DIRS). All byte-identical (only residency/IO change, never compute); guarded no-op when the budget is unset, not streaming, or RSS within tolerance.
Summary
Decode on most boxes is disk-bound β a cold GLM-5.2 token streams ~11 GB of experts β and the weights are read-only. So if a machine has a second SSD, a plain copy of the model on it is untapped read bandwidth. This PR lets the engine stream experts from two copies at once:
COLI_MODEL=/fast/glm52_i4 COLI_MODEL_MIRROR=/second/glm52_i4 ./coli chat COLI_DISK_WEIGHTS=9,3 ... # optional primary,mirror ratio; default = startup bandwidth probeEach expert is routed to one drive by a deterministic
(layer,eid)hash, weighted either byCOLI_DISK_WEIGHTSor by a startup probe that measures both drives with the engine's own access pattern (parallel ~19 MB reads, O_DIRECT). Deterministic routing means the readahead/PILOTprefetch and the demand read always hit the same drive, and no expert is ever cached twice.Design notes
data_offsetsby construction, so everypreadis valid on either copy). Divergent or missing files are skipped per-file with a warning β a partial mirror works (a smaller second SSD holding only some shards still helps)..coli_usage,.coli_kv, and all sidecars stay on the primary.pread_full, inheriting the short-read/EINTR handling.MIRROR:stats line (GB + read counts per drive).Correctness
tests/test_st_mirror(inTEST_BINS) covers mirror validation, read equality on both replicas, and the rejection paths (divergent header, size mismatch, missing file).MIRRORunset, no code path changes.Measured performance
Hardware: Ryzen 9 3900X, 64 GB DDR4-3200, two NVMe drives (Samsung 990 PRO 4 TB + WD SN850X), each ~6 GB/s under the engine's parallel-19 MB access pattern (spec sequential numbers don't survive this pattern). GLM-5.2 744B int4,
DIRECT=1, drafts off.COLI_DISK_WEIGHTS=1,1on this symmetric pair).DIRECT=1(recommended together with the mirror), both copies bypass the page cache for expert reads and the measured cache footprint is the same as single-disk β the "two identical files fight for cache" failure mode doesn't occur. In buffered mode a warm, cache-bound workload can lose a few percent from the split cache; the docs say so.For context: every number in PR #418 (Vulkan backend) was measured on top of exactly this commit, mirror active for all configs β the two features compose.
Integration surface
st.h(+87: replica registration/validation, routed pread),glm.c(+164: routing at every expert read site, probe, stats),tests/test_st_mirror.c(+109), README section,docs/ENVIRONMENT.mdtable, small Makefile/coli/gitignore bits. +370/β15 overdev, two commits (feature + docs). Feature is entirely opt-in viaCOLI_MODEL_MIRROR.Limits