Commit Graph

20 Commits

Author SHA1 Message Date
Vincenzo Fornaro 73badea697 Merge pull request #429 from ZacharyZcR/fix/numa-vma-419
numa: per-layer pin arenas — fix the PIN_GB=all VMA explosion (#419)
2026-07-20 18:08:30 +02:00
Vincenzo Fornaro 06961728d8 Merge pull request #342 from ZacharyZcR/feat/expert-group-overlap
cuda: COLI_GROUP_ASYNC=1 — overlap the CPU expert rows with the GPU groups at decode (opt-in, +6-8%)
2026-07-20 18:07:02 +02:00
Vincenzo Fornaro e78fcfcbca Merge pull request #451 from ZacharyZcR/feat/cuda-grouped-g64
cuda: grouped-int4 (fmt=4) support in the expert-group kernels — opens the GPU tier to g64 and E8 containers (#334)
2026-07-20 18:06:45 +02:00
Vincenzo Fornaro 78a773e539 Merge pull request #432 from ZacharyZcR/feat/cuda-device-router
cuda: COLI_CUDA_ROUTER=1 — route the decode row on the layer's home device (#431 PR-A)
2026-07-20 18:06:30 +02:00
JustVugg d34461c8d8 Merge remote-tracking branch 'origin/dev' into pr270
# Conflicts:
#	c/Makefile
2026-07-20 17:51:50 +02:00
ZacharyZcR 57e67d6c2f numa: skip binding the GPU-prefix staging slabs
The VRAM-ranked prefix's host slabs are upload staging — read once and
freed right after — so binding them buys nothing and cost ~2 transient
VMAs each: measured PEAK maps 28,958 on the six-GPU host even with the
pin arenas in place. With the skip: 11,771, all of it the bounded LRU
ecache, which serves decode reads and stays correctly bound.
2026-07-20 10:17:35 +08:00
ZacharyZcR 536d8bfd1a numa: per-layer pin arenas — fix the PIN_GB=all VMA explosion (#419)
Root cause of #419's 'OOM slab': every per-slab mbind carries its own
memory policy, so bound regions cannot merge — measured ~2 VMAs per slab,
with or without MPOL_MF_MOVE. A PIN_GB=all load (19,456 experts x
slab+fslab) creates ~78k VMAs and crosses the default
vm.max_map_count=65530: posix_memalign dies with terabytes free.

The fix binds the pinned hot-store as ONE arena per layer. Experts of a
layer share a tensor shape, so a layer's pins pack at a fixed stride into
two arenas (weights + scales): 2 mbinds and a handful of VMAs per layer
instead of ~500. Slices are pre-attached to the slots before the load
loop — slab_cap covers expert_load's realloc check, so its alloc branch
never fires and expert_load itself is untouched. aslab marks arena
ownership: expert_host_release detaches instead of freeing (a REPIN
gpu-swap promotion must not free() an interior pointer), and
expert_host_ensure re-attaches the slice before reloading.

Per-slab mbind remains for the bounded allocations (dense qalloc, LRU
ecache, GPU-tier staging), now without MPOL_MF_MOVE: every bind lands
before the pread that first-touches the pages, so there is nothing to
migrate.

numa_init also gains a capability probe done right: one page-aligned
page (mbind rejects unaligned addresses with EINVAL), disabling only on
errno==EPERM — so a constrained container degrades with a message
instead of crashing later, and an EINVAL can never masquerade as a
missing capability.

The arena path activates only when interleave is actually on
(g_numa_nodes>=2, Linux, non-mmap): default builds stay byte-identical.
2026-07-20 10:16:53 +08:00
ZacharyZcR a74e3e0c3a cuda: grouped-int4 (fmt=4) support in the expert-group kernels (#334)
The grouped MoE kernels were per-row-only: GroupDesc had no group-size
fields, row_bytes() returned 0 for fmt=4, the scale buffer was hardcoded
to O floats, and a fmt=4 group that reached the generic path would have
been silently decoded as int2. This closed the GPU expert tier to every
grouped container — including the g64 quality line (#225) and the E8
lattice route (#347) whose whole point is fitting more experts in VRAM.

- ColiCudaTensor gains gs/scale_count; upload allocates O*ceil(I/gs)
  scales for fmt=4 and applies the same offset->signed nibble conversion
  as fmt=2 (identical packing). New ABI entry coli_cuda_tensor_upload_g
  carries gs without touching the existing symbol — an old Windows DLL
  missing it returns 0 and the tensor simply stays CPU-side.
- GroupDesc gains per-tensor group sizes; new grouped_hidden_g4_dual /
  grouped_down_g4 apply the per-group scale inside the accumulation
  (gs is required even, so a packed byte never straddles groups; gs=0
  degrades to per-row, letting fmt=2 members ride the same launch).
- coli_cuda_expert_group routes any group containing fmt=4 through the
  g4 kernels; pure-fmt=2 groups keep the existing paths byte-identical.
  The generic fallback now explicitly rejects fmt=4 instead of decoding
  garbage (#334's prevention note, made real).

tests/test_grouped_g4_cuda.cu: kernel-vs-CPU oracle over 50 trials x 3
experts — gs=64, a non-divisible tail group (200 % 64), and a per-row
member in the same launch: zero mismatches on a 5090.

make check 77/77; CPU, CUDA and MinGW builds clean.
2026-07-20 08:56:49 +08:00
Vincenzo Fornaro e9b36141a4 Merge pull request #449 from cdhdt/fix/rss-guard-uaf
rss_guard: free the evicted expert slab under g_pilot_mx (fixes a use-after-free with the pilot prefetcher)
2026-07-20 02:29:06 +02:00
cdhdt 9c5ab39b62 rss_guard: free the evicted expert slab under g_pilot_mx (use-after-free)
rss_guard marked the victim slot eid=-1 under the lock, unlocked, and only then
freed s->slab. In that window the slot reads as {eid=-1, slab still valid} --
exactly the state pilot_realload's victim scan reuses first -- so a pilot worker
could claim it and pread into the slab while it was being freed: use-after-free,
or a double-free if the loader took its own realloc path.

Keep the free and the pointer/capacity NULLing inside the critical section, so
'slab valid' and 'slot reusable' are never simultaneously observable. The lock is
held across a free() (microseconds); workers only take it briefly for scan+reserve.

Reproduces on dev with a SINGLE pilot worker (rss_guard runs on the main thread
while the pilot worker runs in the background), whenever the RAM guard is active
(RSS_GUARD_GB, or any resolved g_ram_budget_gb).

make check 83/83, native + portable builds 0 warnings.
2026-07-20 01:27:28 +02:00
Vincenzo Fornaro 4b704823db Merge pull request #445 from ZacharyZcR/fix/pin-budget-release-host
pin: with CUDA_RELEASE_HOST the VRAM prefix must not consume the RAM pin budget — +72% on 6×5090
2026-07-20 00:03:13 +02:00
ZacharyZcR 453d1401ba pin: with CUDA_RELEASE_HOST the VRAM prefix must not consume the RAM pin budget
pin_load computed npin from the RAM budget FIRST, then carved the
VRAM-ranked prefix out of those npin slots. With CUDA_RELEASE_HOST the
prefix's host slabs are freed right after upload — so on a multi-GPU
host the top-ranked experts consumed the RAM budget without occupying
RAM, and the CPU tier pinned only the leftovers. Measured on 6x RTX 5090
(251 GB): 9,280 VRAM + only 1,721 RAM pins (32.5 GB warm) on a box whose
RAM fits ~10k more — the cold tail then paid disk on every token and the
hit rate ceilinged at 99.0% forever.

Move the VRAM budget estimate above the npin finalization and make the
release-destined prefix ADDITIVE to the RAM-derived count. Same box,
same env plus the fix:

    [PIN] placement: 9,280 VRAM + 10,176 RAM (192.5 GB warm)
    expert hit rate 100.0% (pin 100.0% + lru 0.0%)  — disk 0

    1,024-token greedy decode: 3.62 -> 6.21 tok/s  (+72%)
    warm late segment (t=768-1024): 5.11 -> 5.73 tok/s  (+12%)
    prefill: 10.4 -> 8.8 s

The whole-run gain is the LRU warmup penalty disappearing (full
residency from the first token); the late-segment gain is the steady
~0.9%-miss disk residue recovered. Non-release configs are untouched:
prefix_est stays 0 and the arithmetic reduces to today's exactly.
2026-07-20 05:15:45 +08:00
Vincenzo Fornaro a7ef058fc0 Merge pull request #438 from monotophic/e7/disk-class-instr
PROF: DISK-CLASS — per-load cold/warm disk instrumentation
2026-07-19 22:40:22 +02:00
monotophic 1f00142b25 PROF: DISK-CLASS — per-load cold/warm disk instrumentation (re-derived onto #391 split)
Re-derivation of e7/disk-class-instr @ de6dd6d (base caa49f7) onto
origin/dev @ 61004dc, after PR #391 split c/glm.c into c/colibri.c plus
quant.h/sample.h/kv_persist.h/telemetry.h/grammar.h. Semantic equivalence,
not a copy: same events, same accounting, re-sited onto the new tree.

Placement: colibri.c, unchanged from before the split. telemetry.h (#391)
is the dashboard/stats module (HWINFO/TIERS/EMAP/HITS protocol lines +
usage persistence) — prof_report(), expert_load_impl(), moe(), g_prof_io
and g_edisk_ns all stayed in colibri.c, so DISK-CLASS's aggregation and
printing follow them there. No relocation needed, no DEVIATION.

Read path: #362 (prefetcher-v3, 22509fc) turned out to be testbed-only
scope per its own merge message ("glm.c untouched") — confirmed zero
c/glm.c changes in that merge's diff. The seven expert_load() call sites
this patch touches (pipe_worker, expert_host_ensure, moe()'s OMP miss
loop, pilot_realload, repin_pass_limit, pin_load x2) are structurally
identical to the pre-split tree; the demand flag re-attaches at the same
sites with the same semantics (1 only at moe()'s own PIPE/OMP miss path,
0 everywhere else), so DISK-CLASS still counts demand loads only.

One real drift, unrelated to #362: #417 (cfcc742) fixed the exact "Metal
pre-routed FASE A never bumps the real elast/eaccess_clock" defect this
feature's comments described as a documented, deliberately-unfixed
upstream issue — the real clock now ticks in FASE A too. The private
elast_dc/eaccess_clock_dc clock is kept anyway: its job was never only
to route around that freeze, it also snapshots pre-bump state so a
call's own routing bump can't contaminate its own classification, and
keeping DISK-CLASS's bookkeeping fully separate from stock elast state
is what makes "byte-identical with PROF=0" provable by construction
instead of by argument. Code comments referencing the old defect are
updated to reflect the fix (historical note + #417/cfcc742 pointer)
rather than describing a bug that no longer exists.

Gates: make glm METAL=0 and METAL=1 both clean, zero warnings (matches
stock 61004dc, also built clean with zero warnings for comparison).
make test-c: 0 failures. make test-python: 77 tests, OK.

Authored by Fable 5 in Claude Code, analysis in partnership with
@Monotophic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:42:57 -04:00
ZacharyZcR 113ece3bc7 cuda: COLI_CUDA_ROUTER=1 — route the decode row on the layer's home device (#431 PR-A)
First increment of the #431 plan (device router -> indirect kernels ->
one-graph decode). At decode (S=1) on the pipe2 path, the router runs on
the layer's home device: a tiny E x D logits GEMV + sigmoid, then a
single-thread selection kernel that clones moe()'s plain routing path
verbatim — bias-augmented top-K by choice with strict-> tie-breaking,
weights from the raw logit, route-level TOPP truncation, norm_topk,
routed_scale. Results pack into one scratch buffer and come back in a
single ~68-byte D2H; moe() consumes them through the same pre-routed
shortcut the Metal layer-CB uses (g_pre_idx, #417 bookkeeping included),
so usage/heat/recency accounting is identical to the CPU router.

Structural value: routing becomes available ON the device timeline,
which is what PR-B (indirect expert kernels, static topology) and PR-C
(whole-decode CUDA Graph) build on.

Opt-in, default off. Gated to the plain routing path — CACHE_ROUTE,
ROUTE_P and ROUTE_TRACE keep the CPU ranking they need; any upload or
launch failure falls back to the CPU router silently. Router weights
(E x D f32, ~6.3 MB/layer) upload lazily to the layer's home device.

tests/test_router_cuda.cu: kernel-vs-CPU-reference oracle over 200
random trials (mixed TOPP/norm_topk/scale): 200/200 exact selections,
zero near-tie flips, zero weight mismatches on a 5090.
2026-07-20 02:53:43 +08:00
ZacharyZcR 1d9f554715 cuda: cache layernorm weights on-device in pipe_layer_sparse (kill 152 sync H2D/token) + overlap-window profiling counters 2026-07-19 23:19:25 +08:00
ZacharyZcR ab55f4900c cuda: COLI_GROUP_ASYNC=1 — async expert-group issue/take with CPU/GPU overlap at decode (opt-in, +6-8% measured) 2026-07-19 23:19:25 +08:00
Nicholas Beerbower 4586d33c60 fix: MTP-head probe uses the last expert by index, not a hardcoded 255
The has_mtp completeness probe checked for `mlp.experts.255.down_proj.weight`,
which only exists when n_routed_experts == 256. REAP-pruned checkpoints (and any
MoE with a different expert count) have fewer experts, so the probe spuriously
reported has_mtp=0 and disabled MTP speculative decode even though the head was
present and complete. Probe `mlp.experts.<n_experts-1>` instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 10:54:01 -04:00
ZacharyZcR bc69a9a6d0 fix: remove duplicate argmax_v — use NaN-safe version from sample.h 2026-07-19 21:15:54 +08:00
ZacharyZcR f853ea8a0b refactor: split glm.c into colibri.c + 4 header modules
Rename glm.c → colibri.c and extract four self-contained modules
into header-only files (same pattern as st.h/tier.h/grammar.h):

  quant.h      (672 lines) — SIMD matmul kernels, quantization
  sample.h     (143 lines) — RNG, top-p sampling, stop-set
  kv_persist.h (121 lines) — .coli_kv disk persistence
  telemetry.h  (189 lines) — dashboard protocol, stats, usage

Main engine file shrinks from 6588 to 5396 lines (−18%).

Build system: primary target is now colibri$(EXE); `make glm`
remains as a phony alias for backward compat. CI, setup.sh,
coli CLI, and all 10 test files that include the engine are
updated. make check passes (C + Python, 73 tests, zero warnings).
2026-07-19 21:12:04 +08:00