Commit Graph

371 Commits

Author SHA1 Message Date
JustVugg 00ed8d5aa4 docs: surface the website (badge + header link) in README
Site (justvugg.github.io/colibri) is live but linked nowhere. Add website +
latest-release badges and a Website header link. README-only, no code change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 18:49:16 +02:00
Vincenzo Fornaro a9eb839868 Merge pull request #462 from JustVugg/site-to-main
site: official website + Pages deploy on main (#424)
2026-07-20 17:44:11 +02:00
JustVugg c6801451bd site: bring the official website + Pages deploy workflow to main (#424)
The site.yml workflow deploys on push to main, so the site only goes live once
site/ is on main. Bring site/index.html + icons + the workflow so a GitHub
Pages deploy (Settings -> Pages -> Source: GitHub Actions) publishes it at
justvugg.github.io/colibri.

Refs #424

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:43:46 +02:00
Vincenzo Fornaro ff93ae2bc1 Merge pull request #461 from JustVugg/docs-to-main
docs: Quick Start guide + Windows prebuilt steps on main (#450)
2026-07-20 17:36:39 +02:00
JustVugg ec087f6bca docs: bring the Quick Start guide (incl. Windows prebuilt steps) to main
main shipped v1.0.0 with prebuilt Windows binaries but no docs on main
explaining what the .exe is or how to run it (#450) — the quickstart lived
only on dev. Bring docs/quickstart.md to main and add a Windows-prebuilt
pointer to the README's Get started section, so users on the default branch
find the steps. Docs-only.

Refs #450

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:36:06 +02:00
Vincenzo Fornaro 86766bf989 Merge pull request #427 from DavidePapero/main
Added Dockerfile and instructions for using Colibrì through a docker container
2026-07-20 17:20:26 +02:00
Davide Quack 27b4c4263a Added Dockerfile and instructions for using Colibrì through a docker container 2026-07-19 16:49:29 +02:00
Vincenzo Fornaro 72874f38a2 Merge pull request #411 from JustVugg/dev
release: fix the Windows job shell in release.yml (v1.0.0 tag build)
v1.0.0
2026-07-19 12:30:32 +02:00
JustVugg 3cd2674f68 release: fix the Windows job shell — 'msys2 {0}', not 'msys2' (+ inherit PATH for 7z)
GitHub Actions shells are format strings; the bare 'msys2' string made the
v1.0.0 tag build fail before its first step ('Invalid shell option'). Same
invocation ci.yml already uses. path-type: inherit so the Package step can
reach the runner's 7z.exe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 12:29:33 +02:00
Vincenzo Fornaro 819941ee2c Merge pull request #400 from JustVugg/dev
Release v1.0.0
2026-07-19 12:25:43 +02:00
Vincenzo Fornaro e25aeecc4c Merge pull request #410 from JustVugg/p403-rss-guard
glm: measured-RSS guard — the RAM budget enforces itself at the safe point (#403)
2026-07-19 11:29:11 +02:00
Vincenzo Fornaro 2f8fefd701 Merge pull request #408 from JustVugg/p401-tools-e2e
serve: end-to-end tool-calling regression test + unparsed-marker diagnosis (#401)
2026-07-19 11:06:14 +02:00
JustVugg c90e2cc438 glm: measured-RSS guard — the RAM budget enforces itself at the safe point (#403)
cap_for_ram's projection is an estimate: on the GB10 (#403) long generations
overshot it by ~40 GB (projected 74.4, real 115.6) and the kernel killed the
engine three times. Run D of the issue proves a low cap CONTAINS the growth;
this guard does that automatically, keyed on MEASURED RSS instead of the
projection.

At the repin safe point (no moe in flight), every ~16 emitted tokens: if RSS
exceeds the resolved budget (RAM_GB/auto, or an explicit RSS_GUARD_GB
ceiling), free the least-used LRU expert slabs in place and lower ecap so the
cache cannot regrow. Slabs are >128 KB so glibc returns the pages to the
kernel immediately -- RSS actually drops.

Eviction never compacts the array: with PILOT_REAL the pilot worker holds
pointers into ecache[] across its preads, so the slot stays in place with
eid=-1/used=0 (first candidate for reuse); reserved slots (eid<0) are never
touched and victim selection happens under g_pilot_mx. resident_bytes is left
alone: LRU slots are never accounted there (only pin + dense).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 11:02:31 +02:00
JustVugg 0f606bc446 test: skip the tools e2e suite on Windows (shebang mock engine)
CreateProcess cannot exec a shebang script, so the gateway exits during
setUpClass on the windows CI job. The gateway logic under test is
platform-independent and stays covered by the POSIX jobs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 11:01:26 +02:00
JustVugg 2122c004d9 serve: end-to-end tool-calling regression test + unparsed-marker diagnosis (#401)
The gateway's tool-calling path had unit coverage (parse_tool_calls,
render_chat) but nothing exercised the real subprocess wire protocol or the
HTTP surface a coding client actually hits. #401 reports plain-text replies
where tool_calls were expected; every documented path checks out, so pin the
whole path down with a mock engine speaking SUBMIT/DATA/DONE and assert:

- non-stream: tool_calls populated, finish_reason tool_calls, no raw markers
- stream: markers suppressed across 20-way chunk splits, tool_calls delta
- tool-result round trip: <|observation|><tool_response> rendering, text reply
- no tools: plain text untouched

Also emit a stderr diagnosis when tools are declared and tool-call markers
are present in the reply but the strict parse matches nothing (typically
quantization-mangled output) pointing at COLI_TOOL_SALVAGE=1 -- the likely
field condition behind #401.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 10:56:00 +02:00
JustVugg d94c68149d Merge pull request #352 from woolcoxm/fix/test-stops-windows
fix(test): make test_stops build on Windows (mkdtemp compat shim)

Conflict with #366's getenv_utf8 in compat.h resolved by keeping both blocks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 10:42:41 +02:00
Vincenzo Fornaro 66b5e57bc4 Merge pull request #397 from NeuralNotwerk/fix/mtp-int8-repair
tools: repair int4-converted MTP heads in place; warn on --mtp --ebits <8
2026-07-19 10:40:07 +02:00
Vincenzo Fornaro 3f99d2bbb7 Merge pull request #395 from Stonki13/fix/windows-cuda-detection
Fix CUDA detection on Windows (coli/doctor.py always report CPU-only)
2026-07-19 10:40:01 +02:00
Vincenzo Fornaro 19bab420a0 Merge pull request #404 from anrasi/fix/indir-meta-resume
convert(fp8->int4): --indir copies the full metadata set and resumes interrupted passes (#383)
2026-07-19 10:39:56 +02:00
Vincenzo Fornaro 36d389bf50 Merge pull request #402 from anrasi/fix/coli-run-think
coli: honor THINK=1 in run mode
2026-07-19 10:39:50 +02:00
JustVugg 6690c4f58e Merge branch 'p394' into trialsafe 2026-07-19 01:28:16 +02:00
JustVugg e5b1e19022 Merge branch 'p366' into trialsafe
# Conflicts:
#	.github/workflows/ci.yml
2026-07-19 01:26:51 +02:00
JustVugg 4d780e2b46 Merge branch 'p378' into trialsafe 2026-07-19 01:25:40 +02:00
Stonki13 d2d3a7b559 Fix CUDA detection on Windows: cuda_binary()/cuda_linkage() always returned False
Both coli's cuda_binary() and doctor.py's cuda_linkage() detect CUDA support
by running `ldd` on the engine binary and looking for a linked libcudart —
but that's Linux-only (cuda_binary() checks sys.platform != "linux", and
cuda_linkage() checks os.name != "posix", both short-circuiting to False on
win32). Windows CUDA_DLL=1 builds never link libcudart at all: glm.exe loads
coli_cuda.dll dynamically via LoadLibrary at startup (backend_loader.c), so
there's no import-table entry for ldd/dumpbin to find in the first place.

The practical effect: `coli doctor` always reported "NVIDIA GPU detected but
the engine is CPU-only" on Windows, and `coli run/chat/serve --gpu ...`
always hard-exited with "--gpu needs the CUDA build" — even on a correctly
built CUDA_DLL=1 binary with coli_cuda.dll sitting right next to glm.exe.

Fix: on win32, detect a COLI_CUDA build by scanning glm.exe for the marker
string "[CUDA] mode: routed experts", which only exists in code compiled
under #ifdef COLI_CUDA (see glm.c's cuda init block), then confirm
coli_cuda.dll actually sits next to the binary — mirroring the Linux
"linked but missing" distinction. Linux/macOS detection is unchanged.

Verified on Windows 11 with mingw-w64 GCC 16.1.0 + CUDA 13.2 + RTX 5080:
`coli doctor` now reports "CUDA engine and devices are available", and
`coli run --gpu 0 --vram 8` populates VRAM (confirmed via the engine's own
"[CUDA] resident set: N tensors, X GB VRAM" runtime log) instead of exiting.
2026-07-18 23:18:16 +02:00
ZacharyZcR 05bba7994c release: version infrastructure + GitHub Release workflow
- c/version.py: single source of truth (__version__ = "1.0.0")
- coli: reads version.py, banner shows dynamic version, --version flag
- .github/workflows/release.yml: tag push triggers cross-platform build
  (Linux x86_64, macOS ARM64, Windows x86_64) and creates a GitHub
  Release with packaged binaries + changelog notes
- CHANGELOG.md: v1.0.0 baseline documenting all shipped features

To cut a release:
  1. bump c/version.py
  2. add a CHANGELOG section
  3. git tag v1.0.0 && git push --tags
2026-07-19 05:01:41 +08:00
recviking d6676c10e9 tools: repair_mtp_int8.py — fix int4-converted MTP heads in place; warn on --mtp --ebits <8
Per-row int4 quantization of the MTP head's eh_proj [D,2D] zeroes its ENTIRE
embedding half: the tensor's two column halves differ in scale by ~20-30x per
row (embedding-half absmax ~0.05, hidden-half ~1.5 on GLM-5.2), so a single
per-row scale (absmax/7) puts every embedding-half weight below half a
quantization step and np.rint rounds it to exact zero (packed bytes 0x88).
The draft head then cannot see the input token and MTP acceptance collapses
to ~0% — the mechanism behind issue #8's measurement (int4: 0-4%; int8:
39-59%), and the reason --mtp already defaults to --ebits 8.

This showed up in the wild: a published pre-converted container had its MTP
shards made with an explicit --ebits 4 and drafts were pure garbage on every
backend (deterministic, data-side; verified byte-for-byte by reproducing the
published bytes with quant_int4 on the official BF16 rows).

- tools/repair_mtp_int8.py: repairs such a container IN PLACE — finds the MTP
  layer's per-row-int4 dense tensors (eh_proj, q/kv/o projections, shared
  experts; routed experts untouched), re-downloads only those from the FP8
  source repo (~355 MB of HTTP range reads, no torch, no token), requantizes
  at int8 with quant_int8's exact math, and rewrites the shards atomically
  with *.bak-int4 backups. --dry-run to inspect. The engine picks up int8
  automatically (qt_from_disk detects format by blob size).
  Validated on GLM-5.2 744B: MTP acceptance 0% -> 100% (greedy), 1.11 -> 3.20
  tokens/forward, decode 0.18 -> 0.30 tok/s (Metal, 4-bit KV).

- convert_fp8_to_int4.py: print a warning when --mtp is combined with
  --ebits <8 and per-row scales (the exact footgun above); --group-size 128
  remains a valid int4 alternative since group scales give the embedding
  half its own scale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 16:27:29 -04:00
andres-gb10 308e269f46 convert(fp8->int4): --indir copies the full metadata set and resumes interrupted passes (#383)
Two gaps in the local path, both raised in #383:

- The main --indir pass copied only config.json while the download path
  copies four files; without tokenizer.json the converted container can't
  run chat/serve. Copy the same four, print what was copied, and warn
  when the source is missing any (tokenizer.json called out explicitly).

- Local passes restarted from shard 0 on every rerun. out-NNNNN names
  count EMITTED shards, not input indexes (shards with no relevant
  tensors emit nothing), so the download path's exists-check can't be
  mirrored directly: a sidecar manifest per prefix records
  input -> output (or empty) plus the conversion parameters, written
  atomically after each shard. Rerun skips what matches and refuses a
  parameter mismatch on the same outdir instead of mixing containers
  (the #355 failure mode).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 21:11:40 +01:00
andres-gb10 799fe41f88 coli: honor THINK=1 in run mode
coli run hardcoded the nothink template (<|assistant|><think></think>),
so THINK=1 had no effect in one-shot runs while serve mode honors it
(glm.c serve template picks <think> vs <think></think> from the same
env var). Pick the template the same way here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 16:45:19 +01:00
bokiko 6a2ab47f3b convert: resolve --mtp/--indexer shards from the local index instead of scanning all of them (#383)
When --indir contains model.safetensors.index.json, the head/indexer
passes convert only the shards that actually hold the requested tensors
(3 instead of scanning 141 for --mtp — every empty scan still opens a
5 GB shard). Prints the selection in the [PLAN] block. Without the index
the full scan is unchanged.

Output is byte-identical to the unfiltered path (sha256-verified on the
GLM-5.2 FP8 head shards, with a decoy shard proving the filter excludes
rather than reorders).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-18 15:15:26 +03:00
Vincenzo Fornaro caa49f7fa0 Merge pull request #384 from ZacharyZcR/perf/auto-numa
plan NUMA interleave on multi-socket Linux
2026-07-18 12:52:08 +02:00
Vincenzo Fornaro 3fed654473 Merge pull request #372 from ZacharyZcR/feat/paged-ragged-kv
cuda: keep paged ragged KV resident across decode steps
2026-07-18 12:48:07 +02:00
JustVugg 4fcbea9056 Merge branch 'p370' into t370
# Conflicts:
#	c/Makefile
#	c/glm.c
2026-07-18 12:41:07 +02:00
ZacharyZcR 9c17dc6d29 plan NUMA interleave on multi-socket Linux 2026-07-18 18:38:40 +08:00
ZacharyZcR 6d3a6168e4 cuda: keep ragged KV resident across decode steps 2026-07-18 18:34:46 +08:00
JustVugg f8e4dc95b5 serve: honor --ngen when the client omits max_tokens; convert: print the resolved plan (#382, #383)
Two operator-surprise fixes:

openai_server.py (#382, LordMZTE): a request without max_tokens defaulted to
min(256, limit) — so `coli serve --ngen 32768` still cut every answer at 256
tokens for clients that omit the field. The operator's configured budget IS the
default now; generation still ends at EOS, so it's a cap, not a target.

convert_fp8_to_int4.py (#383, bokiko): the resolved conversion plan (mode,
source, ebits/io/x bits, grouped-vs-per-row) prints as a [PLAN] line before any
work. The two traps in #383 — --mtp defaulting ebits to 8 with the grouped
branch silently gated off, and --mtp appearing to ignore --indir — are already
defused by the #355 fix (the --mtp pass emits ONLY head tensors on the local
path), but a 3.5-hour job must show its plan at second 1, not in a post-hoc
size sanity check. bokiko's exact invocation now prints:
  [PLAN] mode: MTP head only | source: local ./fp8 | experts 8-bit, ... |
         PER-ROW (grouped branch needs bits<=4; ebits=8 disables it)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 12:28:27 +02:00
JustVugg 8a1dccae3b docs(api): tell coding-CLI users about the prefill cost before they hit it (#373)
yurivict connected crush per the walkthrough and got 'thought for 1h8m and did
nothing' — which is not a hang: agent CLIs send a 10-20k-token system preamble,
and prefill on the CPU-streaming path runs at a few tok/s (attention-bound,
#153). An hour of silent prefill looks exactly like a dead server. The note now
spells out the arithmetic, the curl smoke-test that separates slow-but-working
from broken, and honest guidance on what agent workloads are (not) viable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 02:33:23 +02:00
Vincenzo Fornaro 605b0e28f9 Merge pull request #365 from ZacharyZcR/feat/ragged-batch-attention-upstream
cuda: batch ragged attention across independent streams
2026-07-18 02:04:43 +02:00
JustVugg b09273f73d serve: MTP status message tells the truth in multiplexed mode (#358)
`coli serve` runs the engine with SERVE_BATCH=1 (openai_server.py), which
selects run_serve_mux, which sets g_draft=0 — speculation isn't ragged-safe
across the multi-slot batch. But the "[MTP] active: native speculative decoding
(draft=N)" line is printed in main() BEFORE the serve path is chosen, so
`coli serve DRAFT=8` announced draft=8 and then silently disabled it.
@LordMZTE reported the misleading message.

The load line and the [MTP] stderr line now detect the mux case (SERVE +
SERVE_BATCH) and report it truthfully: "MTP DISABLED (multiplexed serve)" with
draft=0, plus a one-line explanation that single-client interactive use
(`coli chat`, which spawns run_serve without SERVE_BATCH) keeps MTP. No more
draft=8 claim on a path that runs draft=0.

This is the honesty half of #358. The feature half — MTP inside the HTTP
server for a single client — is a real enhancement but needs engine work: the
mux decode kernel would have to run the speculative path when exactly one KV
slot is active (S=1 is not actually ragged), and it needs a server round-trip
test. Tracked separately; the message no longer lies in the meantime.

Reported-by: LordMZTE <#358>

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:57:17 +02:00
JustVugg 679c0742b2 telemetry: split expert hits into pin-tier vs LRU ecache (#336)
The expert lookup counted a hit identically whether the pinned hot-store or the
LRU ecache served it — both Phase C branches bumped the single `m->hits`. With a
warm pin profile the pin tier absorbs most hot experts, so the ecache could be
serving anywhere from ~0% to most hits and the logs couldn't tell which. That
made every cache-policy question unanswerable, including #223's "at what cap
does the eviction policy start to win?" (a flat A/B can mean "pin absorbed
everything" or "genuine floor" and the lumped counter can't distinguish them).

Two counters `hit_pin`/`hit_ecache` bumped at the two lookup branches (the
existing `m->hits++` stays, so all existing math is unchanged), snapshotted in
ProfBase and reset alongside `hits`. Surfaced in the human-readable summaries:
  decode:  expert hit rate 31.3% (pin 22.1% + lru 9.2%)
  [PROF]:  hit 31.3% (X pin + Y lru / Z load)
  tiny:    hit rate 88.1% (0 pin + 74 lru / 10 miss)
The serve-mux STAT protocol line is untouched (openai_server.py parses it
positionally).

Invariant hit_pin + hit_ecache == hits holds by construction — exactly two
sites bump hits and each also bumps one split counter, nothing else mutates
hits. Verified numerically on the tiny oracle: 0 pin + 74 lru = 74 hits, 88.1%.
Zero cost: two increments on paths that already increment.

Reported-by: KingIcyCreamProjects <#336>

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:43:50 +02:00
JustVugg db09466d3d convert(fp8->int4): --mtp/--indexer respected on the --indir path, no more container overwrite (#355)
The local (`--indir`) branch of main() ignored --mtp and --indexer entirely:
it always called convert_shard() without keep_mtp/keep_idx and always wrote
`out-NNNNN.safetensors`. So the documented two-pass workflow — convert the main
model into an outdir, then run `--mtp` into the SAME outdir for the head — did
the opposite on the local path: with --mtp defaulting ebits to 8 and keep_mtp
staying False, it silently re-converted the whole model to per-row int8 and
overwrote the finished fmt=4 container shard by shard, printing nothing wrong.
@mohamedmastouri2000-boop lost 137 of 141 freshly-converted g64 shards to this
while building the public container for #298/#326.

The --indir branch now mirrors the download path: keep_mtp=a.mtp /
keep_idx=a.indexer passed through, output named out-mtp-/out-idx-/out- by mode,
empty shards skipped (an MTP pass emits only shards containing layer n_layers),
and config/tokenizer copied only on the main pass (the head/idx passes land in
an already-complete outdir).

Verified with a synthetic 2-layer + MTP-shard model: after the main pass, a
second --mtp pass into the same outdir leaves out-00000's md5 BYTE-IDENTICAL
and writes out-mtp-00000 alongside it. The bug would have changed that md5.

Reported-by: mohamedmastouri2000-boop <#355>

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:24:43 +02:00
JustVugg eb6d4325dc docs: collapse WINDOWS.md/windows.md case collision into one file (#371)
docs/WINDOWS.md (the #329 install walkthrough) and docs/windows.md (the concise
reference from the README restructure) differ only in case. On case-insensitive
filesystems — every default Windows and macOS checkout — the two paths collide:
the working tree can never be clean, and `git rebase`/`git status` refuse to
run. @KingIcyCreamProjects reported it.

Everything links to the lowercase docs/windows.md (README.md, README.zh-TW.md,
docs/cuda.md), so that is the canonical name. This keeps it and folds in both
contents: the step-by-step walkthrough (download-first, Smart App Control, CUDA
DLL, first run, reference numbers, failure index) followed by the build-flags
reference (AVX-VNNI, warmup). docs/WINDOWS.md is removed. No links change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:15:48 +02:00
JustVugg 4fb5b4e975 sampling: survive non-finite logits instead of emitting token 0 forever (#369)
A single NaN or +Inf logit silently broke the default sampling path. +Inf
became `mx`, then `expf((Inf-mx))`/`expf((NaN-mx))` is NaN, the softmax sum went
NaN, every probability went NaN — and dist_sample's fallback loop
`if(g_pbuf[i]>0)` is false for NaN at every index, so it returned 0. Every
subsequent token: 0. No error, no warning. @KingIcyCreamProjects found it.

dist_build now takes `mx` over finite logits only, gives a non-finite logit
probability 0, and when the distribution is unusable (no finite logit, or a
non-finite/zero sum) collapses to a delta on the finite argmax and warns ONCE
on stderr — degraded, but a valid token and a visible cause, never a silent
stream of zeros. The finite argmax uses the index found during the mx pass
(robust even when lo[0] itself is NaN, where argmax_v would wrongly return 0).

tests/test_sample_nan.c: healthy logits still sample correctly; NaN/+Inf
injected at lo[0], the middle, and the last position all pick the finite
argmax; an all-non-finite vocab leaves no NaN in the buffer and doesn't crash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:10:33 +02:00
Vincenzo 6ce6cc4380 Merge pull request #374 from ZacharyZcR/perf/p0-execution-profile
profile CPU/GPU tier execution and residual P2P
2026-07-18 00:57:35 +02:00
Vincenzo d5dac9bd6c Merge pull request #376 from KingIcyCreamProjects/docs/connect-coding-cli
docs(api): how to connect a coding CLI to the OpenAI endpoint (closes #373)
2026-07-18 00:57:31 +02:00
Vincenzo 1deaa250f5 Merge pull request #375 from bokiko/fix/cpu-absorb-fmt4
glm: fmt=4 support in qt_addrow/qt_matvec_rows — CPU absorbed-attention path decoded grouped int4 as int2
2026-07-18 00:54:57 +02:00
woolcoxm 153ee16f61 bench: multi-seed bench_dsa_select — kill single-input pivot-luck spike (#357)
@KingIcyCreamProjects caught a real artifact on the 9950X3D (#357 thread): the
nk=8192 cell reported ~75x, far above the real ~13-40x algorithm. Root cause was
two compounding bench bugs, both verified against the code:

  1. ONE frozen input per cell. partial_select_desc's median-of-three pivot is
     fully deterministic (no RNG), and bench_dsa_select froze the input then ran
     2000 reps on that identical array -- so all 2000 reps hit the exact same
     pivot sequence. A single lucky input spiked one nk row (stable across re-runs
     because it's deterministic, not because it's real).
  2. brng was a static global, initialized once and NEVER reset between cells.
     So each (shape,nk) cell's input depended on every prior cell's brand() draws
     -- reordering nks[] or adding a shape silently shifted all later inputs.

Fix:
  - brng_seed() reseeds per (shape, nk, seed) so every cell is reproducible and
    independent of cell ordering.
  - Report the MEDIAN of N_SEEDS=11 independent inputs, each itself a median over
    N_REPEAT=2000 timing reps. A lucky pivot now moves one of 11 samples, not the
    reported number.

Measured on Intel Core Ultra 9 185H (this fix, median of 11 seeds):
  realistic@8192  10.97x   (was 13.57x single-seed on this box)
  uniform@8192     9.39x   (was 7.48x)
  plateau@8192    16.11x   (plateau ignores the RNG, unchanged as expected)
  band: 6-26x across all cells, no anomalous spikes.

Correctness is unaffected (test_dsa_select, 129 cases, unchanged). This is a bench
fidelity fix only -- no engine code touched.
2026-07-17 18:17:29 -04:00
ZacharyZcR 570d738ed5 profile effective CPU expert bandwidth 2026-07-18 05:34:41 +08:00
KingIcyCreamProjects 73e0e5ec82 docs(api): add "Connect a coding CLI or editor" recipe (#373)
Documents connecting OpenAI-compatible coding CLIs to `coli serve`.
Covers the common snag reported in #373 — clients like crush refuse to
start without an API key even though the local endpoint needs none — by
showing that any dummy key works (Colibri only enforces COLI_API_KEY if
set). Concrete recipes for aider and crush, a curl smoke test, the
generic base-URL/model/key pattern for other tools, and an honest
tok/s-latency caveat for the streaming path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:24:33 -05:00
bokiko 946fcd4f9f glm: fmt=4 support in qt_addrow/qt_matvec_rows — CPU absorb path decoded grouped int4 as int2
An all-grouped container (kv_b_proj at fmt=4) generates one correct token
and then EOS: qt_addrow and qt_matvec_rows handle fmt 0/1/2 and fall
through to the int2 decoder, so grouped-int4 kv_b was unpacked as 2-bit
pairs under a per-row scale that does not exist in the [O,ng] layout.
Prefill (S>4, reconstruction) is unaffected, which made the failure look
like an EOS bug rather than an attention bug.

Same class as #298 (CUDA absorb kernels missing fmt=4), CPU side. Existing
containers escape it because the recommended mixed-precision recipe keeps
kv_b at int8 (#237).

Adds per-group branches mirroring matmul_i4_grouped semantics. fmt 0/1/2/3
paths are untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-18 00:19:39 +03:00
ZacharyZcR c3a90eca36 profile CPU GPU tier execution costs 2026-07-18 04:54:59 +08:00