Commit Graph

200 Commits

Author SHA1 Message Date
JustVugg 3fdc6d394e Merge remote-tracking branch 'origin/dev' into pr259
# Conflicts:
#	README.md
2026-07-15 15:09:42 +02:00
Vincenzo 39a0777351 Merge pull request #261 from FECO-Admin/fix/randomuuid-fallback
web: fallback when crypto.randomUUID() is unavailable
2026-07-15 14:59:33 +02:00
Vincenzo 0fc18abd24 Merge pull request #265 from skeldoor/feat/interrupt-generation
glm.c/coli: Ctrl-C soft-stops the current turn instead of killing the engine
2026-07-15 14:59:24 +02:00
Vincenzo 87ea13c963 Merge pull request #264 from skeldoor/perf/neon-multiacc-sdot
glm.c: 4-accumulator NEON SDOT for int8/int4 expert dots (2.4x/core on Apple Silicon)
2026-07-15 14:59:12 +02:00
Vincenzo e871fb792d Merge pull request #266 from skeldoor/fix/coli-tiers-leak
coli: consume the engine's TIERS line at startup so it doesn't leak into the first answer
2026-07-15 14:57:08 +02:00
JustVugg da260c38cb server: clamp max_tokens to the server cap instead of rejecting it (fixes #260)
opencode / the ai-sdk OpenAI-compatible client sends a large default max_tokens
(> the server's --max-tokens cap, 1024 by default), and generation_options
returned 400 "must be an integer between 1 and 1024" — even for a trivial
"hello". OpenAI-compatible servers clamp to their own ceiling rather than
reject. Now max_tokens > limit is clamped to limit; only non-int / non-positive
values are a hard error. Test updated to assert the clamp + keep the <1 reject.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 14:56:01 +02:00
Skeldoor b79610852b coli: consume the engine's TIERS line at startup so it doesn't leak into the first answer
After the READY handshake, run_serve emits one TIERS status line (the
web-dashboard expert-pyramid snapshot). cmd_chat never read it, so in
interactive chat it surfaced as literal "TIERS 0 181 19275 0.00 ..." text
prepended to the first response. Read and discard that one line after READY,
matching how the other status frames are consumed. Chat-only; the HTTP serve
path already drains it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 13:37:36 +01:00
Skeldoor 3745cc680e glm.c/coli: Ctrl-C soft-stops the current turn instead of killing the engine
In serve/chat mode, Ctrl-C during generation killed the whole engine, losing
the loaded model and forcing a full reload. Now a SIGINT handler (armed only
in run_serve / run_serve_mux) sets a flag that spec_decode's token loop treats
exactly like hitting the NGEN cap: the turn ends through the normal path, so
the END sentinel, STAT line, usage_save and KV append all run — and :more can
continue the interrupted answer. The mux loop closes in-flight requests via the
same mux_done path. One-shot ./glm runs and Windows keep default SIGINT (die).

coli: stream_turn survives the first KeyboardInterrupt, forwards SIGINT to the
engine (covers non-TTY), drains to the turn boundary, and reports it. A second
Ctrl-C quits. Help line and per-turn footer updated.

POSIX only (sigaction); no behaviour change on Windows. Verified end-to-end on
Apple M4 + Metal: interrupt mid-decode, engine stays up, next prompt answers,
:q exits 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 13:37:02 +01:00
Skeldoor af421d6d3a glm.c: 4-accumulator NEON SDOT for int8/int4 expert dots (2.4x/core on Apple Silicon)
dot_i8i8 and dot_i4i8 accumulated the whole SDOT reduction into a single
int32x4_t. SDOT has ~3-4 cycle latency, so the serial dependency on `acc`
capped each core at ~26 GB/s (int8) / ~12 GB/s (int4) of weight throughput
regardless of memory bandwidth. Split into 4 independent accumulators (64
values/iter) so the loads become the bottleneck instead of the reduction
chain; the original single-acc loop is kept as the tail handler.

Measured on an Apple M4 (isolated microbench, expert-shaped 2048x6144):
  int8*int8  26.0 -> 63.2 GB/s/core  (2.4x)
  int4*int8  12.4 -> 29.9 GB/s/core  (2.4x)
Output is bit-identical to the previous kernels (verified over random
inputs). Non-DOTPROD NEON, AVX2/AVX-512/VNNI and VSX paths are untouched;
only the __ARM_FEATURE_DOTPROD branch changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 13:28:05 +01:00
rene 869e80ee2d web: fallback when crypto.randomUUID() is unavailable
Some browsers and insecure contexts (plain HTTP) throw on
crypto.randomUUID(), which silently breaks the chat UI.
Fall back to a plain UUID v4 generator when the native call
fails - same behaviour on modern browsers, works everywhere
else.
2026-07-15 14:20:50 +02:00
Vincenzo 5d4c3aa11b Merge pull request #257 from woolcoxm/windows-optimizations
Windows disk I/O: pread + PIPE + compat_fadvise (1.70s/tok, mmap reverted)
2026-07-15 13:42:23 +02:00
Vincenzo 4d0438314a Merge pull request #246 from woolcoxm/feat/diskio-kv-batching
Disk I/O: KV cache write batching + persistent file handle
2026-07-15 13:35:03 +02:00
Vincenzo a6895445a9 Merge pull request #254 from woolcoxm/feat/expert-budget
EXPERT_BUDGET=N: miss-aware cap on distinct experts/layer (+75% decode tok/s, 6x prefill on low-RAM hosts)
2026-07-15 13:34:19 +02:00
Vincenzo d9266e39e3 Merge pull request #248 from ZacharyZcR/serve/mux-kv-diag
serve: emit the [API] KV prefix-reuse diagnostic in mux mode (#153)
2026-07-15 13:22:57 +02:00
Vincenzo d99d1a7a0e Merge pull request #255 from ZacharyZcR/tools/quant-e8
tools: E8 lattice quantization in the ablation harness — the missing QuIP# ingredient (#81)
2026-07-15 13:20:44 +02:00
Vincenzo e51ce378ca Merge pull request #245 from ZacharyZcR/tools/atlas-unify
tools: one expert atlas, not two — retire expert_atlas.py, analyze.py gains --web output for the dashboard
2026-07-15 13:20:37 +02:00
Robert Landers 0b9ce242e4 fix wording
Signed-off-by: Robert Landers <landers.robert@gmail.com>
2026-07-15 12:42:13 +02:00
Robert Landers cf062010d8 use async and fix workers
Signed-off-by: Robert Landers <landers.robert@gmail.com>
2026-07-15 12:42:13 +02:00
Robert Landers 5d1eb142ab port uring implementation
Signed-off-by: Robert Landers <landers.robert@gmail.com>
2026-07-15 12:42:10 +02:00
woolcoxm 25219de45b windows: PIPE default ON + compat_fadvise WILLNEED cache-warmer (pread path)
Cut Windows decode disk I/O from 2.06s/tok to 1.70s/tok (budget=4), meeting the
<=2s/tok target. Two changes on the pread expert-load path:

1. compat.h: replace the posix_fadvise no-op with a real WILLNEED cache-warmer
   (overlapped ReadFile into a scratch buffer -> populates the standby page cache
   so the later synchronous pread faults from RAM). Re-arms the existing
   expert_prefetch/PILOT/next-block prefetch chain on Windows. DONTNEED stays a
   no-op (matches macOS; Windows standby-list trimming self-regulates).
   Measured: hit rate 16.4% -> 27.6%.

2. glm.c: flip PIPE (async expert-load thread pool) from default OFF to default ON
   on Windows. Dispatches expert pread onto worker threads so loads overlap the
   matmul, instead of blocking serial load-then-compute. PIPE=0 opts out.
   Measured: expert-disk 65.9s -> 54.3s (-18%).

Also adds compat_fadvise assertions to tests/test_compat_direct.c (data integrity
after cache-warmer, safe no-op on bad fd / non-WILLNEED).

mmap (CreateFileMapping/MapViewOfFile) was implemented and tested at length but
reverted: it regressed on Windows (RSS bloat from touched mapped pages collapsed
the expert cache via ullAvailPhys — a fundamental Windows-vs-Linux difference).
Full findings + the dead-end analysis recorded in issue_diskio.md.
2026-07-15 06:14:15 -04:00
ZacharyZcR 0189a9f0ae tools: E8 lattice quantization for the ablation harness — int{2,3,4,8}[-gN][-e8|-e8u][-rot] (#81) 2026-07-15 17:24:16 +08:00
woolcoxm 9c24b1eceb Merge branch 'feat/expert-budget' into windows-dev 2026-07-15 03:23:25 -04:00
woolcoxm dd0d60692d experiment: miss-aware EXPERT_BUDGET — keep all cache hits, only drop misses
The original budget dropped experts blindly — even cached ones that cost
zero disk I/O. The miss-aware version pre-scans pin/ecache residency
before applying the budget:
  - ALL cache hits are kept (free to compute, no disk I/O)
  - Only misses compete for the remaining budget slots
  - Miss budget = EXPERT_BUDGET - nhits (min 0)

Results (budget=4, same prompt/config as before):
                    Original    Miss-aware
  tok/s             0.33        0.36 (+9%)
  hit rate          16.4%       38.2% (2.3x)
  prefill           8.9s        5.6s (1.6x faster)
  decode            97.4s       88.9s (9% faster)

The hit rate doubling is the key quality signal: the model now gets the
full contribution from all resident experts plus the top-4 new loads,
instead of losing some hits to the budget.
2026-07-15 02:34:58 -04:00
woolcoxm 413b370fbf experiment: EXPERT_BUDGET=N — cap distinct experts/layer, up to 1.8x faster decode
Add EXPERT_BUDGET env var that caps the number of distinct experts loaded
per layer across the batch-union. When the union exceeds the budget, keeps
only the highest-aggregate-gate-weight experts and drops the rest from
idxs[] so they're never loaded from disk.

Complementary to TOPP (per-position) — this trims the cross-position union
that multiplies under MTP/prefill. Based on MoE-Spec (arXiv 2602.16052):
'top 32 of 64 experts capture 93% of routing weight.'

Measurements (GLM-5.2 744B, 24GB RAM, cap=2, MTP=0, 32 tokens):
  Baseline (budget=0):  0.18 tok/s, 9.3% hit,  176s decode, 39s prefill
  EXPERT_BUDGET=12:     0.19 tok/s, 14.0% hit, 171s decode, 12s prefill
  EXPERT_BUDGET=6:      0.26 tok/s, 21.0% hit, 123s decode,  7s prefill
  EXPERT_BUDGET=4:      0.33 tok/s, 16.4% hit,  97s decode,  9s prefill

Budget=4 nearly doubles decode speed (+83%) and 4x's prefill speed by
halving disk reads per layer. Default OFF (EXPERT_BUDGET=0).
2026-07-15 02:34:57 -04:00
woolcoxm 71c262ce1a diskio: KV write batching + persistent handle; generators: unfuse experts
Two independent fixes validated end-to-end on fresh fixtures:

1. KV cache disk I/O (issue_diskio.md opportunities #1 + #4):
   - kv_disk_append: fopen/fclose every turn -> persistent FILE* kept open
     for the engine lifetime, lazy open on first append, closed in
     serve_ctx_free. Eliminates per-turn handle creation overhead.
   - kv_disk_append: ~157 small fwrites per position -> one contiguous
     record memcpy'd into a staging buffer then a single fwrite per
     position. The staging buffer grows on demand via realloc.
   - kv_disk_truncate: closes the persistent handle before truncating
     so the file actually shrinks on disc, then reopens lazily.
   - KVState gains disk_fp, disk_buf, disk_buf_cap fields.
   - Verified: serve-mode round-trip, write 11 tokens then reload and
     resume with no re-prefill, then append 8 more and reload to 19.

2. Expert weight unfusing in test-model generators:
   - The real GLM-5.2-FP8 checkpoint stores routed experts UNFUSED as
     per-expert 2-D tensors, each with its own _scale_inv. HF fuses
     gate+up into a single 3-D gate_up_proj for compute efficiency.
   - The converter and C engine both expect the unfused layout. The
     fused 3-D tensors were silently skipped by the converter, and the
     engine crashed with missing-tensor errors.
   - New unfuse_experts in glm_fp8_emit.py splits gate_up_proj and
     down_proj into per-expert 2-D tensors. Called after reference
     generation but before saving, in both generators, both FP8 and bf16.
   - Also fixed: make_glm_oracle.py FP8 round-trip guard used p.dim()<2
     which let 3-D fused experts through and crashed fp8_block_quantize.
     Changed to p.dim()!=2 to match the converter ndim!=2 guard.

Validated full chain on fresh fixtures:
  generator --fp8 -> 570 e4m3 tensors + 629 scale_inv, was 90 when fused
  converter --group-size 0  -> per-row int4 fmt=2, engine loads clean
  converter --group-size 128 -> grouped int4 fmt=4, 8-16x more scales,
    engine loads clean, fmt=4 auto-detected in both mmap and slab paths
  dequant error: grouped 1.14-1.22x lower than per-row vs FP8 source
2026-07-15 02:32:12 -04:00
woolcoxm e71d4fbe29 windows-dev: CUDA build script, expert budget + grouped quant research
- build_cuda.bat: one-shot nvcc DLL compilation for sm_120 (RTX 5070 Ti)
- issue_budget.md: EXPERT_BUDGET research (cap distinct experts/layer, 2x tok/s)
- issue_grouped_quant.md: root cause of int4 incoherence (per-row vs group-128 scales)
- glm_fp8_emit.py: FP8 e4m3 test weight generator for converter validation
2026-07-15 02:32:12 -04:00
woolcoxm 2c6946c478 test-models: add --fp8 emission for the FP8->int4 converter test path
Both test-model generators (make_glm_oracle.py, make_glm_bench_model.py) can now
emit weights as FP8 e4m3 + 128x128 block scale_inv, in the same layout as the real
GLM-5.2-FP8 checkpoint. This lets convert_fp8_to_int4.py exercise its FP8->int4
dequant path on a local fixture without the 379 GB download.

- New shared helper glm_fp8_emit.py: FP8 block quantize/dequantize (FBGEMM/TE
  scale=amax/448 convention) + state_dict emitter. Only exactly-2-D tensors are
  quantized; 1-D/3-D and norms/router/e_score_correction_bias are kept as f32,
  mirroring the converter's classify() + ndim!=2 guard.
- make_glm_bench_model.py: opt-in --fp8 writes model.safetensors in FP8 layout
  (config.json written explicitly since the FP8 path bypasses save_pretrained);
  manifest gains a 'format' field. Default bf16 behavior unchanged.
- make_glm_oracle.py: opt-in --fp8 round-trips quantizable weights through FP8
  before computing ref_glm.json, so the reference reflects exactly the FP8 model
  the converter ingests. Default bf16 oracle contract unchanged.

Verified end-to-end: FP8 model -> converter --indir -> int4 U8 + .qs F32 output,
bit-identical dequant between helper and converter (maxdiff 0.0).
2026-07-15 02:32:12 -04:00
woolcoxm 21eb86a0dd diskio: research on disk I/O minimization for MoE expert streaming
Audit of all disk I/O paths in the engine (expert pread, KV persistence,
config/tokenizer loads) and research into techniques used by llama.cpp,
vLLM, AirLLM, PRESERVE, HOBBIT, SolidAttention. Findings:

- Expert path is already well-batched (one coalesced ~19MB O_DIRECT pread)
- 76% of decode time is expert-disk I/O on RAM-constrained hosts
- posix_fadvise(WILLNEED) is a no-op on Windows (compat.h:107)
- I/O-to-compute ratio is 3.6x — the binding constraint
- Levers: hit-rate (cache cap), cross-layer prefetch (PILOT_REAL),
  storage (VHDX vs direct NVMe), batched decode

Ranked opportunities and source links documented in issue_diskio.md.
2026-07-15 02:32:12 -04:00
woolcoxm 69f65d5173 Windows-dev: grouped quantization + mixed precision + expert budget + download tool
Consolidates all experiment branches into one Windows-dev branch:

1. Group-scaled int4 (fmt=4, gs=128) — glm.c
   - QT struct: added gs field
   - matmul_i4_grouped: AVX2 kernel, verified to 3e-08 vs f32
   - Format detection: auto-detects from .qs scale array size
   - expert_load: both mmap and slab+pread paths handle fmt=4
   - qt_bytes: fmt=4 case added

2. Per-tensor-type mixed precision — convert_fp8_to_int4.py
   - Split classify() into sh/o/kvb/attn/dmlp sub-types
   - New args: --shared-bits, --o-bits, --kvb-bits, --attn-bits, --dmlp-bits
   - Plan: shared expert + o_proj + kv_b_proj at int8, rest grouped int4
   - Only +5.3 GB RAM vs +0 for pure int4

3. EXPERT_BUDGET (miss-aware) — glm.c
   - Caps distinct experts per layer across batch-union
   - Always keeps cache hits, only drops misses
   - Up to 1.8x faster decode on low-RAM hosts

4. Two-step shared-expert prediction (PILOT_TWO) — glm.c
   - la_predict kind==2 + pilot_prefetch integration
   - +3.1% recall over baseline PILOT

5. FP8 download tool — download_fp8.py
   - ModelScope + HuggingFace dual-source
   - Parallel shard download with stall recovery

6. Tiny model generation — make_glm_oracle.py
   - Generated and tested locally for pipeline validation
2026-07-15 02:32:12 -04:00
woolcoxm e141db047d converter: per-tensor-type mixed-precision control
Split the resident weight classification into 5 sub-types so each can
get different precision:
  sh   = shared expert (highest sensitivity, fires every token)
  o    = o_proj (reconstructs output, biggest attn tensor)
  kvb  = kv_b_proj (reconstructs KV cache on every decode)
  attn = q_a/q_b/kv_a (other attention projections)
  dmlp = dense MLP (first 3 layers)

New args: --shared-bits, --o-bits, --kvb-bits, --attn-bits, --dmlp-bits
Each defaults to ebits (backward compat). When set, the converter applies
that precision to just that tensor type.

Research-backed plan: put the 3 compounding tensors (shared expert, o_proj,
kv_b_proj) at int8 and everything else at grouped int4. Extra RAM cost:
only +5.3 GB (those tensors are small vs the 372 GB expert pool on disk).
2026-07-15 02:32:12 -04:00
ZacharyZcR d04d99e039 serve: print the KV prefix-reuse diagnostic in mux mode too — [API] KV slot line existed only in the legacy \x02PROMPT path (#153) 2026-07-15 14:31:36 +08:00
woolcoxm e2ad6c72e6 diskio: KV write batching + persistent handle; generators: unfuse experts
Two independent fixes validated end-to-end on fresh fixtures:

1. KV cache disk I/O (issue_diskio.md opportunities #1 + #4):
   - kv_disk_append: fopen/fclose every turn -> persistent FILE* kept open
     for the engine lifetime, lazy open on first append, closed in
     serve_ctx_free. Eliminates per-turn handle creation overhead.
   - kv_disk_append: ~157 small fwrites per position -> one contiguous
     record memcpy'd into a staging buffer then a single fwrite per
     position. The staging buffer grows on demand via realloc.
   - kv_disk_truncate: closes the persistent handle before truncating
     so the file actually shrinks on disc, then reopens lazily.
   - KVState gains disk_fp, disk_buf, disk_buf_cap fields.
   - Verified: serve-mode round-trip, write 11 tokens then reload and
     resume with no re-prefill, then append 8 more and reload to 19.

2. Expert weight unfusing in test-model generators:
   - The real GLM-5.2-FP8 checkpoint stores routed experts UNFUSED as
     per-expert 2-D tensors, each with its own _scale_inv. HF fuses
     gate+up into a single 3-D gate_up_proj for compute efficiency.
   - The converter and C engine both expect the unfused layout. The
     fused 3-D tensors were silently skipped by the converter, and the
     engine crashed with missing-tensor errors.
   - New unfuse_experts in glm_fp8_emit.py splits gate_up_proj and
     down_proj into per-expert 2-D tensors. Called after reference
     generation but before saving, in both generators, both FP8 and bf16.
   - Also fixed: make_glm_oracle.py FP8 round-trip guard used p.dim()<2
     which let 3-D fused experts through and crashed fp8_block_quantize.
     Changed to p.dim()!=2 to match the converter ndim!=2 guard.

Validated full chain on fresh fixtures:
  generator --fp8 -> 570 e4m3 tensors + 629 scale_inv, was 90 when fused
  converter --group-size 0  -> per-row int4 fmt=2, engine loads clean
  converter --group-size 128 -> grouped int4 fmt=4, 8-16x more scales,
    engine loads clean, fmt=4 auto-detected in both mmap and slab paths
  dequant error: grouped 1.14-1.22x lower than per-row vs FP8 source
2026-07-15 02:28:33 -04:00
Vincenzo 3fd47b7bbd Merge pull request #242 from woolcoxm/feat/grouped-quant-fmt4
Group-scaled int4 (fmt=4): one scale per 128 elements — fixes incoherent output
2026-07-15 08:25:29 +02:00
ZacharyZcR e7cb7df501 tools: one expert atlas, not two — retire expert_atlas.py, analyze.py gains --web for the dashboard 2026-07-15 14:16:36 +08:00
JustVugg ff04b320d0 security(win): load coli_cuda.dll by absolute path, never from the CWD (DLL hijack)
coli_cuda_load did LoadLibraryA("coli_cuda.dll") with a bare name. Windows'
default search order includes the current working directory (and, without
SafeDllSearchMode, other writable locations), so an attacker who plants a
coli_cuda.dll where the user launches glm.exe — or inside a downloaded model
directory the user cd's into — gets their DllMain executed at load time:
DLL hijacking -> arbitrary code execution.

Now the loader resolves the path next to glm.exe via GetModuleFileNameA and
loads that exact file with LOAD_WITH_ALTERED_SEARCH_PATH, so both the DLL and
its dependency search are anchored to the trusted install directory. Fallback
(if GetModuleFileNameA ever fails) uses LOAD_LIBRARY_SEARCH_APPLICATION_DIR |
LOAD_LIBRARY_SEARCH_SYSTEM32 — which also excludes the CWD. Cross-compiles clean
under mingw-w64; the CPU path is unaffected (this file is _WIN32-only).

Note: grammar.h gr__rule memcpy was reviewed in the same pass and is safe (len
is clamped to 63 into a name[64] buffer).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:16:20 +02:00
JustVugg 4268e00fa1 security: harden safetensors/JSON parsers against malicious model files
A downloaded (supply-chain) model file was fully trusted by the loader. Three
memory-safety holes, all reachable by pointing the engine at a crafted shard —
demonstrated crashing on pre-fix, now rejected fail-closed:

st.h (safetensors):
- header length `hlen` (u64 from the file) was unbounded before malloc(hlen+1):
  a crafted value overflows (malloc(0) then hdr[hlen]=0 OOB) or forces a giant
  allocation. Now bounded to the file size and a 512 MB cap; malloc NULL-checked.
- json_get() returns NULL for missing/mistyped fields, but dtype/data_offsets/
  shape were dereferenced blind (off->kids[0]) — a header omitting data_offsets
  SIGSEGV'd (verified). Now type/arity-checked before use.
- data_offsets [a0,b0] were trusted: b0<a0 gave a negative nbytes -> malloc((size_t))
  giant and an oversized memcpy into the caller's buffer in st_read_f32 (heap
  overflow); off could point outside the file. Now validated 0<=a0<=b0 and
  data_start+b0<=filesize.

json.h: j_parse_val recursed with no depth limit -> stack overflow on nested
input like [[[[...]]]]. Added J_MAX_DEPTH=1024 (headers are ~3 deep); wide-but-
flat objects like the GLM header are unaffected (depth is decremented per return).

eval_glm.py: tempfile.mktemp() -> mkstemp() — closes the TOCTOU/symlink race on
a shared tmp dir (CWE-377).

Network path (openai_server.py + serve SUBMIT parser) audited separately and is
already sound: hmac.compare_digest auth, MAX_BODY cap, resolve()+relative_to
traversal guard, list-form subprocess, bounded/validated SUBMIT header. All 62
tests pass; valid GLM/OLMoE shards load unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:13:20 +02:00
woolcoxm 498ab0c20e experiment: group-scaled int4 (fmt=4) — one scale per 128 elements, not per row
Root cause of gibberish output: the int4 quantization uses one F32 scale per
output row (2048 scales for a 2048x6144 matrix). The FP8 source has 128x128
block scales — 48x finer. This destroys reasoning while keeping surface fluency.

Changes:
- Converter: quant_int4_grouped() with --group-size 128 arg. Same nibble
  packing, but one scale per group of 128 elements along the input dim.
- Engine QT struct: added 'gs' field (group size, 0=per-row backward compat)
- Engine qt_from_disk: auto-detects fmt=4 when scale array is O*ceil(I/128)
  elements instead of O. Old per-row models (fmt=2) work unchanged.
- Engine matmul_i4_grouped(): AVX2 kernel that applies per-group scales.
  Accumulator resets at each group boundary: dot(x[grp],w[grp]) * scale[grp].
- Engine matmul_qt_ex: dispatches to grouped kernel for fmt=4 (always exact,
  no IDOT approximation since the point is quality)
- Engine expert_load: both mmap and slab+pread paths detect fmt=4 from
  scale array size and set gs=128
- qt_bytes: fmt=4 reports correct memory including group scales

Backward compatible: existing per-row int4 models work unchanged.
The fused gate+up pair path (matmul_i4_pair) falls back to separate
matmul_qt calls for fmt=4 — minor perf cost, correctness preserved.
2026-07-15 02:05:18 -04:00
Vincenzo a8895f2d84 Merge pull request #193 from bokiko/feature/disk-split
stats: opt-in disk-load split by speculation context and layer kind (DISK_SPLIT=1)
2026-07-15 07:59:33 +02:00
JustVugg c71e5d1cf8 glm: honest short-read reporting in expert pread; Makefile: -pthread for *BSD
Two small correctness fixes surfaced by community reports on non-Linux hosts:

#236 — expert_load's buffered pread paths (slab + qs scales) used
perror("pread expert") on a short read. Since pread returned a short count
(not -1), errno stays 0 and perror prints "Success" — a confusing message
right before exit(1) in the score/bench path. New pread_full() helper loops
over short reads and EINTR and reports actual/expected bytes and offset, so a
truncated shard reads as such instead of "Success".

#219 — Linux pulls -pthread in via -fopenmp; the *BSDs do not, so pthread_*
fail to link there. Added -pthread to the generic (Linux/*BSD x86-64) and
aarch64 CFLAGS/LDFLAGS. No-op on Linux, required on FreeBSD (complements #206).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 07:55:09 +02:00
JustVugg ddf9212b81 glm: PIN_GB=all clamps to the --ram budget instead of pinning every expert (fixes #229)
PIN_GB=all passed gb=-1.0 to pin_load, which took npin=n (all experts) and
ignored --ram entirely — the OOM-kill regression from #80. A 92 GB host with
--ram 78 was killed mid-generation (anon-rss ~89 GB). Now gb<0 clamps npin to
expert_avail() — how many experts fit the RAM budget, same accounting AUTOPIN
uses. pin_load already adds the pinned bytes to resident_bytes, so the later
cap_for_ram narrows the LRU accordingly with no double count.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 07:50:48 +02:00
Vincenzo 22c91403b7 Merge pull request #228 from bokiko/feature/score-prefix-default
engine: SCORE mode defaults the [gMASK]<sop> prefix ON for GLM snapshots (#194 follow-up, #108)
2026-07-15 07:47:46 +02:00
Vincenzo 00902a2ea9 Merge pull request #224 from michael-denyer/fix-kv-alloc-double-free
glm: fix kv_alloc double-free on KV re-allocation (stale pre-Metal free block)
2026-07-15 07:47:16 +02:00
Vincenzo 5025f00428 Merge pull request #194 from bokiko/feature/glm-prefix-default
eval: default the [gMASK]<sop> prefix ON for GLM snapshots (#108)
2026-07-15 07:46:47 +02:00
Dennis Paul a28f31fa3b tools: expert_atlas — confound-controlled probe harness for the GLM-5.2 expert atlas (#175) (#218)
Probe sweep + affinity analysis + leave-one-prompt-out validation, so anyone can build and
cross-validate the atlas on their own box rather than trusting one machine.

The four traps this harness exists to control (each silently corrupts the atlas):

  --topp     prunes experts by cumulative probability. Measured, same prompt:
             topp=0   -> 21,000 selections across 7,587 distinct experts
             topp=0.7 -> 11,944 selections across 4,687 distinct experts
             It hides 38% of the experts, and it is the recommended speed setting.
  MTP/DRAFT  eusage is incremented inside moe(), BEFORE verification, so rejected
             speculative drafts count experts routed for text never emitted.
  .coli_usage is loaded at startup and accumulates, so a naive STATS dump contains all
             prior history rather than this run.
  autocorrelation: routing within one run is highly correlated, so an expert firing 38
             times during one prompt is ONE observation, not 38. Entropy/chi-square on raw
             selections certifies single-prompt flukes as perfect specialists — analyze.py
             therefore requires affinity to replicate across a category independent prompts.

Result on GLM-5.2 744B int4 (Zen5, CPU routing path), 10 topics x 3 prompts x 64 tokens:

  leave-one-prompt-out accuracy   29/30 = 96.7%   (chance 10%)
  strong specialists (spec>=0.5)  1,041 / 13,260  (7.9%)
  specialisation vs depth         layer 3 ~0.07 -> layers 18-58 ~0.19-0.27
  replication gate rejected       587 single-prompt flukes

The one miss is the interesting part: a Chinese-language poetry prompt classifies as poetry,
not Chinese — routing follows the task over the language.
2026-07-15 07:41:24 +02:00
Michael Denyer 416570339f tests: remove accidentally committed Linux test binaries + gitignore them — fixes make test-c on non-Linux (#226)
c/tests/test_schema_gbnf and c/tests/test_compat_direct were committed
as Linux x86-64 ELF executables (slipped in via #111). On any other
platform make test-c considers them up to date and execs them, failing
with OSError: [Errno 8] Exec format error. Remove them and add the two
names to .gitignore alongside the other test binaries already listed,
so each platform rebuilds its own.
2026-07-15 07:40:42 +02:00
Michael Denyer 2bdf054622 glm: fix kv_alloc double-free on KV re-allocation (stale pre-Metal free block)
kv_alloc had two consecutive if(k->Lc) free blocks. The first freed every
k->Lc[i]/k->Rc[i] and both arrays without unregistering from Metal and
without nulling k->Lc; the second then re-tested the dangling pointer,
called coli_metal_unregister on freed pointers, and freed everything a
second time. Safe only when k->Lc is NULL (first call); any re-allocation
on the same KVState aborts in the allocator.

The first block is the pre-Metal version of the free path: 3716e40 (Metal
backend) replaced it with the Metal-aware block, and ec89136 (GPU resident
pipeline) re-added it above during the merge. Delete it so the Metal-aware
block is the only free path.

Caught by tests/test_kv_alloc from the previous commit:
  before: malloc: *** error for object 0x7: pointer being freed was not allocated (exit 134)
  after:  OK kv_alloc re-allocation (exit 0)
2026-07-15 00:33:45 +01:00
Michael Denyer 69004f73c2 tests: kv_alloc re-allocation regression test
kv_alloc guards every KVState free with if(k->Lc) precisely so it can be
called again on the same KVState (context resize, slot re-init). Exercise
that path: allocate, touch the cache, allocate again at a larger size.

Fails at this commit with a double-free (fixed in the next commit):
  malloc: *** error for object 0x7: pointer being freed was not allocated
2026-07-15 00:32:58 +01:00
Marquez 62419af188 routing: opt-in CACHE_ROUTE max-rank prefer (pin∪LRU), default off — 2.4->3.33 tok/s on GB10 (#199)
Paper-style cache-aware MoE selection (arXiv:2412.00099 max-rank):
keep true top-J always; fill remaining K slots preferring experts already
resident in pin∪LRU within top-M. Default OFF so stock full top-K is
unchanged.

Env: CACHE_ROUTE, ROUTE_J/M/P/ALPHA, ROUTE_AGREE (auto-on with CACHE_ROUTE).
Telemetry: swap%/route_swaps/route_slots, route_agree, route_kl on footer
and serve STAT. Complementary to PILOT (prefetch vs selection change).

Routing-only PR for clean A/B vs PILOT / #119; no CUDA/fuse stack.
See docs/CACHE_ROUTE.md. Closes nothing; for #161 discussion.

Co-authored-by: Vincent Marquez <vincentmarquez405@gmail.com>
2026-07-14 22:08:28 +02:00
yuri@FreeBSD 4a936c8af3 FreeBSD compatibility: platform guards in glm.c/olmoe.c (#206) 2026-07-14 22:08:12 +02:00
bopof bfd49c41b9 Makefile: select $(PYTHON) from the host, not the target triple (#205)
clean/test-c/test-python run python on the build host, but $(PYTHON) was
chosen from $(IS_WIN), which is derived from the target triple
($(CC) -dumpmachine). A Linux->mingw cross build (make CC=x86_64-w64-mingw32-gcc
...) therefore sets IS_WIN and picks `python`, which fails on hosts where only
`python3` exists (e.g. Debian/Ubuntu) — breaking the cross-compile path #171
added.

Key PYTHON off the host instead: $(OS)=Windows_NT (the #129 signal) is set in
every Windows shell and empty on Linux/macOS. EXE stays driven by the target
triple, as it should. Addresses @rofl0r's host/target note on #171.

Co-authored-by: bopof <285767350+bopof@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:07:56 +02:00