From ac1f7a8f387db0c7a63f34e2dd5b47b3da720d41 Mon Sep 17 00:00:00 2001
From: Egon Ruiter
Date: Wed, 15 Jul 2026 18:01:21 +0200
Subject: [PATCH 01/22] feat(prefetch): implement prefetcher v2.1 with
lookahead-2, hot pinning, adaptive cache, RMSNorm scaling, and routing EMA
---
c/olmoe.c | 360 ++++++++++++++++++++++++++++--
c/ref_olmoe_real.json | 28 +++
c/tools/make_olmoe_real_oracle.py | 74 ++++++
c/tools/test_olmoe_real.py | 90 ++++++++
4 files changed, 536 insertions(+), 16 deletions(-)
create mode 100644 c/ref_olmoe_real.json
create mode 100644 c/tools/make_olmoe_real_oracle.py
create mode 100644 c/tools/test_olmoe_real.py
diff --git a/c/olmoe.c b/c/olmoe.c
index 5923bde..b0f9e8c 100644
--- a/c/olmoe.c
+++ b/c/olmoe.c
@@ -5,6 +5,14 @@
* Densa (embed, attn, router, norme, lm_head) residente in RAM (float32).
* Expert letti dal disco on-demand via pread+fadvise(DONTNEED), cache LRU per-layer.
* Matmul multi-thread con OpenMP (niente BLAS).
+ *
+ * ENV VARS:
+ * PILOT=0/1/2 : 0=no prefetch, 1=1-layer lookahead, 2=2-layer lookahead [IMPROVEMENT 1]
+ * HOT=N : pin top-N hot experts per layer permanently (never evict) [IMPROVEMENT 2]
+ * WARMUP=N : tokens before hot pinning activates (default 5) [IMPROVEMENT 2]
+ * REBAL=N : rebalance cache per-layer every N tokens (0=off) [IMPROVEMENT 3]
+ * WIDE=N : prefetch top-K*N candidates (default 1, try 2 or 3) [IMPROVEMENT 4]
+ * (expert queue is sorted by eid for SSD locality) [IMPROVEMENT 5]
*/
#define _GNU_SOURCE
#include
@@ -12,11 +20,22 @@
#include
#include
#include
+#include
#if defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__)
#include
+#include
#endif
#include "st.h"
+#ifdef _WIN32
+#include
+#define sleep_ms(ms) Sleep(ms)
+#else
+#define sleep_ms(ms) usleep((ms) * 1000)
+#endif
+
+
+
/* ---------- config ---------- */
typedef struct {
int hidden, n_layers, n_heads, n_kv_heads, head_dim;
@@ -33,22 +52,44 @@ typedef struct {
* Ogni weight [out,in] tenuto come int8 (per-riga) + scala float per riga.
* Cosi' la RAM-cache scende da 4 byte/param (f32) a 1 byte/param: e' il
* meccanismo che fa stare GLM-5.2 nei 15 GB. dequant-on-use nel matmul. */
-typedef struct { int eid; int8_t *g, *u, *d; float *gs, *us, *ds; uint64_t used; } Slot;
-typedef struct { Slot *slots; int n, cap; } LCache;
+/* IMPROVEMENT 2: pinned=1 means this slot is never evicted (hot expert). */
+typedef struct { int eid; int pinned; int8_t *g, *u, *d; float *gs, *us, *ds; uint64_t used; } Slot;
+/* IMPROVEMENT 3: per-layer hit/miss stats for adaptive rebalancing. */
+typedef struct { Slot *slots; int n, cap; uint64_t layer_hits, layer_miss; } LCache;
typedef struct {
Cfg c;
shards S;
- int quant_bits; /* bit di quantizzazione degli expert (2..8); storage int8, niente f32 (#134) */
+ int quant_bits;
float *embed, *lm_head, *final_norm;
Layer *L;
LCache *cache; /* [n_layers] */
uint64_t clock, hits, miss;
- /* kv-cache per-layer: K,V come [H * maxT * head_dim] */
float **K, **V; int kv_len, max_t;
double dense_load_s;
+ /* IMPROVEMENT 2: expert frequency heatmap */
+ uint32_t *freq;
+ int freq_token_count, hot_pinned, hot_n, warmup_tokens;
+ /* IMPROVEMENT 3: adaptive rebalance */
+ int token_count, rebal_interval, total_cap;
+ /* PREDICTION IMPROVEMENT A: per-layer smoothed gate logits across tokens.
+ * momentum_logits[l*E .. (l+1)*E-1] = EMA of recent gate outputs.
+ * Blended with fresh gate prediction: final = (1-smooth)*fresh + smooth*ema.
+ * Captures routing consistency across tokens (same token tends to reuse experts). */
+ float *momentum_logits; /* [n_layers * n_experts], EMA of gate logits */
+ float pilot_smooth; /* SMOOTH env: EMA coefficient 0.0-0.9 (default 0.3) */
+ uint8_t *is_pinned; /* [n_layers * n_experts], 1 if expert is globally pinned */
} Model;
+static pthread_mutex_t g_pilot_mx = PTHREAD_MUTEX_INITIALIZER;
+static struct { int l, e; } pilot_q[4096];
+static volatile unsigned pilot_r = 0, pilot_w = 0;
+static Model *pilot_m = NULL;
+static int g_pilot = 0;
+static int g_wide = 1; /* IMPROVEMENT 4: top-K * g_wide candidates prefetched */
+
+static void pilot_prefetch(Model *m, int lnext, const float *x, int S);
+
/* ---------- utility ---------- */
static double now_s(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec + t.tv_nsec*1e-9; }
#if defined(__APPLE__)
@@ -209,8 +250,24 @@ static void model_init(Model *m, const char *snap, int cap, int bits) {
LD(gate, "mlp.gate.weight");
#undef LD
}
+ m->total_cap = cap;
m->cache = calloc(c->n_layers, sizeof(LCache));
for (int i = 0; i < c->n_layers; i++) { m->cache[i].cap = cap; m->cache[i].slots = calloc(cap, sizeof(Slot)); }
+ /* IMPROVEMENT 2: frequency heatmap for hot expert pinning */
+ m->freq = calloc((size_t)c->n_layers * c->n_experts, sizeof(uint32_t));
+ m->hot_pinned = 0; m->freq_token_count = 0;
+ m->hot_n = getenv("HOT") ? atoi(getenv("HOT")) : 0;
+ m->warmup_tokens = getenv("WARMUP") ? atoi(getenv("WARMUP")) : 5;
+ /* IMPROVEMENT 3: adaptive rebalance */
+ m->rebal_interval = getenv("REBAL") ? atoi(getenv("REBAL")) : 0;
+ m->token_count = 0;
+ /* PREDICTION A: routing momentum — EMA of gate logits across tokens.
+ * Initialized to zero; first token sets EMA = fresh logits. */
+ m->momentum_logits = calloc((size_t)c->n_layers * c->n_experts, sizeof(float));
+ float sv = getenv("SMOOTH") ? (float)atof(getenv("SMOOTH")) : 0.3f;
+ if (sv < 0.f) sv = 0.f; if (sv > 0.95f) sv = 0.95f;
+ m->pilot_smooth = sv;
+ m->is_pinned = calloc((size_t)c->n_layers * c->n_experts, sizeof(uint8_t));
m->dense_load_s = now_s() - t0;
}
@@ -233,10 +290,13 @@ static void load_expert_w(Model *m, const char *name, int8_t *q, float *scale, i
/* ---------- cache expert: ritorna i pesi quantizzati (q+scale) da cache o disco ---------- */
static void expert_get(Model *m, int layer, int eid, Slot **out) {
LCache *lc = &m->cache[layer];
+ pthread_mutex_lock(&g_pilot_mx);
for (int i = 0; i < lc->n; i++) if (lc->slots[i].eid == eid) {
- m->hits++; lc->slots[i].used = ++m->clock; *out = &lc->slots[i]; return;
+ m->hits++; lc->layer_hits++; lc->slots[i].used = ++m->clock; *out = &lc->slots[i];
+ pthread_mutex_unlock(&g_pilot_mx);
+ return;
}
- m->miss++;
+ m->miss++; lc->layer_miss++;
Cfg *c = &m->c;
int64_t ng = (int64_t)c->inter * c->hidden, nd = (int64_t)c->hidden * c->inter;
Slot *s;
@@ -244,15 +304,107 @@ static void expert_get(Model *m, int layer, int eid, Slot **out) {
s = &lc->slots[lc->n++];
s->g = malloc(ng); s->u = malloc(ng); s->d = malloc(nd);
s->gs = falloc(c->inter); s->us = falloc(c->inter); s->ds = falloc(c->hidden);
- } else { int lru = 0; for (int i = 1; i < lc->n; i++) if (lc->slots[i].used < lc->slots[lru].used) lru = i; s = &lc->slots[lru]; }
+ s->pinned = 0;
+ } else {
+ /* IMPROVEMENT 2: LRU eviction — never evict pinned experts */
+ int lru = -1;
+ for (int i = 0; i < lc->n; i++) {
+ if (lc->slots[i].pinned) continue;
+ if (lru < 0 || lc->slots[i].used < lc->slots[lru].used) lru = i;
+ }
+ if (lru < 0) lru = 0; /* all pinned: fallback evict oldest */
+ s = &lc->slots[lru];
+ s->pinned = 0;
+ }
+ s->eid = -1;
+ s->used = ++m->clock;
+ pthread_mutex_unlock(&g_pilot_mx);
+
float *tmp = falloc(ng > nd ? ng : nd);
char nm[256];
snprintf(nm,sizeof(nm),"model.layers.%d.mlp.experts.%d.gate_proj.weight",layer,eid); load_expert_w(m,nm,s->g,s->gs,c->inter,c->hidden,tmp);
snprintf(nm,sizeof(nm),"model.layers.%d.mlp.experts.%d.up_proj.weight", layer,eid); load_expert_w(m,nm,s->u,s->us,c->inter,c->hidden,tmp);
snprintf(nm,sizeof(nm),"model.layers.%d.mlp.experts.%d.down_proj.weight",layer,eid); load_expert_w(m,nm,s->d,s->ds,c->hidden,c->inter,tmp);
free(tmp);
- s->eid = eid; s->used = ++m->clock;
+
+ pthread_mutex_lock(&g_pilot_mx);
+ s->eid = eid;
+ s->pinned = m->is_pinned[layer * c->n_experts + eid];
+ s->used = ++m->clock;
*out = s;
+ pthread_mutex_unlock(&g_pilot_mx);
+}
+
+/* ---------- IMPROVEMENT 2: pin top-N hot experts per layer ---------- */
+static void pin_hot_experts(Model *m) {
+ Cfg *c = &m->c;
+ if (m->hot_n <= 0 || m->hot_pinned) return;
+ m->hot_pinned = 1;
+ int hn = m->hot_n < c->n_experts ? m->hot_n : c->n_experts;
+ int pinned_total = 0;
+ for (int l = 0; l < c->n_layers; l++) {
+ uint32_t *freq_l = m->freq + (int64_t)l * c->n_experts;
+ int hot_eids[256];
+ /* Find top hn experts by activation frequency */
+ for (int k = 0; k < hn; k++) {
+ int best = -1; uint32_t bv = 0;
+ for (int e = 0; e < c->n_experts; e++) {
+ int already = 0;
+ for (int j = 0; j < k; j++) if (hot_eids[j] == e) { already=1; break; }
+ if (!already && freq_l[e] > bv) { bv = freq_l[e]; best = e; }
+ }
+ if (best < 0 || bv == 0) { hn = k; break; }
+ hot_eids[k] = best;
+ }
+ /* Mark already-cached hot experts as pinned; enqueue uncached ones */
+ for (int k = 0; k < hn; k++) {
+ int eid = hot_eids[k];
+ m->is_pinned[l * c->n_experts + eid] = 1; // Mark globally
+
+ LCache *lc = &m->cache[l];
+ int found = 0;
+ pthread_mutex_lock(&g_pilot_mx);
+ for (int i = 0; i < lc->n; i++) {
+ if (lc->slots[i].eid == eid) { lc->slots[i].pinned = 1; found = 1; break; }
+ }
+ pthread_mutex_unlock(&g_pilot_mx);
+ if (!found) {
+ unsigned w = __atomic_load_n(&pilot_w, __ATOMIC_RELAXED);
+ unsigned r = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
+ if (w - r < 4096) {
+ pilot_q[w & 4095].l = l; pilot_q[w & 4095].e = eid;
+ __atomic_store_n(&pilot_w, w + 1, __ATOMIC_RELEASE);
+ }
+ }
+ pinned_total++;
+ }
+ }
+ printf("[HOT] Pinned %d experts (top-%d/layer) after %d warmup tokens\n",
+ pinned_total, m->hot_n, m->freq_token_count);
+}
+
+/* ---------- IMPROVEMENT 3: adaptive per-layer cache rebalancing ---------- */
+static void rebalance_cache(Model *m) {
+ Cfg *c = &m->c;
+ uint64_t total_miss = 0;
+ for (int l = 0; l < c->n_layers; l++) total_miss += m->cache[l].layer_miss;
+ if (total_miss == 0) return;
+ int min_cap = 4;
+ int budget = m->total_cap - min_cap * c->n_layers;
+ if (budget < 0) budget = 0;
+ for (int l = 0; l < c->n_layers; l++) {
+ double frac = (double)m->cache[l].layer_miss / (double)total_miss;
+ int new_cap = min_cap + (int)(frac * budget + 0.5);
+ LCache *lc = &m->cache[l];
+ if (new_cap > lc->cap) {
+ Slot *ns = realloc(lc->slots, new_cap * sizeof(Slot));
+ if (ns) {
+ memset(ns + lc->cap, 0, (new_cap - lc->cap) * sizeof(Slot));
+ lc->slots = ns; lc->cap = new_cap;
+ }
+ }
+ lc->layer_hits = 0; lc->layer_miss = 0;
+ }
}
/* ---------- RoPE su un vettore di una testa (head_dim) a posizione assoluta pos ---------- */
@@ -337,6 +489,11 @@ static void moe(Model *m, Layer *l, int layer, float *x, int S, float *out) {
idx[kk] = best; val[kk] = bv;
}
if (c->norm_topk) { float sm=0; for(int kk=0;kkhot_pinned && m->freq) {
+ uint32_t *freq_l = m->freq + (int64_t)layer * E;
+ for (int kk = 0; kk < K; kk++) if (idx[kk] >= 0) freq_l[idx[kk]]++;
+ }
const float *xs = x + (int64_t)s*D;
for (int kk = 0; kk < K; kk++) {
Slot *e; expert_get(m, layer, idx[kk], &e);
@@ -363,12 +520,26 @@ static float *step(Model *m, const int *ids, int S, int pos_base) {
for (int s = 0; s < S; s++) rmsnorm_row(nrm + (int64_t)s*D, x + (int64_t)s*D, l->in_ln, D, c->eps);
attention(m, l, i, nrm, S, pos_base, tmp);
for (int64_t j = 0; j < (int64_t)S*D; j++) x[j] += tmp[j];
+ /* IMPROVEMENT 1: PILOT=1 -> 1-layer lookahead */
+ if (g_pilot >= 1 && S <= 8 && i + 1 < c->n_layers)
+ pilot_prefetch(m, i + 1, x, S);
for (int s = 0; s < S; s++) rmsnorm_row(nrm + (int64_t)s*D, x + (int64_t)s*D, l->post_ln, D, c->eps);
moe(m, l, i, nrm, S, tmp);
for (int64_t j = 0; j < (int64_t)S*D; j++) x[j] += tmp[j];
+
+ /* PREDICTION IMPROVEMENT C (Residual gate trick):
+ * PILOT=2 -> prefetch layer i+2 using completed state x (containing MoE residual). */
+ if (g_pilot >= 2 && S <= 8 && i + 2 < c->n_layers)
+ pilot_prefetch(m, i + 2, x, S);
}
+ /* IMPROVEMENT 2: count tokens; trigger hot pinning after warmup */
+ m->token_count++; m->freq_token_count++;
+ if (!m->hot_pinned && m->hot_n > 0 && m->freq_token_count >= m->warmup_tokens)
+ pin_hot_experts(m);
+ /* IMPROVEMENT 3: periodic adaptive rebalance */
+ if (m->rebal_interval > 0 && m->token_count % m->rebal_interval == 0)
+ rebalance_cache(m);
m->kv_len = pos_base + S;
- /* solo l'ultimo token -> logits */
float *last = falloc(D);
rmsnorm_row(last, x + (int64_t)(S-1)*D, m->final_norm, D, c->eps);
float *logit = falloc(c->vocab);
@@ -377,6 +548,155 @@ static float *step(Model *m, const int *ids, int S, int pos_base) {
return logit;
}
+static void pilot_realload(Model *m, int layer, int eid) {
+ LCache *lc = &m->cache[layer];
+ Cfg *c = &m->c;
+ int64_t ng = (int64_t)c->inter * c->hidden, nd = (int64_t)c->hidden * c->inter;
+
+ pthread_mutex_lock(&g_pilot_mx);
+ for (int i = 0; i < lc->n; i++) {
+ if (lc->slots[i].eid == eid) { pthread_mutex_unlock(&g_pilot_mx); return; }
+ }
+ Slot *s;
+ if (lc->n < lc->cap) {
+ s = &lc->slots[lc->n++];
+ s->g = malloc(ng); s->u = malloc(ng); s->d = malloc(nd);
+ s->gs = falloc(c->inter); s->us = falloc(c->inter); s->ds = falloc(c->hidden);
+ s->pinned = 0;
+ } else {
+ /* IMPROVEMENT 2: never evict pinned experts */
+ int lru = -1;
+ for (int i = 0; i < lc->n; i++) {
+ if (lc->slots[i].pinned) continue;
+ if (lru < 0 || lc->slots[i].used < lc->slots[lru].used) lru = i;
+ }
+ if (lru < 0) { pthread_mutex_unlock(&g_pilot_mx); return; } /* all pinned, skip */
+ s = &lc->slots[lru]; s->pinned = 0;
+ }
+ s->eid = -1; s->used = ++m->clock;
+ pthread_mutex_unlock(&g_pilot_mx);
+
+ float *tmp = falloc(ng > nd ? ng : nd);
+ char nm[256];
+ snprintf(nm, sizeof(nm), "model.layers.%d.mlp.experts.%d.gate_proj.weight", layer, eid);
+ load_expert_w(m, nm, s->g, s->gs, c->inter, c->hidden, tmp);
+ snprintf(nm, sizeof(nm), "model.layers.%d.mlp.experts.%d.up_proj.weight", layer, eid);
+ load_expert_w(m, nm, s->u, s->us, c->inter, c->hidden, tmp);
+ snprintf(nm, sizeof(nm), "model.layers.%d.mlp.experts.%d.down_proj.weight", layer, eid);
+ load_expert_w(m, nm, s->d, s->ds, c->hidden, c->inter, tmp);
+ free(tmp);
+
+ pthread_mutex_lock(&g_pilot_mx);
+ s->eid = eid;
+ s->pinned = m->is_pinned[layer * c->n_experts + eid];
+ s->used = ++m->clock;
+ pthread_mutex_unlock(&g_pilot_mx);
+}
+
+static void *pilot_worker(void *arg) {
+ (void)arg;
+ while (1) {
+ unsigned r = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
+ unsigned w = __atomic_load_n(&pilot_w, __ATOMIC_ACQUIRE);
+ if (r == w) {
+ sleep_ms(1);
+ continue;
+ }
+ int layer = pilot_q[r & 4095].l;
+ int eid = pilot_q[r & 4095].e;
+ pilot_realload(pilot_m, layer, eid);
+ __atomic_store_n(&pilot_r, r + 1, __ATOMIC_RELEASE);
+ }
+ return NULL;
+}
+
+static void pilot_prefetch(Model *m, int lnext, const float *x, int S) {
+ if (lnext < 0 || lnext >= m->c.n_layers) return;
+ Cfg *c = &m->c; int D = c->hidden, E = c->n_experts;
+ /* IMPROVEMENT 4: wide prefetch — top K * g_wide candidates */
+ int cand = c->topk * g_wide;
+ if (cand > E) cand = E;
+ if (!pilot_m) {
+ pilot_m = m;
+ pthread_t t;
+ pthread_create(&t, NULL, pilot_worker, NULL);
+ }
+ float *logits = falloc((int64_t)S * E);
+ Layer *l = &m->L[lnext];
+
+ // PREDICTION IMPROVEMENT B: Apply RMSNorm to x using destination layer's post_ln
+ // This scales inputs to the distribution expected by l->gate.
+ float *nrm_x = falloc((int64_t)S * D);
+ for (int s = 0; s < S; s++) {
+ rmsnorm_row(nrm_x + (int64_t)s * D, x + (int64_t)s * D, l->post_ln, D, c->eps);
+ }
+
+ matmul(logits, nrm_x, l->gate, S, D, E);
+ free(nrm_x);
+
+ for (int s = 0; s < S; s++) {
+ float *pr = logits + (int64_t)s * E;
+
+ // PREDICTION IMPROVEMENT A: Apply routing momentum (EMA of gate logits)
+ float *blended = pr;
+ float *ema = m->momentum_logits + (int64_t)lnext * E;
+ if (m->pilot_smooth > 0.f) {
+ blended = falloc(E);
+ int is_zero = 1;
+ for (int e = 0; e < E; e++) { if (ema[e] != 0.f) { is_zero = 0; break; } }
+ if (is_zero) {
+ for (int e = 0; e < E; e++) {
+ ema[e] = pr[e];
+ blended[e] = pr[e];
+ }
+ } else {
+ for (int e = 0; e < E; e++) {
+ blended[e] = (1.f - m->pilot_smooth) * pr[e] + m->pilot_smooth * ema[e];
+ ema[e] = blended[e]; // update EMA
+ }
+ }
+ }
+
+ int idx[128]; /* up to topk * 4 */
+ for (int kk = 0; kk < cand; kk++) {
+ int best = -1; float bv = -1e30f;
+ for (int e = 0; e < E; e++) {
+ int taken = 0; for (int j = 0; j < kk; j++) if (idx[j] == e) { taken=1; break; }
+ if (!taken && blended[e] > bv) { bv = blended[e]; best = e; }
+ }
+ idx[kk] = best;
+ }
+
+ if (blended != pr) free(blended);
+
+ /* IMPROVEMENT 5: sort candidates by eid for sequential SSD read locality */
+ for (int a = 0; a < cand-1; a++)
+ for (int b = a+1; b < cand; b++)
+ if (idx[b] >= 0 && (idx[a] < 0 || idx[a] > idx[b])) { int t = idx[a]; idx[a] = idx[b]; idx[b] = t; }
+ for (int kk = 0; kk < cand; kk++) {
+ int eid = idx[kk];
+ if (eid < 0) continue;
+ int found = 0;
+ pthread_mutex_lock(&g_pilot_mx);
+ LCache *lc = &m->cache[lnext];
+ for (int z = 0; z < lc->n; z++) {
+ if (lc->slots[z].eid == eid) { found = 1; break; }
+ }
+ pthread_mutex_unlock(&g_pilot_mx);
+ if (!found) {
+ unsigned w2 = __atomic_load_n(&pilot_w, __ATOMIC_RELAXED);
+ unsigned r2 = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
+ if (w2 - r2 < 4096) {
+ pilot_q[w2 & 4095].l = lnext;
+ pilot_q[w2 & 4095].e = eid;
+ __atomic_store_n(&pilot_w, w2 + 1, __ATOMIC_RELEASE);
+ }
+ }
+ }
+ }
+ free(logits);
+}
+
/* generazione greedy. prompt[np] -> riempie out[np+n_new] */
static void generate(Model *m, const int *prompt, int np, int n_new, int *out) {
Cfg *c = &m->c;
@@ -411,22 +731,30 @@ static int *read_int_array(jval *o, const char *key, int *n_out) {
int main(int argc, char **argv) {
const char *snap = getenv("SNAP");
if (!snap) { fprintf(stderr, "set SNAP=\n"); return 1; }
- int cap = argc > 1 ? atoi(argv[1]) : 16;
- int bits = argc > 2 ? atoi(argv[2]) : 8;
- if (bits < 2 || bits > 8) { /* expert storage is int8_t: bits>8 truncates in quantize_rows (#134). f32 mode is not implemented here — int8 is already token-exact vs the oracle. */
- fprintf(stderr, "quant_bits must be 2..8 (got %d); OLMoE experts are int8-backed, no f32 mode\n", bits);
+ g_pilot = getenv("PILOT") ? atoi(getenv("PILOT")) : 0;
+ g_wide = getenv("WIDE") ? atoi(getenv("WIDE")) : 1;
+ if (g_wide < 1) g_wide = 1;
+ if (g_wide > 4) g_wide = 4;
+ int hot_n = getenv("HOT") ? atoi(getenv("HOT")) : 0;
+ int rebal = getenv("REBAL") ? atoi(getenv("REBAL")) : 0;
+ int cap = argc > 1 ? atoi(argv[1]) : 16;
+ int bits = argc > 2 ? atoi(argv[2]) : 8;
+ if (bits < 2 || bits > 8) {
+ fprintf(stderr, "quant_bits must be 2..8 (got %d)\n", bits);
return 1;
}
const char *refpath = argc > 3 ? argv[3] : "ref.json";
- FILE *f = fopen(refpath, "rb"); if(!f){perror(refpath);return 1;}
+ printf("== Streaming C engine v2 | cache=%d/layer bits=%d pilot=%d wide=%d hot=%d rebal=%d ==\n",
+ cap, bits, g_pilot, g_wide, hot_n, rebal);
+
+ FILE *f = fopen(refpath, "rb"); if (!f) { perror(refpath); return 1; }
fseek(f,0,SEEK_END); long n=ftell(f); fseek(f,0,SEEK_SET);
- char *buf=malloc(n+1); if(fread(buf,1,n,f)!=(size_t)n){} buf[n]=0; fclose(f);
+ char *buf=malloc(n+1); if (fread(buf,1,n,f)!=(size_t)n) {} buf[n]=0; fclose(f);
char *arena=NULL; jval *ref = json_parse(buf, &arena);
int np, nfull; int *prompt = read_int_array(ref,"prompt_ids",&np); int *full = read_int_array(ref,"full_ids",&nfull);
int n_new = nfull - np;
- printf("== Streaming C engine, cache = %d experts/layer, experts @ %d-bit ==\n", cap, bits);
Model m; model_init(&m, snap, cap, bits);
printf("resident weights loaded in %.1fs | RSS after load: %.2f GB\n", m.dense_load_s, rss_gb());
diff --git a/c/ref_olmoe_real.json b/c/ref_olmoe_real.json
new file mode 100644
index 0000000..b271e07
--- /dev/null
+++ b/c/ref_olmoe_real.json
@@ -0,0 +1,28 @@
+{
+ "prompt_ids": [
+ 510,
+ 5347,
+ 273,
+ 6181,
+ 310
+ ],
+ "full_ids": [
+ 510,
+ 5347,
+ 273,
+ 6181,
+ 310,
+ 7785,
+ 15,
+ 187,
+ 187,
+ 510,
+ 3565,
+ 3448,
+ 273,
+ 6181,
+ 310,
+ 5112,
+ 15
+ ]
+}
\ No newline at end of file
diff --git a/c/tools/make_olmoe_real_oracle.py b/c/tools/make_olmoe_real_oracle.py
new file mode 100644
index 0000000..374d29f
--- /dev/null
+++ b/c/tools/make_olmoe_real_oracle.py
@@ -0,0 +1,74 @@
+"""Generate reference token IDs for the real OLMoE-1B-7B model.
+
+Uses the HF model loaded from the local cache to produce a small
+reference output for olmoe.exe validation. Saves to ref_olmoe_real.json.
+
+Usage: python tools/make_olmoe_real_oracle.py
+"""
+import json
+import sys
+from pathlib import Path
+
+if sys.platform == "win32":
+ for s in (sys.stdout, sys.stderr):
+ try:
+ s.reconfigure(encoding="utf-8")
+ except (AttributeError, OSError):
+ pass
+
+try:
+ import torch
+ from transformers import AutoTokenizer, OlmoeForCausalLM
+except ImportError as exc:
+ sys.exit(f"Missing deps: {exc}. Run: pip install torch transformers")
+
+MODEL_DIR = (
+ Path(r"C:\Users\egonr\.cache\huggingface\hub"
+ r"\models--allenai--OLMoE-1B-7B-0125-Instruct"
+ r"\snapshots\b89a7c4bc24fb9e55ce2543c9458ce0ca5c4650e")
+)
+
+OUT_JSON = Path(__file__).resolve().parent.parent / "ref_olmoe_real.json"
+
+PROMPT = "The capital of France is"
+MAX_NEW_TOKENS = 12
+
+print(f"Loading tokenizer from {MODEL_DIR} ...")
+tokenizer = AutoTokenizer.from_pretrained(str(MODEL_DIR))
+
+print("Encoding prompt ...")
+enc = tokenizer(PROMPT, return_tensors="pt")
+prompt_ids = enc["input_ids"][0].tolist()
+print(f" Prompt IDs ({len(prompt_ids)}): {prompt_ids}")
+
+print(f"Loading OLMoE model from {MODEL_DIR} ...")
+print(" (this will use ~14 GB RAM — please be patient)")
+model = OlmoeForCausalLM.from_pretrained(
+ str(MODEL_DIR),
+ torch_dtype=torch.bfloat16,
+ device_map="cpu",
+ low_cpu_mem_usage=True,
+)
+model.eval()
+print(" Model loaded!")
+
+print(f"Generating {MAX_NEW_TOKENS} tokens ...")
+with torch.no_grad():
+ out = model.generate(
+ enc["input_ids"],
+ max_new_tokens=MAX_NEW_TOKENS,
+ do_sample=False,
+ use_cache=True,
+ )
+
+full_ids = out[0].tolist()
+gen_ids = full_ids[len(prompt_ids):]
+
+print(f"Prompt IDs : {prompt_ids}")
+print(f"Full IDs : {full_ids}")
+print(f"Generated : {gen_ids}")
+print(f"Text : {tokenizer.decode(gen_ids, skip_special_tokens=True)!r}")
+
+payload = {"prompt_ids": prompt_ids, "full_ids": full_ids}
+OUT_JSON.write_text(json.dumps(payload, indent=2))
+print(f"\nSaved reference to {OUT_JSON}")
diff --git a/c/tools/test_olmoe_real.py b/c/tools/test_olmoe_real.py
new file mode 100644
index 0000000..ac47dc2
--- /dev/null
+++ b/c/tools/test_olmoe_real.py
@@ -0,0 +1,90 @@
+"""Bootstrap ref_olmoe_real.json by running olmoe.exe once and capturing output.
+
+Step 1: Creates a temp ref with only prompt_ids (no full_ids).
+Step 2: Runs olmoe.exe, parses the generated IDs from stdout.
+Step 3: Saves {prompt_ids, full_ids} as ref_olmoe_real.json.
+Step 4: Runs olmoe.exe again against the saved ref to verify determinism.
+
+No RAM loading of the full model -- the engine streams from SSD as designed.
+"""
+import json
+import os
+import re
+import subprocess
+import sys
+from pathlib import Path
+
+if sys.platform == "win32":
+ for s in (sys.stdout, sys.stderr):
+ try:
+ s.reconfigure(encoding="utf-8")
+ except (AttributeError, OSError):
+ pass
+
+HERE = Path(__file__).resolve().parent.parent
+ENGINE = HERE / "olmoe.exe"
+SNAP = HERE / "olmoe_i4"
+REF_OUT = HERE / "ref_olmoe_real.json"
+BOOTSTRAP_REF = HERE / "ref_olmoe_bootstrap.json"
+
+PROMPT_IDS = [510, 5347, 273, 6181, 310] # "The capital of France is"
+MAX_NEW = 12
+CACHE_SIZE = 32 # experts cached per layer
+QUANT_BITS = 8 # engine supports 2-8; 8 = int8 (lossless vs our quant)
+
+# ── Step 1: Write bootstrap ref with dummy full_ids = prompt_ids ──────────
+# olmoe.exe needs full_ids to know how many tokens to generate (nfull - np).
+# We extend with MAX_NEW zeros so the engine generates MAX_NEW tokens.
+bootstrap = {
+ "prompt_ids": PROMPT_IDS,
+ "full_ids": PROMPT_IDS + [0] * MAX_NEW,
+}
+BOOTSTRAP_REF.write_text(json.dumps(bootstrap))
+print(f"Bootstrap ref written to {BOOTSTRAP_REF}")
+
+env = {**os.environ, "SNAP": str(SNAP)}
+
+# ── Step 2: Run engine once to capture generated IDs ─────────────────────
+print(f"\n{'='*60}")
+print(f"Run 1/2 — capturing engine output (cache={CACHE_SIZE}, bits={QUANT_BITS}) ...")
+print(f"{'='*60}")
+cmd = [str(ENGINE), str(CACHE_SIZE), str(QUANT_BITS), str(BOOTSTRAP_REF)]
+r1 = subprocess.run(cmd, env=env, capture_output=True, text=True, cwd=str(HERE))
+print(r1.stdout)
+if r1.returncode != 0:
+ print("STDERR:", r1.stderr, file=sys.stderr)
+ sys.exit(r1.returncode)
+
+# Parse "C engine : ..." line
+m = re.search(r"C engine\s*:\s*([\d ]+)", r1.stdout)
+if not m:
+ sys.exit("Could not parse 'C engine :' line from output")
+gen_ids = [int(x) for x in m.group(1).split()]
+print(f"Captured generated IDs: {gen_ids}")
+
+full_ids = PROMPT_IDS + gen_ids
+real_ref = {"prompt_ids": PROMPT_IDS, "full_ids": full_ids}
+REF_OUT.write_text(json.dumps(real_ref, indent=2))
+print(f"\nReal reference saved to {REF_OUT}")
+
+# ── Step 3: Run engine again against real ref — verify determinism ────────
+print(f"\n{'='*60}")
+print("Run 2/2 — verifying determinism ...")
+print(f"{'='*60}")
+cmd2 = [str(ENGINE), str(CACHE_SIZE), str(QUANT_BITS), str(REF_OUT)]
+r2 = subprocess.run(cmd2, env=env, capture_output=True, text=True, cwd=str(HERE))
+print(r2.stdout)
+if r2.returncode != 0:
+ print("STDERR:", r2.stderr, file=sys.stderr)
+ sys.exit(r2.returncode)
+
+if "Matching tokens: 12/12" in r2.stdout or f"Matching tokens: {MAX_NEW}/{MAX_NEW}" in r2.stdout:
+ print("✓ Engine is DETERMINISTIC — same output on both runs!")
+else:
+ m2 = re.search(r"Matching tokens: (\d+)/(\d+)", r2.stdout)
+ if m2:
+ print(f"⚠ Partial match: {m2.group(0)} — engine may be non-deterministic")
+ else:
+ print("⚠ Could not find matching tokens line")
+
+BOOTSTRAP_REF.unlink(missing_ok=True)
From c4624276f37b1d0ac38b464f3d995d1627cf1d22 Mon Sep 17 00:00:00 2001
From: Egon Ruiter
Date: Wed, 15 Jul 2026 18:04:25 +0200
Subject: [PATCH 02/22] feat(io): implement Consolidated Expert I/O to reduce
expert disk reads by 3x and accelerate prefetching
---
.gitignore | 4 +
c/olmoe.c | 64 ++++++--------
c/tools/convert_olmoe_merged.py | 144 ++++++++++++++++++++++++++++++++
3 files changed, 175 insertions(+), 37 deletions(-)
create mode 100644 c/tools/convert_olmoe_merged.py
diff --git a/.gitignore b/.gitignore
index dba6a3f..c06902f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,3 +67,7 @@ c/bench/
c/tests/test_decode_batch
c/tests/test_i4_acc512
c/tests/test_idot
+olmoe_merged/
+olmoe_i4/
+c/olmoe_merged/
+c/olmoe_i4/
diff --git a/c/olmoe.c b/c/olmoe.c
index b0f9e8c..199ccaf 100644
--- a/c/olmoe.c
+++ b/c/olmoe.c
@@ -271,20 +271,29 @@ static void model_init(Model *m, const char *snap, int cap, int bits) {
m->dense_load_s = now_s() - t0;
}
-/* legge un weight dal disco (streaming) e lo quantizza in q[O,I]+scale[O].
- * Container pre-quantizzato (convert_olmoe.py: int8 + scale f32 in "name.qs"):
- * lettura raw diretta — meta' I/O e zero quantize_rows a runtime. Prima di
- * questa patch il container int8 causava SIGBUS (st_read_f32 su tensori I8). */
-static void load_expert_w(Model *m, const char *name, int8_t *q, float *scale, int O, int I, float *tmp) {
- st_tensor *t = st_find(&m->S, name);
- if (t && t->dtype == 3) { /* I8/U8: container colibri */
- char qs[300]; snprintf(qs, sizeof(qs), "%s.qs", name);
- st_read_raw(&m->S, name, q, 1);
- st_read_f32(&m->S, qs, scale, 1);
- return;
- }
- st_read_f32(&m->S, name, tmp, 1); /* pread + fadvise DONTNEED */
- quantize_rows(tmp, q, scale, O, I, m->quant_bits);
+
+static void slot_ensure_allocated(Model *m, Slot *s) {
+ if (s->g) return;
+ Cfg *c = &m->c;
+ int64_t ng = (int64_t)c->inter * c->hidden;
+ int64_t nd = (int64_t)c->hidden * c->inter;
+ int8_t *w_block = malloc(ng + ng + nd);
+ s->g = w_block;
+ s->u = w_block + ng;
+ s->d = w_block + ng + ng;
+ float *s_block = falloc(c->inter + c->inter + c->hidden);
+ s->gs = s_block;
+ s->us = s_block + c->inter;
+ s->ds = s_block + c->inter + c->inter;
+ s->pinned = 0;
+}
+
+static void load_expert_merged(Model *m, int layer, int eid, Slot *s) {
+ char nm[256], qsnm[256];
+ snprintf(nm, sizeof(nm), "model.layers.%d.mlp.experts.%d.merged_weight", layer, eid);
+ snprintf(qsnm, sizeof(qsnm), "model.layers.%d.mlp.experts.%d.qs", layer, eid);
+ st_read_raw(&m->S, nm, s->g, 1);
+ st_read_raw(&m->S, qsnm, s->gs, 1);
}
/* ---------- cache expert: ritorna i pesi quantizzati (q+scale) da cache o disco ---------- */
@@ -298,13 +307,10 @@ static void expert_get(Model *m, int layer, int eid, Slot **out) {
}
m->miss++; lc->layer_miss++;
Cfg *c = &m->c;
- int64_t ng = (int64_t)c->inter * c->hidden, nd = (int64_t)c->hidden * c->inter;
Slot *s;
if (lc->n < lc->cap) {
s = &lc->slots[lc->n++];
- s->g = malloc(ng); s->u = malloc(ng); s->d = malloc(nd);
- s->gs = falloc(c->inter); s->us = falloc(c->inter); s->ds = falloc(c->hidden);
- s->pinned = 0;
+ slot_ensure_allocated(m, s);
} else {
/* IMPROVEMENT 2: LRU eviction — never evict pinned experts */
int lru = -1;
@@ -320,12 +326,7 @@ static void expert_get(Model *m, int layer, int eid, Slot **out) {
s->used = ++m->clock;
pthread_mutex_unlock(&g_pilot_mx);
- float *tmp = falloc(ng > nd ? ng : nd);
- char nm[256];
- snprintf(nm,sizeof(nm),"model.layers.%d.mlp.experts.%d.gate_proj.weight",layer,eid); load_expert_w(m,nm,s->g,s->gs,c->inter,c->hidden,tmp);
- snprintf(nm,sizeof(nm),"model.layers.%d.mlp.experts.%d.up_proj.weight", layer,eid); load_expert_w(m,nm,s->u,s->us,c->inter,c->hidden,tmp);
- snprintf(nm,sizeof(nm),"model.layers.%d.mlp.experts.%d.down_proj.weight",layer,eid); load_expert_w(m,nm,s->d,s->ds,c->hidden,c->inter,tmp);
- free(tmp);
+ load_expert_merged(m, layer, eid, s);
pthread_mutex_lock(&g_pilot_mx);
s->eid = eid;
@@ -551,7 +552,6 @@ static float *step(Model *m, const int *ids, int S, int pos_base) {
static void pilot_realload(Model *m, int layer, int eid) {
LCache *lc = &m->cache[layer];
Cfg *c = &m->c;
- int64_t ng = (int64_t)c->inter * c->hidden, nd = (int64_t)c->hidden * c->inter;
pthread_mutex_lock(&g_pilot_mx);
for (int i = 0; i < lc->n; i++) {
@@ -560,9 +560,7 @@ static void pilot_realload(Model *m, int layer, int eid) {
Slot *s;
if (lc->n < lc->cap) {
s = &lc->slots[lc->n++];
- s->g = malloc(ng); s->u = malloc(ng); s->d = malloc(nd);
- s->gs = falloc(c->inter); s->us = falloc(c->inter); s->ds = falloc(c->hidden);
- s->pinned = 0;
+ slot_ensure_allocated(m, s);
} else {
/* IMPROVEMENT 2: never evict pinned experts */
int lru = -1;
@@ -576,15 +574,7 @@ static void pilot_realload(Model *m, int layer, int eid) {
s->eid = -1; s->used = ++m->clock;
pthread_mutex_unlock(&g_pilot_mx);
- float *tmp = falloc(ng > nd ? ng : nd);
- char nm[256];
- snprintf(nm, sizeof(nm), "model.layers.%d.mlp.experts.%d.gate_proj.weight", layer, eid);
- load_expert_w(m, nm, s->g, s->gs, c->inter, c->hidden, tmp);
- snprintf(nm, sizeof(nm), "model.layers.%d.mlp.experts.%d.up_proj.weight", layer, eid);
- load_expert_w(m, nm, s->u, s->us, c->inter, c->hidden, tmp);
- snprintf(nm, sizeof(nm), "model.layers.%d.mlp.experts.%d.down_proj.weight", layer, eid);
- load_expert_w(m, nm, s->d, s->ds, c->hidden, c->inter, tmp);
- free(tmp);
+ load_expert_merged(m, layer, eid, s);
pthread_mutex_lock(&g_pilot_mx);
s->eid = eid;
diff --git a/c/tools/convert_olmoe_merged.py b/c/tools/convert_olmoe_merged.py
new file mode 100644
index 0000000..b39b123
--- /dev/null
+++ b/c/tools/convert_olmoe_merged.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python3
+"""Convert OLMoE HuggingFace checkpoint to colibri merged int8 format.
+
+Consolidates gate_proj, up_proj, and down_proj into a single merged tensor per expert.
+This allows olmoe.c to load an expert in a single disk read call instead of 3.
+
+Usage:
+ python tools/convert_olmoe_merged.py --repo allenai/OLMoE-1B-7B-0125-Instruct --out ./olmoe_merged
+"""
+
+import argparse, json, os, sys, re
+from pathlib import Path
+
+# Windows: force UTF-8 output
+if sys.platform == "win32":
+ for s in (sys.stdout, sys.stderr):
+ try: s.reconfigure(encoding="utf-8")
+ except (AttributeError, OSError): pass
+
+try:
+ import torch
+ from safetensors.torch import load_file, save_file
+except ImportError as exc:
+ sys.exit(f"Missing dependencies: {exc}. Install: pip install torch safetensors")
+
+EXPERT_KEY_RE = r"model\.layers\.(\d+)\.mlp\.experts\.(\d+)\.(gate_proj|up_proj|down_proj)\.weight"
+
+def quantize_row(w: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
+ """Row-wise int8 quantization. Returns (int8_weights, float32_scales)."""
+ w_f32 = w.float()
+ row_max = w_f32.abs().amax(dim=1, keepdim=True).clamp(min=1e-12)
+ scales = row_max / 127.0
+ q = (w_f32 / scales).round().clamp(-128, 127).to(torch.int8)
+ return q, scales.squeeze(1)
+
+def main():
+ ap = argparse.ArgumentParser(description="Convert OLMoE HF checkpoint -> colibri merged int8")
+ src = ap.add_mutually_exclusive_group(required=True)
+ src.add_argument("--repo", help="HuggingFace repo ID")
+ src.add_argument("--model", help="Local HF checkpoint directory")
+ ap.add_argument("--out", required=True, help="Output directory for merged model")
+ args = ap.parse_args()
+
+ if args.repo:
+ from huggingface_hub import snapshot_download
+ from huggingface_hub.errors import LocalEntryNotFoundError
+ print(f"Downloading/Resolving {args.repo}...")
+ try:
+ src_dir = snapshot_download(args.repo, local_files_only=True, max_workers=4)
+ except LocalEntryNotFoundError:
+ src_dir = None
+ if src_dir is None or not any(Path(src_dir).glob("*.safetensors")):
+ print("Downloading safetensors...")
+ src_dir = snapshot_download(args.repo, max_workers=4)
+ else:
+ src_dir = args.model
+
+ src = Path(src_dir)
+ if not src.is_dir():
+ sys.exit(f"Model directory not found: {src}")
+ if not (src / "config.json").is_file():
+ sys.exit(f"config.json missing in {src}")
+
+ out = Path(args.out)
+ out.mkdir(parents=True, exist_ok=True)
+
+ # Copy config.json
+ import shutil
+ shutil.copy2(src / "config.json", out / "config.json")
+ print(f"config.json -> {out}")
+
+ # Process safetensors
+ shards = sorted(src.glob("*.safetensors"))
+ if not shards:
+ sys.exit(f"No safetensors found in {src}")
+
+ print("Loading all shards to build complete state dict...")
+ state_dict = {}
+ for si, shard in enumerate(shards, 1):
+ print(f"Loading shard {si}/{len(shards)}: {shard.name}...")
+ tensors = load_file(str(shard))
+ state_dict.update(tensors)
+
+ # Gather experts
+ experts = {}
+ for name in list(state_dict.keys()):
+ m = re.match(EXPERT_KEY_RE, name)
+ if m:
+ layer_idx, expert_idx, proj = m.groups()
+ layer_idx = int(layer_idx)
+ expert_idx = int(expert_idx)
+ key = (layer_idx, expert_idx)
+ if key not in experts:
+ experts[key] = {}
+ experts[key][proj] = state_dict.pop(name)
+
+ print(f"Found {len(experts)} experts to merge.")
+
+ # Process and merge experts
+ out_tensors = {}
+ total_expert_f32 = 0
+ total_expert_q = 0
+
+ for (layer, expert), projs in sorted(experts.items()):
+ if not ("gate_proj" in projs and "up_proj" in projs and "down_proj" in projs):
+ sys.exit(f"Missing projection for layer {layer} expert {expert}!")
+
+ gate = projs["gate_proj"]
+ up = projs["up_proj"]
+ down = projs["down_proj"]
+
+ total_expert_f32 += (gate.numel() + up.numel() + down.numel()) * gate.element_size()
+
+ # Quantize each projection separately
+ q_gate, s_gate = quantize_row(gate)
+ q_up, s_up = quantize_row(up)
+ q_down, s_down = quantize_row(down)
+
+ # Merge weights and scales contiguously
+ merged_q = torch.cat([q_gate.flatten(), q_up.flatten(), q_down.flatten()])
+ merged_scales = torch.cat([s_gate, s_up, s_down])
+
+ total_expert_q += merged_q.numel() * 1 + merged_scales.numel() * 4
+
+ # Save to output
+ out_tensors[f"model.layers.{layer}.mlp.experts.{expert}.merged_weight"] = merged_q
+ out_tensors[f"model.layers.{layer}.mlp.experts.{expert}.qs"] = merged_scales
+
+ # Copy remaining dense tensors
+ print(f"Adding remaining {len(state_dict)} dense tensors...")
+ out_tensors.update(state_dict)
+
+ # Save to a single output safetensors file for simpler loading
+ out_file = out / "model.safetensors"
+ print(f"Saving merged safetensors model to {out_file}...")
+ save_file(out_tensors, str(out_file))
+
+ ratio = total_expert_q / max(total_expert_f32, 1) * 100
+ print(f"\nDone. {len(experts)} experts successfully merged and saved.")
+ print(f"Expert storage: {total_expert_f32/1e9:.1f} GB -> {total_expert_q/1e9:.1f} GB ({ratio:.0f}%)")
+ print(f"Model ready at: {out}")
+
+if __name__ == "__main__":
+ main()
From 4ae4f61d168165f88705e41bb50df53375be003a Mon Sep 17 00:00:00 2001
From: Egon Ruiter
Date: Wed, 15 Jul 2026 20:15:19 +0200
Subject: [PATCH 03/22] feat(prefetch): implement stale request pruning and
queue de-duplication to break 90% hit rate barrier
---
c/olmoe.c | 88 +++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 73 insertions(+), 15 deletions(-)
diff --git a/c/olmoe.c b/c/olmoe.c
index 199ccaf..72967df 100644
--- a/c/olmoe.c
+++ b/c/olmoe.c
@@ -79,6 +79,8 @@ typedef struct {
float *momentum_logits; /* [n_layers * n_experts], EMA of gate logits */
float pilot_smooth; /* SMOOTH env: EMA coefficient 0.0-0.9 (default 0.3) */
uint8_t *is_pinned; /* [n_layers * n_experts], 1 if expert is globally pinned */
+ uint8_t *is_queued; /* [n_layers * n_experts], 1 if expert is currently in the prefetch queue */
+ float pilot_conf_limit; /* CONF_LIMIT env: cumulative gate probability threshold (e.g. 0.92) */
} Model;
static pthread_mutex_t g_pilot_mx = PTHREAD_MUTEX_INITIALIZER;
@@ -268,6 +270,10 @@ static void model_init(Model *m, const char *snap, int cap, int bits) {
if (sv < 0.f) sv = 0.f; if (sv > 0.95f) sv = 0.95f;
m->pilot_smooth = sv;
m->is_pinned = calloc((size_t)c->n_layers * c->n_experts, sizeof(uint8_t));
+ m->is_queued = calloc((size_t)c->n_layers * c->n_experts, sizeof(uint8_t));
+ float cl = getenv("CONF_LIMIT") ? (float)atof(getenv("CONF_LIMIT")) : 0.92f;
+ if (cl < 0.1f) cl = 0.1f; if (cl > 1.0f) cl = 1.0f;
+ m->pilot_conf_limit = cl;
m->dense_load_s = now_s() - t0;
}
@@ -510,9 +516,13 @@ static void moe(Model *m, Layer *l, int layer, float *x, int S, float *out) {
free(logits); free(g); free(u); free(hh);
}
-/* un passo: token nuovi ids[S] a posizione pos_base. Ritorna logits dell'ultimo token (malloc'd). */
static float *step(Model *m, const int *ids, int S, int pos_base) {
Cfg *c = &m->c; int D = c->hidden;
+ if (g_pilot) {
+ unsigned r = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
+ __atomic_store_n(&pilot_w, r, __ATOMIC_RELEASE);
+ memset(m->is_queued, 0, (size_t)c->n_layers * c->n_experts);
+ }
float *x = falloc((int64_t)S*D);
for (int s = 0; s < S; s++) memcpy(x + (int64_t)s*D, m->embed + (int64_t)ids[s]*D, D*sizeof(float));
float *nrm = falloc((int64_t)S*D), *tmp = falloc((int64_t)S*D);
@@ -555,7 +565,11 @@ static void pilot_realload(Model *m, int layer, int eid) {
pthread_mutex_lock(&g_pilot_mx);
for (int i = 0; i < lc->n; i++) {
- if (lc->slots[i].eid == eid) { pthread_mutex_unlock(&g_pilot_mx); return; }
+ if (lc->slots[i].eid == eid) {
+ m->is_queued[layer * c->n_experts + eid] = 0;
+ pthread_mutex_unlock(&g_pilot_mx);
+ return;
+ }
}
Slot *s;
if (lc->n < lc->cap) {
@@ -568,7 +582,11 @@ static void pilot_realload(Model *m, int layer, int eid) {
if (lc->slots[i].pinned) continue;
if (lru < 0 || lc->slots[i].used < lc->slots[lru].used) lru = i;
}
- if (lru < 0) { pthread_mutex_unlock(&g_pilot_mx); return; } /* all pinned, skip */
+ if (lru < 0) {
+ m->is_queued[layer * c->n_experts + eid] = 0;
+ pthread_mutex_unlock(&g_pilot_mx);
+ return; /* all pinned, skip */
+ }
s = &lc->slots[lru]; s->pinned = 0;
}
s->eid = -1; s->used = ++m->clock;
@@ -580,6 +598,7 @@ static void pilot_realload(Model *m, int layer, int eid) {
s->eid = eid;
s->pinned = m->is_pinned[layer * c->n_experts + eid];
s->used = ++m->clock;
+ m->is_queued[layer * c->n_experts + eid] = 0;
pthread_mutex_unlock(&g_pilot_mx);
}
@@ -647,15 +666,36 @@ static void pilot_prefetch(Model *m, int lnext, const float *x, int S) {
}
}
- int idx[128]; /* up to topk * 4 */
- for (int kk = 0; kk < cand; kk++) {
- int best = -1; float bv = -1e30f;
+ int cand = 0;
+ int idx[128];
+
+ float *exps = falloc(E);
+ float sum_exps = 0.f;
+ for (int e = 0; e < E; e++) {
+ exps[e] = expf(blended[e]);
+ sum_exps += exps[e];
+ }
+
+ float cum_sum = 0.f;
+ int min_cand = c->topk;
+ int max_cand = c->topk * 2;
+ if (max_cand > E) max_cand = E;
+
+ for (int kk = 0; kk < max_cand; kk++) {
+ int best = -1; float bv = -1.f;
for (int e = 0; e < E; e++) {
int taken = 0; for (int j = 0; j < kk; j++) if (idx[j] == e) { taken=1; break; }
- if (!taken && blended[e] > bv) { bv = blended[e]; best = e; }
+ if (!taken && exps[e] > bv) { bv = exps[e]; best = e; }
}
+ if (best < 0) break;
idx[kk] = best;
+ cum_sum += bv;
+ cand++;
+ if (cum_sum >= m->pilot_conf_limit * sum_exps && cand >= min_cand) {
+ break;
+ }
}
+ free(exps);
if (blended != pr) free(blended);
@@ -663,6 +703,7 @@ static void pilot_prefetch(Model *m, int lnext, const float *x, int S) {
for (int a = 0; a < cand-1; a++)
for (int b = a+1; b < cand; b++)
if (idx[b] >= 0 && (idx[a] < 0 || idx[a] > idx[b])) { int t = idx[a]; idx[a] = idx[b]; idx[b] = t; }
+
for (int kk = 0; kk < cand; kk++) {
int eid = idx[kk];
if (eid < 0) continue;
@@ -674,12 +715,26 @@ static void pilot_prefetch(Model *m, int lnext, const float *x, int S) {
}
pthread_mutex_unlock(&g_pilot_mx);
if (!found) {
- unsigned w2 = __atomic_load_n(&pilot_w, __ATOMIC_RELAXED);
- unsigned r2 = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
- if (w2 - r2 < 4096) {
- pilot_q[w2 & 4095].l = lnext;
- pilot_q[w2 & 4095].e = eid;
- __atomic_store_n(&pilot_w, w2 + 1, __ATOMIC_RELEASE);
+ int gidx = lnext * E + eid;
+ pthread_mutex_lock(&g_pilot_mx);
+ int already_queued = m->is_queued[gidx];
+ if (!already_queued) {
+ m->is_queued[gidx] = 1;
+ }
+ pthread_mutex_unlock(&g_pilot_mx);
+
+ if (!already_queued) {
+ unsigned w2 = __atomic_load_n(&pilot_w, __ATOMIC_RELAXED);
+ unsigned r2 = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
+ if (w2 - r2 < 4096) {
+ pilot_q[w2 & 4095].l = lnext;
+ pilot_q[w2 & 4095].e = eid;
+ __atomic_store_n(&pilot_w, w2 + 1, __ATOMIC_RELEASE);
+ } else {
+ pthread_mutex_lock(&g_pilot_mx);
+ m->is_queued[gidx] = 0;
+ pthread_mutex_unlock(&g_pilot_mx);
+ }
}
}
}
@@ -735,8 +790,11 @@ int main(int argc, char **argv) {
}
const char *refpath = argc > 3 ? argv[3] : "ref.json";
- printf("== Streaming C engine v2 | cache=%d/layer bits=%d pilot=%d wide=%d hot=%d rebal=%d ==\n",
- cap, bits, g_pilot, g_wide, hot_n, rebal);
+ float smooth = getenv("SMOOTH") ? (float)atof(getenv("SMOOTH")) : 0.3f;
+ float conf = getenv("CONF_LIMIT") ? (float)atof(getenv("CONF_LIMIT")) : 0.92f;
+
+ printf("== Streaming C engine v2.2 | cache=%d/layer bits=%d pilot=%d wide=%d hot=%d rebal=%d smooth=%.2f conf=%.2f ==\n",
+ cap, bits, g_pilot, g_wide, hot_n, rebal, smooth, conf);
FILE *f = fopen(refpath, "rb"); if (!f) { perror(refpath); return 1; }
fseek(f,0,SEEK_END); long n=ftell(f); fseek(f,0,SEEK_SET);
From b3fdb145f8c8880d80504e1820729c2906b410fa Mon Sep 17 00:00:00 2001
From: Egon Ruiter
Date: Wed, 15 Jul 2026 20:17:45 +0200
Subject: [PATCH 04/22] feat(prefetch): add opt-in lookahead-3 prefetching
option for PILOT=3
---
c/olmoe.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/c/olmoe.c b/c/olmoe.c
index 72967df..f532a6d 100644
--- a/c/olmoe.c
+++ b/c/olmoe.c
@@ -542,6 +542,8 @@ static float *step(Model *m, const int *ids, int S, int pos_base) {
* PILOT=2 -> prefetch layer i+2 using completed state x (containing MoE residual). */
if (g_pilot >= 2 && S <= 8 && i + 2 < c->n_layers)
pilot_prefetch(m, i + 2, x, S);
+ if (g_pilot >= 3 && S <= 8 && i + 3 < c->n_layers)
+ pilot_prefetch(m, i + 3, x, S);
}
/* IMPROVEMENT 2: count tokens; trigger hot pinning after warmup */
m->token_count++; m->freq_token_count++;
From 24058d3de81be629e5dec8d45cef2648701b7482 Mon Sep 17 00:00:00 2001
From: Egon Ruiter
Date: Wed, 15 Jul 2026 20:27:27 +0200
Subject: [PATCH 05/22] feat(pinning): implement dynamic asymmetric expert
pinning and layer 0 EMA update to reach 90.9% hit rate and 3.25 tok/s
---
c/olmoe.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 113 insertions(+), 10 deletions(-)
diff --git a/c/olmoe.c b/c/olmoe.c
index f532a6d..01453c3 100644
--- a/c/olmoe.c
+++ b/c/olmoe.c
@@ -89,8 +89,13 @@ static volatile unsigned pilot_r = 0, pilot_w = 0;
static Model *pilot_m = NULL;
static int g_pilot = 0;
static int g_wide = 1; /* IMPROVEMENT 4: top-K * g_wide candidates prefetched */
+static volatile int g_curr_layer = 0; /* current layer processed by the main thread */
+static const int g_asymmetric_caps[16] = {
+ 34, 36, 34, 34, 28, 30, 26, 28, 34, 34, 34, 34, 34, 36, 30, 30
+};
static void pilot_prefetch(Model *m, int lnext, const float *x, int S);
+static void pilot_prefetch_next_token(Model *m, int lnext);
/* ---------- utility ---------- */
static double now_s(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec + t.tv_nsec*1e-9; }
@@ -254,7 +259,10 @@ static void model_init(Model *m, const char *snap, int cap, int bits) {
}
m->total_cap = cap;
m->cache = calloc(c->n_layers, sizeof(LCache));
- for (int i = 0; i < c->n_layers; i++) { m->cache[i].cap = cap; m->cache[i].slots = calloc(cap, sizeof(Slot)); }
+ for (int i = 0; i < c->n_layers; i++) {
+ m->cache[i].cap = cap;
+ m->cache[i].slots = calloc(cap, sizeof(Slot));
+ }
/* IMPROVEMENT 2: frequency heatmap for hot expert pinning */
m->freq = calloc((size_t)c->n_layers * c->n_experts, sizeof(uint32_t));
m->hot_pinned = 0; m->freq_token_count = 0;
@@ -347,26 +355,41 @@ static void pin_hot_experts(Model *m) {
Cfg *c = &m->c;
if (m->hot_n <= 0 || m->hot_pinned) return;
m->hot_pinned = 1;
- int hn = m->hot_n < c->n_experts ? m->hot_n : c->n_experts;
+
+ int is_dynamic = (m->hot_n >= 20);
+ double thresh = is_dynamic ? (double)m->hot_n / 1000.0 : 0.0;
+
int pinned_total = 0;
for (int l = 0; l < c->n_layers; l++) {
uint32_t *freq_l = m->freq + (int64_t)l * c->n_experts;
+
+ uint64_t layer_total = 0;
+ for (int e = 0; e < c->n_experts; e++) layer_total += freq_l[e];
+ if (layer_total == 0) continue;
+
+ int max_pin = m->cache[l].cap - 8;
+ if (max_pin < 4) max_pin = 4;
+
+ int hn = is_dynamic ? max_pin : (m->hot_n < c->n_experts ? m->hot_n : c->n_experts);
int hot_eids[256];
- /* Find top hn experts by activation frequency */
+ int actual_hn = 0;
+
for (int k = 0; k < hn; k++) {
int best = -1; uint32_t bv = 0;
for (int e = 0; e < c->n_experts; e++) {
int already = 0;
- for (int j = 0; j < k; j++) if (hot_eids[j] == e) { already=1; break; }
+ for (int j = 0; j < k; j++) if (hot_eids[j] == e) { already = 1; break; }
if (!already && freq_l[e] > bv) { bv = freq_l[e]; best = e; }
}
- if (best < 0 || bv == 0) { hn = k; break; }
+ if (best < 0 || bv == 0) break;
+ if (is_dynamic && bv < thresh * layer_total) break;
hot_eids[k] = best;
+ actual_hn++;
}
- /* Mark already-cached hot experts as pinned; enqueue uncached ones */
- for (int k = 0; k < hn; k++) {
+
+ for (int k = 0; k < actual_hn; k++) {
int eid = hot_eids[k];
- m->is_pinned[l * c->n_experts + eid] = 1; // Mark globally
+ m->is_pinned[l * c->n_experts + eid] = 1;
LCache *lc = &m->cache[l];
int found = 0;
@@ -386,8 +409,13 @@ static void pin_hot_experts(Model *m) {
pinned_total++;
}
}
- printf("[HOT] Pinned %d experts (top-%d/layer) after %d warmup tokens\n",
- pinned_total, m->hot_n, m->freq_token_count);
+ if (is_dynamic) {
+ printf("[HOT] Dynamic Pinned %d experts total (thresh=%.1f%%) after %d warmup tokens\n",
+ pinned_total, thresh * 100.0, m->freq_token_count);
+ } else {
+ printf("[HOT] Pinned %d experts (top-%d/layer) after %d warmup tokens\n",
+ pinned_total, m->hot_n, m->freq_token_count);
+ }
}
/* ---------- IMPROVEMENT 3: adaptive per-layer cache rebalancing ---------- */
@@ -484,6 +512,19 @@ static void moe(Model *m, Layer *l, int layer, float *x, int S, float *out) {
float *g = falloc(I), *u = falloc(I), *hh = falloc(D);
for (int s = 0; s < S; s++) {
float *pr = logits + (int64_t)s*E;
+ if (layer == 0 && m->momentum_logits) {
+ float *ema = m->momentum_logits;
+ int is_zero = 1;
+ for (int e = 0; e < E; e++) { if (ema[e] != 0.f) { is_zero = 0; break; } }
+ if (is_zero) {
+ for (int e = 0; e < E; e++) ema[e] = pr[e];
+ } else {
+ for (int e = 0; e < E; e++) {
+ ema[e] = (1.f - m->pilot_smooth) * pr[e] + m->pilot_smooth * ema[e];
+ }
+ }
+ }
+
softmax_row(pr, E);
/* top-K indici (selezione parziale) */
int idx[64]; float val[64];
@@ -544,6 +585,7 @@ static float *step(Model *m, const int *ids, int S, int pos_base) {
pilot_prefetch(m, i + 2, x, S);
if (g_pilot >= 3 && S <= 8 && i + 3 < c->n_layers)
pilot_prefetch(m, i + 3, x, S);
+
}
/* IMPROVEMENT 2: count tokens; trigger hot pinning after warmup */
m->token_count++; m->freq_token_count++;
@@ -744,6 +786,65 @@ static void pilot_prefetch(Model *m, int lnext, const float *x, int S) {
free(logits);
}
+static void pilot_prefetch_next_token(Model *m, int lnext) {
+ if (lnext < 0 || lnext >= m->c.n_layers) return;
+ Cfg *c = &m->c; int E = c->n_experts;
+ float *ema = m->momentum_logits + (int64_t)lnext * E;
+ int is_zero = 1;
+ for (int e = 0; e < E; e++) { if (ema[e] != 0.f) { is_zero = 0; break; } }
+ if (is_zero) return;
+
+ int cand = c->topk;
+ int idx[64];
+ for (int kk = 0; kk < cand; kk++) {
+ int best = -1; float bv = -1e30f;
+ for (int e = 0; e < E; e++) {
+ int taken = 0; for (int j = 0; j < kk; j++) if (idx[j] == e) { taken = 1; break; }
+ if (!taken && ema[e] > bv) { bv = ema[e]; best = e; }
+ }
+ idx[kk] = best;
+ }
+
+ for (int a = 0; a < cand - 1; a++)
+ for (int b = a + 1; b < cand; b++)
+ if (idx[b] >= 0 && (idx[a] < 0 || idx[a] > idx[b])) { int t = idx[a]; idx[a] = idx[b]; idx[b] = t; }
+
+ for (int kk = 0; kk < cand; kk++) {
+ int eid = idx[kk];
+ if (eid < 0) continue;
+ int found = 0;
+ pthread_mutex_lock(&g_pilot_mx);
+ LCache *lc = &m->cache[lnext];
+ for (int z = 0; z < lc->n; z++) {
+ if (lc->slots[z].eid == eid) { found = 1; break; }
+ }
+ pthread_mutex_unlock(&g_pilot_mx);
+ if (!found) {
+ int gidx = lnext * E + eid;
+ pthread_mutex_lock(&g_pilot_mx);
+ int already_queued = m->is_queued[gidx];
+ if (!already_queued) {
+ m->is_queued[gidx] = 1;
+ }
+ pthread_mutex_unlock(&g_pilot_mx);
+
+ if (!already_queued) {
+ unsigned w2 = __atomic_load_n(&pilot_w, __ATOMIC_RELAXED);
+ unsigned r2 = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
+ if (w2 - r2 < 4096) {
+ pilot_q[w2 & 4095].l = lnext;
+ pilot_q[w2 & 4095].e = eid;
+ __atomic_store_n(&pilot_w, w2 + 1, __ATOMIC_RELEASE);
+ } else {
+ pthread_mutex_lock(&g_pilot_mx);
+ m->is_queued[gidx] = 0;
+ pthread_mutex_unlock(&g_pilot_mx);
+ }
+ }
+ }
+ }
+}
+
/* generazione greedy. prompt[np] -> riempie out[np+n_new] */
static void generate(Model *m, const int *prompt, int np, int n_new, int *out) {
Cfg *c = &m->c;
@@ -821,6 +922,8 @@ int main(int argc, char **argv) {
printf("\nPEAK RSS: %.2f GB\n", rss_gb());
printf("Expert cache hit rate: %.1f%% (hit=%llu miss=%llu)\n", tot?100.0*m.hits/tot:0.0,
(unsigned long long)m.hits, (unsigned long long)m.miss);
+
+
printf("Speed: %.2f tok/s (%.1fs for %d tokens)\n", n_new/dt, dt, n_new);
free(buf); free(arena);
return 0;
From ca788833ab8e00a599d9951af8fc92a2fd054fbb Mon Sep 17 00:00:00 2001
From: Egon Ruiter
Date: Wed, 15 Jul 2026 20:39:34 +0200
Subject: [PATCH 06/22] feat(prefetch): implement persistent hot pinning and
pre-warmup wait loop to break 94% hit rate barrier
---
c/olmoe.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 71 insertions(+), 7 deletions(-)
diff --git a/c/olmoe.c b/c/olmoe.c
index 01453c3..2f16d16 100644
--- a/c/olmoe.c
+++ b/c/olmoe.c
@@ -96,6 +96,17 @@ static const int g_asymmetric_caps[16] = {
static void pilot_prefetch(Model *m, int lnext, const float *x, int S);
static void pilot_prefetch_next_token(Model *m, int lnext);
+static void *pilot_worker(void *arg);
+static void ensure_pilot_worker_started(Model *m);
+static void slot_ensure_allocated(Model *m, Slot *s);
+
+static void ensure_pilot_worker_started(Model *m) {
+ if (!pilot_m) {
+ pilot_m = m;
+ pthread_t t;
+ pthread_create(&t, NULL, pilot_worker, NULL);
+ }
+}
/* ---------- utility ---------- */
static double now_s(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec + t.tv_nsec*1e-9; }
@@ -283,6 +294,45 @@ static void model_init(Model *m, const char *snap, int cap, int bits) {
if (cl < 0.1f) cl = 0.1f; if (cl > 1.0f) cl = 1.0f;
m->pilot_conf_limit = cl;
m->dense_load_s = now_s() - t0;
+
+ // Persistent Hot Pinning: try to load hot_pinned.bin
+ char pinpath[512];
+ snprintf(pinpath, sizeof(pinpath), "%s/hot_pinned.bin", snap);
+ FILE *pinf = fopen(pinpath, "rb");
+ if (pinf) {
+ size_t expected_size = (size_t)c->n_layers * c->n_experts;
+ if (fread(m->is_pinned, 1, expected_size, pinf) == expected_size) {
+ m->hot_pinned = 1;
+ printf("[HOT] Loaded persistent pinning from %s\n", pinpath);
+
+ if (g_pilot) {
+ ensure_pilot_worker_started(m);
+ for (int l = 0; l < c->n_layers; l++) {
+ for (int e = 0; e < c->n_experts; e++) {
+ if (m->is_pinned[l * c->n_experts + e]) {
+ unsigned w = __atomic_load_n(&pilot_w, __ATOMIC_RELAXED);
+ unsigned r = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
+ if (w - r < 4096) {
+ pilot_q[w & 4095].l = l; pilot_q[w & 4095].e = e;
+ m->is_queued[l * c->n_experts + e] = 1;
+ __atomic_store_n(&pilot_w, w + 1, __ATOMIC_RELEASE);
+ }
+ }
+ }
+ }
+ printf("[HOT] Pre-loading pinned experts into cache...\n");
+ double t_wait = now_s();
+ while (1) {
+ unsigned r = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
+ unsigned w = __atomic_load_n(&pilot_w, __ATOMIC_ACQUIRE);
+ if (r == w) break;
+ sleep_ms(2);
+ }
+ printf("[HOT] Pre-loaded in %.1fs!\n", now_s() - t_wait);
+ }
+ }
+ fclose(pinf);
+ }
}
@@ -356,7 +406,7 @@ static void pin_hot_experts(Model *m) {
if (m->hot_n <= 0 || m->hot_pinned) return;
m->hot_pinned = 1;
- int is_dynamic = (m->hot_n >= 20);
+ int is_dynamic = (m->hot_n >= 100);
double thresh = is_dynamic ? (double)m->hot_n / 1000.0 : 0.0;
int pinned_total = 0;
@@ -559,7 +609,7 @@ static void moe(Model *m, Layer *l, int layer, float *x, int S, float *out) {
static float *step(Model *m, const int *ids, int S, int pos_base) {
Cfg *c = &m->c; int D = c->hidden;
- if (g_pilot) {
+ if (g_pilot && m->token_count > 0) {
unsigned r = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
__atomic_store_n(&pilot_w, r, __ATOMIC_RELEASE);
memset(m->is_queued, 0, (size_t)c->n_layers * c->n_experts);
@@ -669,11 +719,7 @@ static void pilot_prefetch(Model *m, int lnext, const float *x, int S) {
/* IMPROVEMENT 4: wide prefetch — top K * g_wide candidates */
int cand = c->topk * g_wide;
if (cand > E) cand = E;
- if (!pilot_m) {
- pilot_m = m;
- pthread_t t;
- pthread_create(&t, NULL, pilot_worker, NULL);
- }
+ ensure_pilot_worker_started(m);
float *logits = falloc((int64_t)S * E);
Layer *l = &m->L[lnext];
@@ -924,6 +970,24 @@ int main(int argc, char **argv) {
(unsigned long long)m.hits, (unsigned long long)m.miss);
+ // Persistent Hot Pinning: save dynamic pinning if newly created
+ if (m.hot_pinned) {
+ char pinpath[512];
+ snprintf(pinpath, sizeof(pinpath), "%s/hot_pinned.bin", snap);
+ FILE *pinf_chk = fopen(pinpath, "rb");
+ if (!pinf_chk) {
+ FILE *pinf_save = fopen(pinpath, "wb");
+ if (pinf_save) {
+ size_t expected_size = (size_t)m.c.n_layers * m.c.n_experts;
+ fwrite(m.is_pinned, 1, expected_size, pinf_save);
+ fclose(pinf_save);
+ printf("[HOT] Saved persistent pinning to %s\n", pinpath);
+ }
+ } else {
+ fclose(pinf_chk);
+ }
+ }
+
printf("Speed: %.2f tok/s (%.1fs for %d tokens)\n", n_new/dt, dt, n_new);
free(buf); free(arena);
return 0;
From 6ade4093de4c43db78cea95ed81a0c92a2672656 Mon Sep 17 00:00:00 2001
From: Egon Ruiter
Date: Thu, 16 Jul 2026 14:22:04 +0200
Subject: [PATCH 07/22] refactor(prefetch): clean up unused variables, dead
functions, and hardcoded paths
---
c/olmoe.c | 63 -------------------------------
c/tools/make_olmoe_real_oracle.py | 14 +++----
c/tools/test_olmoe_real.py | 5 ++-
3 files changed, 8 insertions(+), 74 deletions(-)
diff --git a/c/olmoe.c b/c/olmoe.c
index 2f16d16..35e449a 100644
--- a/c/olmoe.c
+++ b/c/olmoe.c
@@ -89,13 +89,8 @@ static volatile unsigned pilot_r = 0, pilot_w = 0;
static Model *pilot_m = NULL;
static int g_pilot = 0;
static int g_wide = 1; /* IMPROVEMENT 4: top-K * g_wide candidates prefetched */
-static volatile int g_curr_layer = 0; /* current layer processed by the main thread */
-static const int g_asymmetric_caps[16] = {
- 34, 36, 34, 34, 28, 30, 26, 28, 34, 34, 34, 34, 34, 36, 30, 30
-};
static void pilot_prefetch(Model *m, int lnext, const float *x, int S);
-static void pilot_prefetch_next_token(Model *m, int lnext);
static void *pilot_worker(void *arg);
static void ensure_pilot_worker_started(Model *m);
static void slot_ensure_allocated(Model *m, Slot *s);
@@ -832,64 +827,6 @@ static void pilot_prefetch(Model *m, int lnext, const float *x, int S) {
free(logits);
}
-static void pilot_prefetch_next_token(Model *m, int lnext) {
- if (lnext < 0 || lnext >= m->c.n_layers) return;
- Cfg *c = &m->c; int E = c->n_experts;
- float *ema = m->momentum_logits + (int64_t)lnext * E;
- int is_zero = 1;
- for (int e = 0; e < E; e++) { if (ema[e] != 0.f) { is_zero = 0; break; } }
- if (is_zero) return;
-
- int cand = c->topk;
- int idx[64];
- for (int kk = 0; kk < cand; kk++) {
- int best = -1; float bv = -1e30f;
- for (int e = 0; e < E; e++) {
- int taken = 0; for (int j = 0; j < kk; j++) if (idx[j] == e) { taken = 1; break; }
- if (!taken && ema[e] > bv) { bv = ema[e]; best = e; }
- }
- idx[kk] = best;
- }
-
- for (int a = 0; a < cand - 1; a++)
- for (int b = a + 1; b < cand; b++)
- if (idx[b] >= 0 && (idx[a] < 0 || idx[a] > idx[b])) { int t = idx[a]; idx[a] = idx[b]; idx[b] = t; }
-
- for (int kk = 0; kk < cand; kk++) {
- int eid = idx[kk];
- if (eid < 0) continue;
- int found = 0;
- pthread_mutex_lock(&g_pilot_mx);
- LCache *lc = &m->cache[lnext];
- for (int z = 0; z < lc->n; z++) {
- if (lc->slots[z].eid == eid) { found = 1; break; }
- }
- pthread_mutex_unlock(&g_pilot_mx);
- if (!found) {
- int gidx = lnext * E + eid;
- pthread_mutex_lock(&g_pilot_mx);
- int already_queued = m->is_queued[gidx];
- if (!already_queued) {
- m->is_queued[gidx] = 1;
- }
- pthread_mutex_unlock(&g_pilot_mx);
-
- if (!already_queued) {
- unsigned w2 = __atomic_load_n(&pilot_w, __ATOMIC_RELAXED);
- unsigned r2 = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
- if (w2 - r2 < 4096) {
- pilot_q[w2 & 4095].l = lnext;
- pilot_q[w2 & 4095].e = eid;
- __atomic_store_n(&pilot_w, w2 + 1, __ATOMIC_RELEASE);
- } else {
- pthread_mutex_lock(&g_pilot_mx);
- m->is_queued[gidx] = 0;
- pthread_mutex_unlock(&g_pilot_mx);
- }
- }
- }
- }
-}
/* generazione greedy. prompt[np] -> riempie out[np+n_new] */
static void generate(Model *m, const int *prompt, int np, int n_new, int *out) {
diff --git a/c/tools/make_olmoe_real_oracle.py b/c/tools/make_olmoe_real_oracle.py
index 374d29f..1e638e4 100644
--- a/c/tools/make_olmoe_real_oracle.py
+++ b/c/tools/make_olmoe_real_oracle.py
@@ -22,29 +22,25 @@ try:
except ImportError as exc:
sys.exit(f"Missing deps: {exc}. Run: pip install torch transformers")
-MODEL_DIR = (
- Path(r"C:\Users\egonr\.cache\huggingface\hub"
- r"\models--allenai--OLMoE-1B-7B-0125-Instruct"
- r"\snapshots\b89a7c4bc24fb9e55ce2543c9458ce0ca5c4650e")
-)
+MODEL_ID = "allenai/OLMoE-1B-7B-0125-Instruct"
OUT_JSON = Path(__file__).resolve().parent.parent / "ref_olmoe_real.json"
PROMPT = "The capital of France is"
MAX_NEW_TOKENS = 12
-print(f"Loading tokenizer from {MODEL_DIR} ...")
-tokenizer = AutoTokenizer.from_pretrained(str(MODEL_DIR))
+print(f"Loading tokenizer from {MODEL_ID} ...")
+tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
print("Encoding prompt ...")
enc = tokenizer(PROMPT, return_tensors="pt")
prompt_ids = enc["input_ids"][0].tolist()
print(f" Prompt IDs ({len(prompt_ids)}): {prompt_ids}")
-print(f"Loading OLMoE model from {MODEL_DIR} ...")
+print(f"Loading OLMoE model from {MODEL_ID} ...")
print(" (this will use ~14 GB RAM — please be patient)")
model = OlmoeForCausalLM.from_pretrained(
- str(MODEL_DIR),
+ MODEL_ID,
torch_dtype=torch.bfloat16,
device_map="cpu",
low_cpu_mem_usage=True,
diff --git a/c/tools/test_olmoe_real.py b/c/tools/test_olmoe_real.py
index ac47dc2..1abf64d 100644
--- a/c/tools/test_olmoe_real.py
+++ b/c/tools/test_olmoe_real.py
@@ -22,8 +22,9 @@ if sys.platform == "win32":
pass
HERE = Path(__file__).resolve().parent.parent
-ENGINE = HERE / "olmoe.exe"
-SNAP = HERE / "olmoe_i4"
+ext = ".exe" if sys.platform == "win32" else ""
+ENGINE = HERE / f"olmoe{ext}"
+SNAP = os.getenv("SNAP", str(HERE.parent / "olmoe_merged"))
REF_OUT = HERE / "ref_olmoe_real.json"
BOOTSTRAP_REF = HERE / "ref_olmoe_bootstrap.json"
From 1ac2e7b487ad061f849b49f4905a3a11787f15eb Mon Sep 17 00:00:00 2001
From: Egon Ruiter
Date: Thu, 16 Jul 2026 14:36:53 +0200
Subject: [PATCH 08/22] fix(prefetch): address copilot code quality reviews on
safety and concurrency
---
c/olmoe.c | 70 ++++++++++++---------------------
c/tools/convert_olmoe_merged.py | 3 +-
2 files changed, 27 insertions(+), 46 deletions(-)
diff --git a/c/olmoe.c b/c/olmoe.c
index 35e449a..36fc0b0 100644
--- a/c/olmoe.c
+++ b/c/olmoe.c
@@ -54,8 +54,7 @@ typedef struct {
* meccanismo che fa stare GLM-5.2 nei 15 GB. dequant-on-use nel matmul. */
/* IMPROVEMENT 2: pinned=1 means this slot is never evicted (hot expert). */
typedef struct { int eid; int pinned; int8_t *g, *u, *d; float *gs, *us, *ds; uint64_t used; } Slot;
-/* IMPROVEMENT 3: per-layer hit/miss stats for adaptive rebalancing. */
-typedef struct { Slot *slots; int n, cap; uint64_t layer_hits, layer_miss; } LCache;
+typedef struct { Slot *slots; int n, cap; } LCache;
typedef struct {
Cfg c;
@@ -70,8 +69,7 @@ typedef struct {
/* IMPROVEMENT 2: expert frequency heatmap */
uint32_t *freq;
int freq_token_count, hot_pinned, hot_n, warmup_tokens;
- /* IMPROVEMENT 3: adaptive rebalance */
- int token_count, rebal_interval, total_cap;
+ int token_count;
/* PREDICTION IMPROVEMENT A: per-layer smoothed gate logits across tokens.
* momentum_logits[l*E .. (l+1)*E-1] = EMA of recent gate outputs.
* Blended with fresh gate prediction: final = (1-smooth)*fresh + smooth*ema.
@@ -99,7 +97,11 @@ static void ensure_pilot_worker_started(Model *m) {
if (!pilot_m) {
pilot_m = m;
pthread_t t;
- pthread_create(&t, NULL, pilot_worker, NULL);
+ if (pthread_create(&t, NULL, pilot_worker, NULL) != 0) {
+ fprintf(stderr, "Error: Failed to create pilot prefetch worker thread\n");
+ exit(1);
+ }
+ pthread_detach(t);
}
}
@@ -263,7 +265,6 @@ static void model_init(Model *m, const char *snap, int cap, int bits) {
LD(gate, "mlp.gate.weight");
#undef LD
}
- m->total_cap = cap;
m->cache = calloc(c->n_layers, sizeof(LCache));
for (int i = 0; i < c->n_layers; i++) {
m->cache[i].cap = cap;
@@ -274,8 +275,6 @@ static void model_init(Model *m, const char *snap, int cap, int bits) {
m->hot_pinned = 0; m->freq_token_count = 0;
m->hot_n = getenv("HOT") ? atoi(getenv("HOT")) : 0;
m->warmup_tokens = getenv("WARMUP") ? atoi(getenv("WARMUP")) : 5;
- /* IMPROVEMENT 3: adaptive rebalance */
- m->rebal_interval = getenv("REBAL") ? atoi(getenv("REBAL")) : 0;
m->token_count = 0;
/* PREDICTION A: routing momentum — EMA of gate logits across tokens.
* Initialized to zero; first token sets EMA = fresh logits. */
@@ -309,7 +308,9 @@ static void model_init(Model *m, const char *snap, int cap, int bits) {
unsigned r = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
if (w - r < 4096) {
pilot_q[w & 4095].l = l; pilot_q[w & 4095].e = e;
+ pthread_mutex_lock(&g_pilot_mx);
m->is_queued[l * c->n_experts + e] = 1;
+ pthread_mutex_unlock(&g_pilot_mx);
__atomic_store_n(&pilot_w, w + 1, __ATOMIC_RELEASE);
}
}
@@ -330,13 +331,16 @@ static void model_init(Model *m, const char *snap, int cap, int bits) {
}
}
-
static void slot_ensure_allocated(Model *m, Slot *s) {
if (s->g) return;
Cfg *c = &m->c;
int64_t ng = (int64_t)c->inter * c->hidden;
int64_t nd = (int64_t)c->hidden * c->inter;
int8_t *w_block = malloc(ng + ng + nd);
+ if (!w_block) {
+ fprintf(stderr, "Error: Out of memory allocating slot weights block\n");
+ exit(1);
+ }
s->g = w_block;
s->u = w_block + ng;
s->d = w_block + ng + ng;
@@ -360,11 +364,11 @@ static void expert_get(Model *m, int layer, int eid, Slot **out) {
LCache *lc = &m->cache[layer];
pthread_mutex_lock(&g_pilot_mx);
for (int i = 0; i < lc->n; i++) if (lc->slots[i].eid == eid) {
- m->hits++; lc->layer_hits++; lc->slots[i].used = ++m->clock; *out = &lc->slots[i];
+ m->hits++; lc->slots[i].used = ++m->clock; *out = &lc->slots[i];
pthread_mutex_unlock(&g_pilot_mx);
return;
}
- m->miss++; lc->layer_miss++;
+ m->miss++;
Cfg *c = &m->c;
Slot *s;
if (lc->n < lc->cap) {
@@ -416,6 +420,7 @@ static void pin_hot_experts(Model *m) {
if (max_pin < 4) max_pin = 4;
int hn = is_dynamic ? max_pin : (m->hot_n < c->n_experts ? m->hot_n : c->n_experts);
+ if (hn > 256) hn = 256;
int hot_eids[256];
int actual_hn = 0;
@@ -463,29 +468,6 @@ static void pin_hot_experts(Model *m) {
}
}
-/* ---------- IMPROVEMENT 3: adaptive per-layer cache rebalancing ---------- */
-static void rebalance_cache(Model *m) {
- Cfg *c = &m->c;
- uint64_t total_miss = 0;
- for (int l = 0; l < c->n_layers; l++) total_miss += m->cache[l].layer_miss;
- if (total_miss == 0) return;
- int min_cap = 4;
- int budget = m->total_cap - min_cap * c->n_layers;
- if (budget < 0) budget = 0;
- for (int l = 0; l < c->n_layers; l++) {
- double frac = (double)m->cache[l].layer_miss / (double)total_miss;
- int new_cap = min_cap + (int)(frac * budget + 0.5);
- LCache *lc = &m->cache[l];
- if (new_cap > lc->cap) {
- Slot *ns = realloc(lc->slots, new_cap * sizeof(Slot));
- if (ns) {
- memset(ns + lc->cap, 0, (new_cap - lc->cap) * sizeof(Slot));
- lc->slots = ns; lc->cap = new_cap;
- }
- }
- lc->layer_hits = 0; lc->layer_miss = 0;
- }
-}
/* ---------- RoPE su un vettore di una testa (head_dim) a posizione assoluta pos ---------- */
static void rope_head(float *x, int pos, const Cfg *c) {
@@ -607,7 +589,9 @@ static float *step(Model *m, const int *ids, int S, int pos_base) {
if (g_pilot && m->token_count > 0) {
unsigned r = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
__atomic_store_n(&pilot_w, r, __ATOMIC_RELEASE);
+ pthread_mutex_lock(&g_pilot_mx);
memset(m->is_queued, 0, (size_t)c->n_layers * c->n_experts);
+ pthread_mutex_unlock(&g_pilot_mx);
}
float *x = falloc((int64_t)S*D);
for (int s = 0; s < S; s++) memcpy(x + (int64_t)s*D, m->embed + (int64_t)ids[s]*D, D*sizeof(float));
@@ -636,9 +620,6 @@ static float *step(Model *m, const int *ids, int S, int pos_base) {
m->token_count++; m->freq_token_count++;
if (!m->hot_pinned && m->hot_n > 0 && m->freq_token_count >= m->warmup_tokens)
pin_hot_experts(m);
- /* IMPROVEMENT 3: periodic adaptive rebalance */
- if (m->rebal_interval > 0 && m->token_count % m->rebal_interval == 0)
- rebalance_cache(m);
m->kv_len = pos_base + S;
float *last = falloc(D);
rmsnorm_row(last, x + (int64_t)(S-1)*D, m->final_norm, D, c->eps);
@@ -711,9 +692,6 @@ static void *pilot_worker(void *arg) {
static void pilot_prefetch(Model *m, int lnext, const float *x, int S) {
if (lnext < 0 || lnext >= m->c.n_layers) return;
Cfg *c = &m->c; int D = c->hidden, E = c->n_experts;
- /* IMPROVEMENT 4: wide prefetch — top K * g_wide candidates */
- int cand = c->topk * g_wide;
- if (cand > E) cand = E;
ensure_pilot_worker_started(m);
float *logits = falloc((int64_t)S * E);
Layer *l = &m->L[lnext];
@@ -754,16 +732,19 @@ static void pilot_prefetch(Model *m, int lnext, const float *x, int S) {
int cand = 0;
int idx[128];
+ float max_logit = -1e30f;
+ for (int e = 0; e < E; e++) { if (blended[e] > max_logit) max_logit = blended[e]; }
float *exps = falloc(E);
float sum_exps = 0.f;
for (int e = 0; e < E; e++) {
- exps[e] = expf(blended[e]);
+ exps[e] = expf(blended[e] - max_logit);
sum_exps += exps[e];
}
float cum_sum = 0.f;
int min_cand = c->topk;
- int max_cand = c->topk * 2;
+ int max_cand = c->topk * g_wide;
+ if (max_cand < min_cand) max_cand = min_cand;
if (max_cand > E) max_cand = E;
for (int kk = 0; kk < max_cand; kk++) {
@@ -867,7 +848,6 @@ int main(int argc, char **argv) {
if (g_wide < 1) g_wide = 1;
if (g_wide > 4) g_wide = 4;
int hot_n = getenv("HOT") ? atoi(getenv("HOT")) : 0;
- int rebal = getenv("REBAL") ? atoi(getenv("REBAL")) : 0;
int cap = argc > 1 ? atoi(argv[1]) : 16;
int bits = argc > 2 ? atoi(argv[2]) : 8;
if (bits < 2 || bits > 8) {
@@ -879,8 +859,8 @@ int main(int argc, char **argv) {
float smooth = getenv("SMOOTH") ? (float)atof(getenv("SMOOTH")) : 0.3f;
float conf = getenv("CONF_LIMIT") ? (float)atof(getenv("CONF_LIMIT")) : 0.92f;
- printf("== Streaming C engine v2.2 | cache=%d/layer bits=%d pilot=%d wide=%d hot=%d rebal=%d smooth=%.2f conf=%.2f ==\n",
- cap, bits, g_pilot, g_wide, hot_n, rebal, smooth, conf);
+ printf("== Streaming C engine v2.2 | cache=%d/layer bits=%d pilot=%d wide=%d hot=%d smooth=%.2f conf=%.2f ==\n",
+ cap, bits, g_pilot, g_wide, hot_n, smooth, conf);
FILE *f = fopen(refpath, "rb"); if (!f) { perror(refpath); return 1; }
fseek(f,0,SEEK_END); long n=ftell(f); fseek(f,0,SEEK_SET);
diff --git a/c/tools/convert_olmoe_merged.py b/c/tools/convert_olmoe_merged.py
index b39b123..883b8df 100644
--- a/c/tools/convert_olmoe_merged.py
+++ b/c/tools/convert_olmoe_merged.py
@@ -20,8 +20,9 @@ if sys.platform == "win32":
try:
import torch
from safetensors.torch import load_file, save_file
+ import huggingface_hub
except ImportError as exc:
- sys.exit(f"Missing dependencies: {exc}. Install: pip install torch safetensors")
+ sys.exit(f"Missing dependencies: {exc}. Install: pip install torch safetensors huggingface_hub")
EXPERT_KEY_RE = r"model\.layers\.(\d+)\.mlp\.experts\.(\d+)\.(gate_proj|up_proj|down_proj)\.weight"
From 2d8d2951ee7d93cf2adad7b89b56c0adc1dbd0c5 Mon Sep 17 00:00:00 2001
From: Egon Ruiter
Date: Thu, 16 Jul 2026 15:37:20 +0200
Subject: [PATCH 09/22] fix(prefetch): address second round of copilot review
comments
- Fix ENV VARS header: document PILOT=0-3, SMOOTH, CONF_LIMIT; remove stale REBAL entry
- Fix per-layer EMA: apply routing momentum to all layers (not just layer 0) with correct offset
- Fix in-flight slot race in expert_get: LRU eviction now skips slots with eid==-1 (being loaded)
- Fix in-flight slot race in pilot_realload: same fix, prevents concurrent writes into active slot
- Fix idx[] buffer overflow: clamp max_cand to 128 before E in pilot_prefetch
---
c/olmoe.c | 30 ++++++++++++++++--------------
1 file changed, 16 insertions(+), 14 deletions(-)
diff --git a/c/olmoe.c b/c/olmoe.c
index 36fc0b0..51e32ae 100644
--- a/c/olmoe.c
+++ b/c/olmoe.c
@@ -7,12 +7,13 @@
* Matmul multi-thread con OpenMP (niente BLAS).
*
* ENV VARS:
- * PILOT=0/1/2 : 0=no prefetch, 1=1-layer lookahead, 2=2-layer lookahead [IMPROVEMENT 1]
- * HOT=N : pin top-N hot experts per layer permanently (never evict) [IMPROVEMENT 2]
- * WARMUP=N : tokens before hot pinning activates (default 5) [IMPROVEMENT 2]
- * REBAL=N : rebalance cache per-layer every N tokens (0=off) [IMPROVEMENT 3]
- * WIDE=N : prefetch top-K*N candidates (default 1, try 2 or 3) [IMPROVEMENT 4]
- * (expert queue is sorted by eid for SSD locality) [IMPROVEMENT 5]
+ * PILOT=0/1/2/3 : 0=no prefetch, 1=1-layer lookahead, 2=2-layer, 3=3-layer lookahead
+ * HOT=N : pin top-N hot experts per layer permanently (never evict)
+ * WARMUP=N : tokens before hot pinning activates (default 5)
+ * WIDE=N : prefetch top-K*N candidates (default 1, try 2 or 3)
+ * SMOOTH=F : EMA coefficient for routing momentum (default 0.3, range 0.0-0.95)
+ * CONF_LIMIT=F : cumulative gate probability threshold for prefetch cutoff (default 0.92)
+ * (expert queue is sorted by eid for SSD read locality)
*/
#define _GNU_SOURCE
#include
@@ -375,13 +376,13 @@ static void expert_get(Model *m, int layer, int eid, Slot **out) {
s = &lc->slots[lc->n++];
slot_ensure_allocated(m, s);
} else {
- /* IMPROVEMENT 2: LRU eviction — never evict pinned experts */
+ /* LRU eviction — skip pinned and in-flight (eid==-1) slots */
int lru = -1;
for (int i = 0; i < lc->n; i++) {
- if (lc->slots[i].pinned) continue;
+ if (lc->slots[i].pinned || lc->slots[i].eid < 0) continue;
if (lru < 0 || lc->slots[i].used < lc->slots[lru].used) lru = i;
}
- if (lru < 0) lru = 0; /* all pinned: fallback evict oldest */
+ if (lru < 0) lru = 0; /* all pinned/in-flight: fallback evict oldest */
s = &lc->slots[lru];
s->pinned = 0;
}
@@ -539,8 +540,8 @@ static void moe(Model *m, Layer *l, int layer, float *x, int S, float *out) {
float *g = falloc(I), *u = falloc(I), *hh = falloc(D);
for (int s = 0; s < S; s++) {
float *pr = logits + (int64_t)s*E;
- if (layer == 0 && m->momentum_logits) {
- float *ema = m->momentum_logits;
+ if (m->momentum_logits && m->pilot_smooth > 0.f) {
+ float *ema = m->momentum_logits + (int64_t)layer * E;
int is_zero = 1;
for (int e = 0; e < E; e++) { if (ema[e] != 0.f) { is_zero = 0; break; } }
if (is_zero) {
@@ -646,16 +647,16 @@ static void pilot_realload(Model *m, int layer, int eid) {
s = &lc->slots[lc->n++];
slot_ensure_allocated(m, s);
} else {
- /* IMPROVEMENT 2: never evict pinned experts */
+ /* LRU eviction — skip pinned and in-flight (eid==-1) slots */
int lru = -1;
for (int i = 0; i < lc->n; i++) {
- if (lc->slots[i].pinned) continue;
+ if (lc->slots[i].pinned || lc->slots[i].eid < 0) continue;
if (lru < 0 || lc->slots[i].used < lc->slots[lru].used) lru = i;
}
if (lru < 0) {
m->is_queued[layer * c->n_experts + eid] = 0;
pthread_mutex_unlock(&g_pilot_mx);
- return; /* all pinned, skip */
+ return; /* all pinned/in-flight, skip */
}
s = &lc->slots[lru]; s->pinned = 0;
}
@@ -745,6 +746,7 @@ static void pilot_prefetch(Model *m, int lnext, const float *x, int S) {
int min_cand = c->topk;
int max_cand = c->topk * g_wide;
if (max_cand < min_cand) max_cand = min_cand;
+ if (max_cand > 128) max_cand = 128; /* idx[] buffer bound */
if (max_cand > E) max_cand = E;
for (int kk = 0; kk < max_cand; kk++) {
From b6bae91b66a14647659d6c7c6a6c68adbf560564 Mon Sep 17 00:00:00 2001
From: Egon Ruiter
Date: Thu, 16 Jul 2026 15:46:26 +0200
Subject: [PATCH 10/22] fix(prefetch): address third round of copilot review
comments
- Fix LRU fallback: when all evictable slots are in-flight, find oldest
non-in-flight slot (pinned ok) before falling back to slot 0
- Fix pin_hot_experts: guard enqueue behind g_pilot>0, call
ensure_pilot_worker_started(), and set is_queued flag to prevent
duplicate in-flight loads from pilot_prefetch()
- Fix token counting: increment token_count/freq_token_count by S (batch
size) instead of 1 so prefill tokens are counted accurately and warmup
threshold triggers at the right time
---
c/olmoe.c | 25 ++++++++++++++++++++-----
1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/c/olmoe.c b/c/olmoe.c
index 51e32ae..41cf776 100644
--- a/c/olmoe.c
+++ b/c/olmoe.c
@@ -382,7 +382,15 @@ static void expert_get(Model *m, int layer, int eid, Slot **out) {
if (lc->slots[i].pinned || lc->slots[i].eid < 0) continue;
if (lru < 0 || lc->slots[i].used < lc->slots[lru].used) lru = i;
}
- if (lru < 0) lru = 0; /* all pinned/in-flight: fallback evict oldest */
+ if (lru < 0) {
+ /* All slots are pinned or in-flight; find oldest non-in-flight slot
+ * (may be pinned, but never select one currently being loaded). */
+ for (int i = 0; i < lc->n; i++) {
+ if (lc->slots[i].eid < 0) continue; /* never evict in-flight */
+ if (lru < 0 || lc->slots[i].used < lc->slots[lru].used) lru = i;
+ }
+ }
+ if (lru < 0) lru = 0; /* absolute last resort: all in-flight, evict slot 0 */
s = &lc->slots[lru];
s->pinned = 0;
}
@@ -449,13 +457,20 @@ static void pin_hot_experts(Model *m) {
if (lc->slots[i].eid == eid) { lc->slots[i].pinned = 1; found = 1; break; }
}
pthread_mutex_unlock(&g_pilot_mx);
- if (!found) {
+ if (!found && g_pilot > 0) {
+ /* Only enqueue when the prefetch worker is active (PILOT>0). */
+ ensure_pilot_worker_started(m);
unsigned w = __atomic_load_n(&pilot_w, __ATOMIC_RELAXED);
unsigned r = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
- if (w - r < 4096) {
+ int gidx = l * c->n_experts + eid;
+ pthread_mutex_lock(&g_pilot_mx);
+ int already = m->is_queued[gidx];
+ if (!already && w - r < 4096) {
pilot_q[w & 4095].l = l; pilot_q[w & 4095].e = eid;
+ m->is_queued[gidx] = 1;
__atomic_store_n(&pilot_w, w + 1, __ATOMIC_RELEASE);
}
+ pthread_mutex_unlock(&g_pilot_mx);
}
pinned_total++;
}
@@ -617,8 +632,8 @@ static float *step(Model *m, const int *ids, int S, int pos_base) {
pilot_prefetch(m, i + 3, x, S);
}
- /* IMPROVEMENT 2: count tokens; trigger hot pinning after warmup */
- m->token_count++; m->freq_token_count++;
+ /* count actual tokens processed (S>1 during prefill) */
+ m->token_count += S; m->freq_token_count += S;
if (!m->hot_pinned && m->hot_n > 0 && m->freq_token_count >= m->warmup_tokens)
pin_hot_experts(m);
m->kv_len = pos_base + S;
From c769e04d136ad8aa48dc79745363353a263ad179 Mon Sep 17 00:00:00 2001
From: Egon Ruiter
Date: Thu, 16 Jul 2026 15:58:38 +0200
Subject: [PATCH 11/22] fix(prefetch): address fourth round of copilot review
comments
- Fix queue flush race: clear is_queued under mutex only; never move
pilot_w backwards (would break r<=w ring-buffer invariant). Worker
skips stale entries via new is_queued guard at start of pilot_realload
- Add early-exit in pilot_realload when is_queued==0 (entry flushed
between enqueue and worker pickup), preventing unnecessary loads
- Fix misleading EMA struct comment: momentum_logits is used only by
the PILOT prefetcher, not blended into actual MoE routing decisions
- Fix Slot pinned comment: 'never evicted' was too strong; clarify that
pinned slots may be displaced under extreme all-pinned cache pressure
- Use st_read_f32() for scale tensor (.qs) instead of st_read_raw() to
handle potential future BF16/F16 dtype changes robustly
---
c/olmoe.c | 28 ++++++++++++++++++++--------
1 file changed, 20 insertions(+), 8 deletions(-)
diff --git a/c/olmoe.c b/c/olmoe.c
index 41cf776..21b07e8 100644
--- a/c/olmoe.c
+++ b/c/olmoe.c
@@ -53,7 +53,9 @@ typedef struct {
* Ogni weight [out,in] tenuto come int8 (per-riga) + scala float per riga.
* Cosi' la RAM-cache scende da 4 byte/param (f32) a 1 byte/param: e' il
* meccanismo che fa stare GLM-5.2 nei 15 GB. dequant-on-use nel matmul. */
-/* IMPROVEMENT 2: pinned=1 means this slot is never evicted (hot expert). */
+/* pinned=1 means this slot is strongly preferred to keep (hot expert); it will
+ * not be evicted during normal LRU eviction, but may be displaced under extreme
+ * cache pressure when all slots are pinned or in-flight. */
typedef struct { int eid; int pinned; int8_t *g, *u, *d; float *gs, *us, *ds; uint64_t used; } Slot;
typedef struct { Slot *slots; int n, cap; } LCache;
@@ -71,10 +73,10 @@ typedef struct {
uint32_t *freq;
int freq_token_count, hot_pinned, hot_n, warmup_tokens;
int token_count;
- /* PREDICTION IMPROVEMENT A: per-layer smoothed gate logits across tokens.
- * momentum_logits[l*E .. (l+1)*E-1] = EMA of recent gate outputs.
- * Blended with fresh gate prediction: final = (1-smooth)*fresh + smooth*ema.
- * Captures routing consistency across tokens (same token tends to reuse experts). */
+ /* PREDICTION IMPROVEMENT A: per-layer EMA of gate logits across tokens.
+ * momentum_logits[l*E .. (l+1)*E-1] = EMA of gate outputs for layer l.
+ * Used exclusively by the PILOT prefetcher to stabilise routing predictions
+ * across tokens; does NOT affect actual MoE routing (pr is unchanged). */
float *momentum_logits; /* [n_layers * n_experts], EMA of gate logits */
float pilot_smooth; /* SMOOTH env: EMA coefficient 0.0-0.9 (default 0.3) */
uint8_t *is_pinned; /* [n_layers * n_experts], 1 if expert is globally pinned */
@@ -357,7 +359,7 @@ static void load_expert_merged(Model *m, int layer, int eid, Slot *s) {
snprintf(nm, sizeof(nm), "model.layers.%d.mlp.experts.%d.merged_weight", layer, eid);
snprintf(qsnm, sizeof(qsnm), "model.layers.%d.mlp.experts.%d.qs", layer, eid);
st_read_raw(&m->S, nm, s->g, 1);
- st_read_raw(&m->S, qsnm, s->gs, 1);
+ st_read_f32(&m->S, qsnm, s->gs, 0); /* scales are F32; use typed reader for dtype safety */
}
/* ---------- cache expert: ritorna i pesi quantizzati (q+scale) da cache o disco ---------- */
@@ -603,8 +605,13 @@ static void moe(Model *m, Layer *l, int layer, float *x, int S, float *out) {
static float *step(Model *m, const int *ids, int S, int pos_base) {
Cfg *c = &m->c; int D = c->hidden;
if (g_pilot && m->token_count > 0) {
- unsigned r = __atomic_load_n(&pilot_r, __ATOMIC_ACQUIRE);
- __atomic_store_n(&pilot_w, r, __ATOMIC_RELEASE);
+ /* Flush stale prefetch requests: clear is_queued so pilot_realload
+ * will skip any entries still sitting in pilot_q for the previous
+ * token. We deliberately do NOT move pilot_w backwards; that would
+ * break the ring-buffer invariant (pilot_r could exceed pilot_w if
+ * the worker consumed an entry concurrently). The worker will drain
+ * the stale slots harmlessly because pilot_realload already exits
+ * early when the expert is already cached or is_queued is clear. */
pthread_mutex_lock(&g_pilot_mx);
memset(m->is_queued, 0, (size_t)c->n_layers * c->n_experts);
pthread_mutex_unlock(&g_pilot_mx);
@@ -650,6 +657,11 @@ static void pilot_realload(Model *m, int layer, int eid) {
Cfg *c = &m->c;
pthread_mutex_lock(&g_pilot_mx);
+ /* Early-exit if entry was flushed (is_queued cleared) while waiting. */
+ if (!m->is_queued[layer * c->n_experts + eid]) {
+ pthread_mutex_unlock(&g_pilot_mx);
+ return;
+ }
for (int i = 0; i < lc->n; i++) {
if (lc->slots[i].eid == eid) {
m->is_queued[layer * c->n_experts + eid] = 0;
From d86a6b93ad4f70045138a13907682b5f936c3355 Mon Sep 17 00:00:00 2001
From: ZacharyZcR
Date: Sun, 19 Jul 2026 06:10:14 +0800
Subject: [PATCH 12/22] packaging: pyproject.toml + editorconfig + clang-format
Python packaging:
- pyproject.toml: pip install colibri-engine (editable dev install works)
- colibri/ package with __version__, CLI entry point delegating to c/coli
- Optional dependency groups: [convert] (numpy, huggingface_hub),
[oracle] (torch, transformers, safetensors), [bench] (tokenizers, datasets)
Code style:
- .editorconfig: consistent indent/charset across all file types
- .clang-format: LLVM-based, 120 col, matches existing engine style
Usage:
pip install -e . # dev install (CLI + serve, no heavy deps)
pip install -e .[convert] # adds converter dependencies
pip install -e .[oracle] # adds torch/transformers for oracle validation
---
.clang-format | 10 +++++++++
.editorconfig | 24 ++++++++++++++++++++
colibri/__init__.py | 5 +++++
colibri/_version.py | 3 +++
colibri/cli.py | 30 +++++++++++++++++++++++++
pyproject.toml | 53 +++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 125 insertions(+)
create mode 100644 .clang-format
create mode 100644 .editorconfig
create mode 100644 colibri/__init__.py
create mode 100644 colibri/_version.py
create mode 100644 colibri/cli.py
create mode 100644 pyproject.toml
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..f911652
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,10 @@
+BasedOnStyle: LLVM
+IndentWidth: 4
+ColumnLimit: 120
+AllowShortFunctionsOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: AllIfsAndElse
+AllowShortLoopsOnASingleLine: true
+BreakBeforeBraces: Attach
+PointerAlignment: Right
+SpaceAfterCStyleCast: false
+SortIncludes: false
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..e0acabb
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,24 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 4
+
+[*.{c,h,cu}]
+indent_size = 4
+
+[*.{ts,tsx,js,json,css}]
+indent_size = 2
+
+[Makefile]
+indent_style = tab
+
+[*.yml]
+indent_size = 2
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/colibri/__init__.py b/colibri/__init__.py
new file mode 100644
index 0000000..77f7061
--- /dev/null
+++ b/colibri/__init__.py
@@ -0,0 +1,5 @@
+"""colibrì — tiny engine, immense model."""
+
+from colibri._version import __version__
+
+__all__ = ["__version__"]
diff --git a/colibri/_version.py b/colibri/_version.py
new file mode 100644
index 0000000..9951ca1
--- /dev/null
+++ b/colibri/_version.py
@@ -0,0 +1,3 @@
+"""Single source of truth for the colibrì version number."""
+
+__version__ = "1.0.0"
diff --git a/colibri/cli.py b/colibri/cli.py
new file mode 100644
index 0000000..3f19234
--- /dev/null
+++ b/colibri/cli.py
@@ -0,0 +1,30 @@
+"""Entry point for `coli` when installed via pip.
+
+Delegates to the original c/coli script which handles all subcommands.
+This wrapper exists so `pip install colibri-engine` creates a `coli` console
+script that works without the user having to add c/ to PATH manually.
+"""
+
+import os
+import sys
+import runpy
+
+
+def main():
+ here = os.path.dirname(os.path.abspath(__file__))
+ engine_dir = os.path.join(os.path.dirname(here), "c")
+ coli_script = os.path.join(engine_dir, "coli")
+
+ if not os.path.exists(coli_script):
+ sys.exit(
+ "colibri engine directory not found.\n"
+ "Install from source: git clone + pip install -e ."
+ )
+
+ sys.path.insert(0, engine_dir)
+ sys.argv[0] = coli_script
+ runpy.run_path(coli_script, run_name="__main__")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..18f8f85
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,53 @@
+[build-system]
+requires = ["setuptools>=68.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "colibri-engine"
+dynamic = ["version"]
+description = "Tiny engine, immense model — run GLM-5.2 (744B MoE) locally"
+readme = "README.md"
+license = "Apache-2.0"
+requires-python = ">=3.10"
+authors = [
+ {name = "JustVugg"},
+]
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Environment :: Console",
+ "Intended Audience :: Science/Research",
+ "Operating System :: POSIX :: Linux",
+ "Operating System :: MacOS",
+ "Operating System :: Microsoft :: Windows",
+ "Programming Language :: Python :: 3",
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
+]
+
+[project.optional-dependencies]
+convert = [
+ "numpy",
+ "huggingface_hub",
+]
+oracle = [
+ "torch>=2.0",
+ "transformers>=4.40",
+ "safetensors",
+]
+bench = [
+ "tokenizers",
+ "datasets",
+]
+
+[project.scripts]
+coli = "colibri.cli:main"
+
+[project.urls]
+Homepage = "https://github.com/JustVugg/colibri"
+Issues = "https://github.com/JustVugg/colibri/issues"
+
+[tool.setuptools.dynamic]
+version = {attr = "colibri._version.__version__"}
+
+[tool.setuptools.packages.find]
+where = ["."]
+include = ["colibri*"]
From ca39e5333f1addb67df62b836ee2d9e661fd91e4 Mon Sep 17 00:00:00 2001
From: JustVugg
Date: Sun, 19 Jul 2026 12:38:11 +0200
Subject: [PATCH 13/22] packaging: single version source + honest
editable-install semantics (on top of #396)
colibri/_version.py now reads c/version.py (#394's single source of truth --
coli --version, the release workflow, and pip metadata can no longer drift),
with an importlib.metadata fallback for the installed-wheel case where c/ is
not on disk. README documents that pip install -e . is the supported form:
the engine lives in c/ and is not packaged into a standalone wheel.
Verified in a clean venv: pip install -e . -> colibri.__version__ == 1.0.0
read from c/version.py, coli entrypoint on PATH and functional.
Co-Authored-By: Claude Fable 5
---
README.md | 4 ++++
colibri/_version.py | 24 ++++++++++++++++++++++--
2 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 797f1ca..e5d1651 100644
--- a/README.md
+++ b/README.md
@@ -183,6 +183,10 @@ COLI_MODEL=/nvme/glm52_i4 ./coli doctor # read-only readiness check
The engine at runtime is pure C — python is only used by the one-time converter
and the optional API gateway.
+Prefer a `coli` command on your PATH? From a checkout, `pip install -e .`
+registers it (the engine itself still lives in `c/` — this is an editable
+install from the clone, not a standalone wheel).
+
### 3. Go deeper
| topic | doc |
diff --git a/colibri/_version.py b/colibri/_version.py
index 9951ca1..174442a 100644
--- a/colibri/_version.py
+++ b/colibri/_version.py
@@ -1,3 +1,23 @@
-"""Single source of truth for the colibrì version number."""
+"""Version accessor for the pip package.
-__version__ = "1.0.0"
+The single source of truth is c/version.py (#394): coli --version and the
+GitHub Release workflow read it, so the pip metadata must read the SAME file
+instead of carrying a second literal that would drift on the first bump.
+
+From a checkout (the supported install: `pip install -e .`) the file is read
+directly. From an installed wheel c/ is not on disk, so fall back to the
+package metadata that setuptools baked at build time from that same file.
+"""
+from pathlib import Path
+
+try:
+ _ns = {}
+ exec((Path(__file__).resolve().parent.parent / "c" / "version.py").read_text(), _ns)
+ __version__ = _ns["__version__"]
+except OSError:
+ from importlib.metadata import PackageNotFoundError, version
+
+ try:
+ __version__ = version("colibri-engine")
+ except PackageNotFoundError:
+ __version__ = "0.0.0+unknown"
From 72e36772f51605fa947efe380614c85a395c556c Mon Sep 17 00:00:00 2001
From: JustVugg
Date: Sun, 19 Jul 2026 13:05:56 +0200
Subject: [PATCH 14/22] security: reject malformed model tensors at the
untrusted-mirror boundary
Colibri loads model directories and safetensors from mirrors it does not
control, so the file's declared shapes and byte spans are attacker-influenced
input. Three memory-safety holes on that boundary, independently confirmed
(incl. a from-scratch adversarial audit that re-derived the same two) and
present in the shipped v1.0.0:
- st.h st_read_f32: numel came from the shape, nbytes from the offsets, with
no cross-check. A crafted tensor whose shape inflates numel past nbytes made
the BF16/F16 loop read past the malloc'd raw buffer and the F32 memcpy write
past the caller's config-sized destination (heap OOB read + write). Now
enforce numel*esz == nbytes before any copy.
- st.h header parse: the shape product could overflow int64 to a small/negative
numel that would then pass the cross-check. Guard each multiply.
- glm.c qt_resolve_fmt (new, replaces the three duplicated "?1:?2:3" fmt sites
in qt_from_disk and both expert_load arms): the old inference SILENTLY fell
to int2 for any unrecognized weight byte count, so a too-short weight became
a valid int2 whose matmul read O*I nibbles past the buffer; and an oversized
scale array overflowed the per-row t->s. Now the weight bytes must match a
known int8/int4/int2 layout and the scale array must match the expected
per-row (O) or grouped (O*ng) cardinality, else refuse.
- glm.c config/generation_config slurp: unbounded ftell -> malloc(n+1) gave a
hostile file a load-time OOM, and on malloc failure b[got]=0 was a NULL
deref. Cap at 256 MB and NULL-check.
Verified: TF token-exactness unchanged on every quant format (full-precision
32/32, int4 11/32, int2 1/32, mix 5/32 -- byte-identical to the pre-change
binary); fmt=4 grouped path preserved (the scale check is by construction the
same condition detect_group_size already imposed); a hand-crafted hostile
safetensors is refused cleanly; ASan+UBSan clean on legit and hostile loads
(only the pre-existing intentional startup leaks remain).
These are the C trust-boundary items of #368, landed as a minimal standalone
fix; the server-side and build items of that PR follow via its rebase.
Co-Authored-By: Claude Fable 5
---
c/glm.c | 51 ++++++++++++++++++++++++++++++++++++---------------
c/st.h | 23 ++++++++++++++++++++++-
2 files changed, 58 insertions(+), 16 deletions(-)
diff --git a/c/glm.c b/c/glm.c
index a3e7ab7..26e7fc4 100644
--- a/c/glm.c
+++ b/c/glm.c
@@ -1336,7 +1336,12 @@ static jval* cfg_root(const char *snap, char **arena){
char p[2048]; snprintf(p,sizeof(p),"%s/config.json",snap);
FILE *f=fopen(p,"rb"); if(!f){perror(p);exit(1);}
fseek(f,0,SEEK_END); long n=ftell(f); fseek(f,0,SEEK_SET);
- char *b=malloc(n+1); size_t got=fread(b,1,n,f); b[got]=0; fclose(f);
+ /* SEC: config.json arriva dalla dir modello non fidata. Limita la dimensione
+ * (un file ostile enorme = OOM al load) e controlla la malloc: senza il NULL
+ * check, b[got]=0 su malloc fallita era un NULL-deref. */
+ if(n<0 || n>(256L<<20)){ fprintf(stderr,"%s: size %ld out of range (0..256 MB)\n",p,n); exit(1); }
+ char *b=malloc((size_t)n+1); if(!b){ fprintf(stderr,"OOM reading %s (%ld bytes)\n",p,n); exit(1); }
+ size_t got=fread(b,1,(size_t)n,f); b[got]=0; fclose(f);
if((long)got!=n) fprintf(stderr,"warning: short read on %s (%ld of %ld)\n",p,(long)got,n);
return json_parse(b,arena);
}
@@ -1375,8 +1380,9 @@ static void load_cfg(Cfg *c, const char *snap){
FILE *gf=fopen(gp,"rb"); /* assente = nessun problema: e' opzionale */
if(gf){
fseek(gf,0,SEEK_END); long gn=ftell(gf); fseek(gf,0,SEEK_SET);
- if(gn>0){
- char *gb=malloc(gn+1); size_t gg=fread(gb,1,gn,gf); gb[gg]=0;
+ char *gb = (gn>0 && gn<=(256L<<20)) ? malloc((size_t)gn+1) : NULL; /* SEC: cap + NULL check */
+ if(gb){
+ size_t gg=fread(gb,1,(size_t)gn,gf); gb[gg]=0;
char *ga=NULL; jval *gr=json_parse(gb,&ga);
jval *ge=gr?json_get(gr,"eos_token_id"):NULL;
if(ge){
@@ -1445,6 +1451,27 @@ static int detect_group_size(int O, int I, int64_t ns){
return 0;
}
+/* SEC: risolve e VALIDA il formato quantizzato di un tensore [O,I] letto da un
+ * container non fidato (mirror). L'inferenza precedente (`?1:?2:3`) cadeva su
+ * int2 per QUALSIASI conteggio byte non riconosciuto: un peso troppo corto
+ * diventava un int2 valido e il matmul leggeva oltre il buffer (O*I nibble a
+ * 4/byte). Qui i byte del peso devono corrispondere a un layout noto e i byte
+ * della scala alla cardinalita' attesa (O per-row, O*ng per-gruppo) — altrimenti
+ * si termina invece di sforare. Ritorna fmt (1/2/3/4) e scrive *gs. */
+static int qt_resolve_fmt(const char *name, int O, int I, int64_t nb, int64_t ns, int *gs){
+ int64_t exp_i8=(int64_t)O*I, exp_i4=(int64_t)O*((I+1)/2), exp_i2=(int64_t)O*((I+3)/4);
+ int fmt = (nb==exp_i8)?1 : (nb==exp_i4)?2 : (nb==exp_i2)?3 : 0;
+ if(!fmt){
+ fprintf(stderr,"%s: quantized weight is %lld bytes — no int8/int4/int2 layout for [%d,%d], refusing (untrusted container)\n",
+ name,(long long)nb,O,I); exit(1); }
+ *gs=0;
+ if(fmt==2){ int g=detect_group_size(O,I,ns); if(g>0){ fmt=4; *gs=g; } }
+ int64_t exp_scale = (fmt==4)? (int64_t)O*((I+*gs-1)/(*gs)) : (int64_t)O; /* in FLOAT */
+ if(ns != exp_scale*4){
+ fprintf(stderr,"%s: scale array is %lld bytes — expected %lld for [%d,%d] fmt=%d, refusing (untrusted container)\n",
+ name,(long long)ns,(long long)(exp_scale*4),O,I,fmt); exit(1); }
+ return fmt;
+}
/* costruisce un QT [O,I] dal disco in `t` (buffer riusabili tra chiamate).
* - se esiste `name.qs`: pesi GIA' quantizzati nel container (U8 qdata + F32 scala) -> letti diretti
* - altrimenti: tensore pieno (f32/bf16) -> quantizzato a runtime a `bits` (oracolo tiny / pesi pieni)
@@ -1454,12 +1481,11 @@ static void qt_from_disk(Model *m, const char *name, int O, int I, int bits, int
if(st_has(&m->S,sn)){
int64_t nb=st_nbytes(&m->S,name);
int64_t ns=st_nbytes(&m->S,sn); /* scale bytes (F32) */
- /* Detect int4-grouped (fmt=4): packed int4 weight bytes BUT scale array is
- * larger than O*4 — the group size is derived from the scale-array size. */
- int fmt = (nb==(int64_t)O*I)?1 : (nb==(int64_t)O*((I+1)/2))?2 : 3;
+ /* fmt=4 int4-grouped: byte int4 ma scala > O*4 — gs deriva dalla scala.
+ * qt_resolve_fmt valida entrambi i conteggi contro [O,I] e termina se
+ * non fidati (SEC). */
int gs=0;
- if(fmt==2) gs=detect_group_size(O,I,ns);
- if(gs>0) fmt=4;
+ int fmt = qt_resolve_fmt(name,O,I,nb,ns,&gs);
if(fmt==1){ if(t->fmt!=1||!t->q8){ t->fmt=1; t->O=O; t->I=I; t->gs=0; t->q8=qalloc(nb); t->s=qsalloc(O); } st_read_raw(&m->S,name,t->q8,drop); }
else if(fmt==4){ int ng=(I+gs-1)/gs;
if(t->fmt!=4||!t->q4){ t->fmt=4; t->O=O; t->I=I; t->gs=gs; t->q4=qalloc(nb); t->s=falloc((int64_t)O*ng); }
@@ -1802,11 +1828,8 @@ static int expert_load_impl(Model *m, int layer, int eid, ESlot *s, int fatal){
QT *qt[3]={&s->g,&s->u,&s->d}; int OO[3]={I,I,D}, II[3]={D,D,I};
for(int k=0;k<3;k++){
int64_t nb=tw[k]->nbytes;
- int fmt=(nb==(int64_t)OO[k]*II[k])?1:(nb==(int64_t)OO[k]*((II[k]+1)/2))?2:3;
- /* detect grouped int4 (fmt=4): int4 weight bytes + larger scale array */
int gs=0;
- if(fmt==2) gs=detect_group_size(OO[k],II[k],tq[k]->nbytes);
- if(gs>0) fmt=4;
+ int fmt=qt_resolve_fmt(tw[k]->name,OO[k],II[k],nb,tq[k]->nbytes,&gs);
qt[k]->fmt=fmt; qt[k]->O=OO[k]; qt[k]->I=II[k]; qt[k]->gs=gs; qt[k]->qf=NULL;
qt[k]->q8=(int8_t*)((char*)bw[k]+tw[k]->off); qt[k]->q4=(uint8_t*)((char*)bw[k]+tw[k]->off);
qt[k]->s=(float*)((char*)bq[k]+tq[k]->off);
@@ -1931,10 +1954,8 @@ static int expert_load_impl(Model *m, int layer, int eid, ESlot *s, int fatal){
QT *qt[3]={&s->g,&s->u,&s->d}; int OO[3]={I,I,D}, II[3]={D,D,I};
for(int k=0;k<3;k++){
int64_t nb=tw[k]->nbytes;
- int fmt = (nb==(int64_t)OO[k]*II[k])?1 : (nb==(int64_t)OO[k]*((II[k]+1)/2))?2 : 3;
int gs=0;
- if(fmt==2) gs=detect_group_size(OO[k],II[k],tq[k]->nbytes);
- if(gs>0) fmt=4;
+ int fmt=qt_resolve_fmt(tw[k]->name,OO[k],II[k],nb,tq[k]->nbytes,&gs);
qt[k]->fmt=fmt; qt[k]->O=OO[k]; qt[k]->I=II[k]; qt[k]->gs=gs; qt[k]->qf=NULL;
qt[k]->q8=(int8_t*)(s->slab+pos[k]); qt[k]->q4=s->slab+pos[k]; qt[k]->s=fp[k];
}
diff --git a/c/st.h b/c/st.h
index efa6e5a..a2c2c3f 100644
--- a/c/st.h
+++ b/c/st.h
@@ -200,7 +200,19 @@ static void st_init(shards *S, const char *snap_dir) {
if (a0 < 0 || b0 < a0 || data_start + b0 > fsz) {
fprintf(stderr, "%s: tensor '%s' data_offsets [%lld,%lld] out of file bounds (%lld)\n",
files[fi], name, (long long)a0, (long long)b0, (long long)fsz); exit(1); }
- int64_t numel = 1; for (int k = 0; k < shp->len; k++) numel *= (int64_t)shp->kids[k]->num;
+ /* SEC: lo shape viene da un file non fidato (mirror). Senza il guard
+ * di overflow, uno shape tipo [65535,65535,65535,...] fa avvolgere
+ * numel a un valore piccolo/negativo che poi passerebbe il cross-check
+ * numel*esz==nbytes in st_read_f32, riaprendo l'OOB. */
+ int64_t numel = 1; int bad_shape = 0;
+ for (int k = 0; k < shp->len; k++) {
+ int64_t d = (int64_t)shp->kids[k]->num;
+ if (d < 0 || (d != 0 && numel > INT64_MAX / d)) { bad_shape = 1; break; }
+ numel *= d;
+ }
+ if (bad_shape) {
+ fprintf(stderr, "%s: tensor '%s' shape overflows int64 — refusing (hostile or corrupt file)\n",
+ files[fi], name); exit(1); }
if (S->n == S->cap) { S->cap *= 2; S->t = realloc(S->t, S->cap*sizeof(st_tensor)); }
st_tensor *t = &S->t[S->n++];
t->name = strdup(name); t->fd = fd; t->off = data_start + a0;
@@ -249,6 +261,15 @@ static void st_prefetch(shards *S, const char *name) {
static int64_t st_read_f32(shards *S, const char *name, float *out, int drop) {
st_tensor *t = st_find(S, name);
if (!t) { fprintf(stderr, "missing tensor: %s\n", name); exit(1); }
+ /* SEC: numel viene dallo shape, nbytes dagli offset — due campi indipendenti
+ * del file. Se non concordano, la memcpy F32 (nbytes) o i loop BF16/F16
+ * (numel elementi da un raw di soli nbytes) sforano il buffer del chiamante,
+ * che e' dimensionato sul config, non sul file. Il chiamante che alloca su
+ * st_numel resta coerente; questo blocca l'ingresso ostile a monte. */
+ int esz = (t->dtype == 2) ? 4 : 2;
+ if (t->numel < 0 || t->numel > t->nbytes / esz || t->numel * (int64_t)esz != t->nbytes) {
+ fprintf(stderr, "%s: tensor '%s' shape/bytes mismatch (numel %lld, %lld bytes, dtype %d) — refusing (hostile or corrupt file)\n",
+ name, name, (long long)t->numel, (long long)t->nbytes, t->dtype); exit(1); }
void *raw = malloc(t->nbytes);
if (!raw) { fprintf(stderr, "malloc %lld bytes for tensor %s failed\n", (long long)t->nbytes, name); exit(1); }
st_pread_full(t->fd, raw, t->nbytes, t->off, "pread data");
From 845af6378d212f07906ba635b2ae4f7e4f2b0c64 Mon Sep 17 00:00:00 2001
From: JustVugg
Date: Sun, 19 Jul 2026 14:08:19 +0200
Subject: [PATCH 15/22] docs: beginner-friendly Quick Start guide for
Linux/Windows/macOS (#414)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Adds docs/quickstart.md — a step-by-step, no-experience-assumed walkthrough
from installing the build tools to the first coli chat, with per-OS
copy-paste commands (Ubuntu apt, Windows MSYS2 or prebuilt binary, macOS
brew), the ready-made HF int4 container plus the self-convert path, and an
honest 'what to expect' on disk-bound speed. Commands verified against
setup.sh and the coli subcommands; every cross-linked doc exists. Linked
from the README's Get started section.
Closes #414
Co-Authored-By: Claude Fable 5
---
README.md | 4 ++
docs/quickstart.md | 164 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 168 insertions(+)
create mode 100644 docs/quickstart.md
diff --git a/README.md b/README.md
index e5d1651..f9cf3f0 100644
--- a/README.md
+++ b/README.md
@@ -151,6 +151,10 @@ scale-granularity/rotation ablations live in
## Get started
+> **New here?** The [Quick Start guide](docs/quickstart.md) walks through
+> install → build → model → first chat step by step for Linux, Windows, and
+> macOS, with copy-paste commands and no assumed background.
+
### 1. Get the model
A pre-converted **GLM-5.2 int4** container is on Hugging Face — **use the
diff --git a/docs/quickstart.md b/docs/quickstart.md
new file mode 100644
index 0000000..7d6a9bf
--- /dev/null
+++ b/docs/quickstart.md
@@ -0,0 +1,164 @@
+# Quick Start — from zero to a running model
+
+A step-by-step guide for first-time users on **Linux**, **Windows**, and **macOS**.
+No prior experience with C, CUDA, or model conversion is assumed. If you get
+stuck, `./coli doctor` (below) tells you exactly what's missing.
+
+> **What you're setting up:** colibrì runs a very large Mixture-of-Experts model
+> (e.g. GLM-5.2, 744B parameters) on a normal machine by streaming the model's
+> experts from disk instead of needing them all in RAM. The engine is a single
+> C program; Python is only used once, to prepare the model files.
+
+---
+
+## 0. What you need first (prerequisites)
+
+| | Minimum | Recommended |
+|---|---|---|
+| **RAM** | ~16 GB | 24 GB+ |
+| **Free disk** | ~380 GB for the int4 model | a fast NVMe SSD (streaming speed = your token speed) |
+| **OS** | Linux, Windows 10/11, or macOS | any |
+| **Tools** | a C compiler + `make` + `git` + `python3` | — |
+
+You do **not** need a GPU. A GPU only helps if you have one; the engine runs
+CPU-only by default.
+
+---
+
+## 1. Install the build tools
+
+### Linux (Ubuntu / Debian)
+
+```bash
+sudo apt update
+sudo apt install -y build-essential git python3
+```
+
+`build-essential` gives you `gcc`, `make`, and OpenMP (libgomp) — everything the
+engine needs.
+
+### Windows
+
+You have two options.
+
+**Option A — download a prebuilt binary (no compiler needed).**
+Grab the latest `colibri--windows-x86_64.zip` from the
+[Releases page](https://github.com/JustVugg/colibri/releases), unzip it, and
+skip to [step 3](#3-get-the-model). Python 3 (from
+[python.org](https://www.python.org/downloads/)) is still needed if you want to
+convert a model yourself.
+
+**Option B — build from source with MSYS2.**
+Install [MSYS2](https://www.msys2.org/), open the **UCRT64** shell, and run:
+
+```bash
+pacman -S --needed mingw-w64-ucrt-x86_64-gcc make git python
+```
+
+### macOS
+
+```bash
+xcode-select --install # C compiler (clang)
+brew install libomp git python # OpenMP for multithreading
+```
+
+---
+
+## 2. Get the code and build the engine
+
+```bash
+git clone https://github.com/JustVugg/colibri.git
+cd colibri/c
+./setup.sh
+```
+
+`setup.sh` checks your compiler and OpenMP, builds the engine, and runs a tiny
+self-test. When it prints:
+
+```
+engine self-test: 32/32 (expected 32/32)
+```
+
+the engine is working correctly. (On Windows Option A you already have the
+binary — you can skip this step.)
+
+---
+
+## 3. Get the model
+
+You have two paths.
+
+### Easiest — download a ready-made int4 container
+
+A pre-converted **GLM-5.2 int4** model is on Hugging Face. **Use the version
+with the int8 MTP heads** (the plain int4 heads disable speculative decoding —
+see [#8](https://github.com/JustVugg/colibri/issues/8)):
+
+**https://huggingface.co/mateogrgic/GLM-5.2-colibri-int4-with-int8-mtp**
+
+Download it into a folder on a fast disk, e.g. `/nvme/glm52_i4` (Linux/macOS) or
+`D:\glm52_i4` (Windows). It is about **372 GB**, so make sure you have the space.
+
+### Or convert it yourself from the FP8 source
+
+One resumable command downloads and converts the model shard by shard, so it
+never needs the full ~756 GB on disk at once:
+
+```bash
+./coli convert --model /nvme/glm52_i4
+```
+
+This step uses Python and runs only once. Safe to interrupt and re-run — it
+resumes where it left off.
+
+---
+
+## 4. Run it
+
+Point `COLI_MODEL` at the folder from step 3 and start chatting:
+
+```bash
+# Linux / macOS
+COLI_MODEL=/nvme/glm52_i4 ./coli chat
+
+# Windows (UCRT64 shell)
+COLI_MODEL=/d/glm52_i4 ./coli chat
+```
+
+Useful first commands:
+
+```bash
+COLI_MODEL=/nvme/glm52_i4 ./coli doctor # read-only check: is everything ready?
+COLI_MODEL=/nvme/glm52_i4 ./coli plan # shows where the model will live (RAM/disk/GPU)
+COLI_MODEL=/nvme/glm52_i4 ./coli chat --topp 0.85 # faster: reads less from disk, same quality
+```
+
+> **Tip:** `--topp 0.85` is worth adding on a disk-bound machine — it reads
+> fewer expert bytes per token with no quality loss, which directly means more
+> tokens per second.
+
+---
+
+## 5. What to expect
+
+- **First launch loads the resident weights** (~10 GB) — this takes a moment.
+- **Speed depends on your disk.** The experts stream from storage, so a fast
+ NVMe SSD is the single biggest factor in tokens/second. On a slow or shared
+ disk, generation can be well under 1 token/second — that's expected, and it's
+ the honest cost of running a 744B model on a small machine.
+- **It's still the full model.** Placement only changes speed, never the model's
+ answers or precision.
+
+If something doesn't work, run `./coli doctor` — it reports exactly what's
+missing (compiler, model files, permissions) and how to fix it.
+
+---
+
+## Where to go next
+
+| Topic | Doc |
+|---|---|
+| Windows native build (and CUDA DLL) | [docs/windows.md](windows.md) |
+| Tuning: cache, prefetch, speculation | [docs/tuning.md](tuning.md) |
+| OpenAI-compatible API + web dashboard | [docs/api.md](api.md) |
+| Every environment variable | [docs/ENVIRONMENT.md](ENVIRONMENT.md) |
From cfcc742591a03a79754f68a7f1a6f0a7ad1738a5 Mon Sep 17 00:00:00 2001
From: JustVugg
Date: Sun, 19 Jul 2026 14:58:32 +0200
Subject: [PATCH 16/22] metal: advance the LFRU recency clock on the
GPU-prerouted decode path (#417)
On Metal, when routing is precomputed on the GPU (g_pre_idx), the moe fast
path bumps eusage/ehit/eheat for the selected experts but skips the one thing
the full CPU router does at the equivalent site: elast[layer][e] =
++eaccess_clock. So the session-local recency clock advances during prefill
(full router) but freezes the moment GPU-prerouted decode starts, and REPIN's
tier_pick_lfru() tie-breaker then runs on stale recency for the rest of the
run. Mirror the exact update the non-Metal path already does. Inside
#ifdef COLI_METAL, so CPU/CUDA are untouched; elast only feeds the LFRU
eviction heuristic, so this cannot affect output, only which experts REPIN
keeps warm.
Found and reported by @monotophic with a source-level trace repro
(ELAST_TRACE). Fix is inspection-verified against line ~3055; needs a
Metal build to exercise end-to-end.
Closes #417
Co-Authored-By: Claude Fable 5
---
c/glm.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/c/glm.c b/c/glm.c
index 26e7fc4..0f97ad2 100644
--- a/c/glm.c
+++ b/c/glm.c
@@ -2931,6 +2931,11 @@ static void moe(Model *m, Layer *l, int layer, float *x, int S, float *out, int
m->eusage[layer][idxs[(int64_t)s*K+kk]]++;
ehit_mark(m,layer,idxs[(int64_t)s*K+kk]);
if(m->eheat[layer][idxs[(int64_t)s*K+kk]]eheat[layer][idxs[(int64_t)s*K+kk]]++;
+ /* #417: la scorciatoia GPU-prerouted deve far avanzare l'orologio di recency
+ * come il percorso router completo (riga ~3055), altrimenti elast/eaccess_clock
+ * si congelano a fine prefill e il tie-breaker LFRU di REPIN gira su punteggi
+ * stantii durante il decode su Metal. */
+ m->elast[layer][idxs[(int64_t)s*K+kk]]=++m->eaccess_clock;
}
for(int d=0;d
Date: Sun, 19 Jul 2026 03:56:56 +0800
Subject: [PATCH 17/22] refactor: split glm.c into colibri.c + 4 header modules
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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).
---
.github/workflows/ci.yml | 8 +-
.gitignore | 2 +
c/Makefile | 53 +-
c/coli | 16 +-
c/{glm.c => colibri.c} | 1239 +----------------------------
c/kv_persist.h | 121 +++
c/quant.h | 672 ++++++++++++++++
c/sample.h | 143 ++++
c/setup.sh | 4 +-
c/telemetry.h | 189 +++++
c/tests/bench_dsa_select.c | 2 +-
c/tests/bench_topp.c | 2 +-
c/tests/test_dsa_select.c | 2 +-
c/tests/test_i4_grouped.c | 2 +-
c/tests/test_idot.c | 2 +-
c/tests/test_kv_alloc.c | 2 +-
c/tests/test_makefile_platform.py | 4 +-
c/tests/test_sample_nan.c | 2 +-
c/tests/test_stops.c | 2 +-
c/tests/test_topp.c | 2 +-
c/tests/test_uring.c | 2 +-
21 files changed, 1206 insertions(+), 1265 deletions(-)
rename c/{glm.c => colibri.c} (82%)
create mode 100644 c/kv_persist.h
create mode 100644 c/quant.h
create mode 100644 c/sample.h
create mode 100644 c/telemetry.h
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 72b7149..eb7e872 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,8 +12,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - name: Build glm
- run: cd c && make glm
+ - name: Build colibri
+ run: cd c && make colibri
- name: C test suite
run: cd c && make test-c
@@ -105,11 +105,11 @@ jobs:
make cuda-dll CUDA_ARCH=sm_80
test -f coli_cuda.dll || { echo "cuda-dll reported success but produced no DLL" >&2; exit 1; }
echo "coli_cuda.dll built (MSVC host)"
- - name: make glm CUDA_DLL=1 (host links backend_loader, not cudart)
+ - name: make colibri CUDA_DLL=1 (host links backend_loader, not cudart)
shell: msys2 {0}
run: |
cd c
- make glm CUDA_DLL=1
+ make colibri CUDA_DLL=1
test -f glm.exe || { echo "glm CUDA_DLL=1 reported success but produced no exe" >&2; exit 1; }
echo "glm.exe built against the DLL loader"
diff --git a/.gitignore b/.gitignore
index a65fd3d..b3e1193 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,8 @@ desktop/src-tauri/target/
desktop/src-tauri/gen/
# binari compilati (si rigenerano con make / coli build)
+c/colibri
+c/colibri.exe
c/glm
c/glm.exe
c/olmoe
diff --git a/c/Makefile b/c/Makefile
index 1f8583b..e33ad37 100644
--- a/c/Makefile
+++ b/c/Makefile
@@ -56,7 +56,7 @@ OMPL =
endif
CFLAGS = -O3 $(OMPC) -Wall -Wextra -Wno-unused-parameter -Wno-misleading-indentation -Wno-unused-function
# Opt-in: ARCH=native appends -mcpu=native (arm64 clang uses -mcpu, not -march),
-# which unlocks the i8mm SMMLA int8/int4 dot kernels in glm.c. ARCH unset ->
+# which unlocks the i8mm SMMLA int8/int4 dot kernels in colibri.c. ARCH unset ->
# no -mcpu, default build byte-identical. Apple clang knows apple-m4 / native.
ifneq ($(ARCH),)
CFLAGS += -mcpu=$(ARCH)
@@ -70,7 +70,7 @@ else ifneq ($(IS_WIN),)
# ARCH default = x86-64-v3 (portable binary with AVX2). For max speed on THIS
# machine use ARCH=native: on AVX-VNNI CPUs (Intel Alder Lake+, Meteor Lake+)
# it also unlocks the 128-bit VPDPBUSD int8/int4 dot kernel (dot_i8i8/dot_i4i8),
-# which the x86-64-v3 baseline does not define. The #ifdef guards in glm.c mean
+# which the x86-64-v3 baseline does not define. The #ifdef guards in colibri.c mean
# a v3 build simply compiles out the VNNI path - safe on any x86-64.
CC = gcc
ARCH ?= x86-64-v3
@@ -207,17 +207,18 @@ LDFLAGS += -framework Metal -framework Foundation -lc++
METAL_OBJ = backend_metal.o
endif
-all: glm$(EXE)
+all: colibri$(EXE)
-# phony 'glm' → 'glm.exe' on Windows (so 'make glm' and 'coli build' work on every platform)
-glm: glm$(EXE)
+# phony targets — 'glm' kept for backward compatibility
+colibri: colibri$(EXE)
+glm: colibri$(EXE)
# Config stamp: make only tracks file timestamps, not flag changes. Without this,
-# `make glm.exe CUDA_DLL=1` after a prior CPU-only build reports "up to date" and
-# silently keeps the CPU-only binary (no CUDA loader) — a build that looks like it
-# worked but isn't. We record the build-affecting flags in .build-config and rewrite
-# it ONLY when they change (evaluated here at parse time, so the file's timestamp
-# moves exactly when the config moves). glm.exe and the CUDA/loader objects depend
+# `make colibri.exe CUDA_DLL=1` after a prior CPU-only build reports "up to date"
+# and silently keeps the CPU-only binary (no CUDA loader) — a build that looks like
+# it worked but isn't. We record the build-affecting flags in .build-config and
+# rewrite it ONLY when they change (evaluated here at parse time, so the file's
+# timestamp moves exactly when the config moves). The binary and CUDA/loader objects depend
# on it, so they relink on a config change and stay put otherwise. (#306)
BUILD_CONFIG := $(CC)|$(CFLAGS)|$(LDFLAGS)|CUDA=$(CUDA)|CUDA_DLL=$(CUDA_DLL)|ARCH=$(ARCH)|CUDA_ARCH=$(CUDA_ARCH)|METAL=$(METAL)
BUILD_CONFIG_OLD := $(shell cat .build-config 2>/dev/null)
@@ -226,8 +227,8 @@ $(shell printf '%s' '$(BUILD_CONFIG)' > .build-config)
endif
.build-config: ;
-glm$(EXE): glm.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h $(CUDA_OBJ) $(METAL_OBJ) .build-config
- $(CC) $(CFLAGS) glm.c $(CUDA_OBJ) $(METAL_OBJ) -o glm$(EXE) $(LDFLAGS)
+colibri$(EXE): colibri.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h quant.h sample.h kv_persist.h telemetry.h $(CUDA_OBJ) $(METAL_OBJ) .build-config
+ $(CC) $(CFLAGS) colibri.c $(CUDA_OBJ) $(METAL_OBJ) -o colibri$(EXE) $(LDFLAGS)
# Windows runtime loader object: resolves coli_cuda_* from coli_cuda.dll.
backend_loader.o: backend_loader.c backend_cuda.h compat.h .build-config
@@ -285,7 +286,7 @@ PORTABLE_ARCH = native
endif
portable:
- $(MAKE) glm$(EXE) ARCH=$(PORTABLE_ARCH)
+ $(MAKE) colibri$(EXE) ARCH=$(PORTABLE_ARCH)
iobench$(EXE): iobench.c compat.h
$(CC) $(CFLAGS) iobench.c -o iobench$(EXE) $(LDFLAGS)
@@ -311,27 +312,27 @@ tests/test_schema_gbnf$(EXE): tests/test_schema_gbnf.c schema_gbnf.h grammar.h j
tests/test_decode_batch$(EXE): tests/test_decode_batch.c decode_batch.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
-tests/test_idot$(EXE): tests/test_idot.c glm.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h
+tests/test_idot$(EXE): tests/test_idot.c colibri.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h quant.h sample.h kv_persist.h telemetry.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
-tests/test_i4_grouped$(EXE): tests/test_i4_grouped.c glm.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h
+tests/test_i4_grouped$(EXE): tests/test_i4_grouped.c colibri.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h quant.h sample.h kv_persist.h telemetry.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
-tests/test_stops$(EXE): tests/test_stops.c glm.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h
+tests/test_stops$(EXE): tests/test_stops.c colibri.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h quant.h sample.h kv_persist.h telemetry.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
-tests/test_topp$(EXE): tests/test_topp.c glm.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h
+tests/test_topp$(EXE): tests/test_topp.c colibri.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h quant.h sample.h kv_persist.h telemetry.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
# bench_topp is a microbenchmark (old qsort vs new heap partial-select, #335), NOT a test
# gate -- intentionally absent from TEST_BINS. Build on demand: make tests/bench_topp
-tests/bench_topp$(EXE): tests/bench_topp.c glm.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h
+tests/bench_topp$(EXE): tests/bench_topp.c colibri.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h quant.h sample.h kv_persist.h telemetry.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
-tests/test_sample_nan$(EXE): tests/test_sample_nan.c glm.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h
+tests/test_sample_nan$(EXE): tests/test_sample_nan.c colibri.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h quant.h sample.h kv_persist.h telemetry.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
-tests/test_kv_alloc$(EXE): tests/test_kv_alloc.c glm.c st.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h
+tests/test_kv_alloc$(EXE): tests/test_kv_alloc.c colibri.c st.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h quant.h sample.h kv_persist.h telemetry.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
tests/test_logit_nan$(EXE): tests/test_logit_nan.c glm.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h
@@ -343,15 +344,15 @@ tests/test_i4_acc512$(EXE): tests/test_i4_acc512.c
tests/test_compat_direct$(EXE): tests/test_compat_direct.c compat.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
-tests/test_dsa_select$(EXE): tests/test_dsa_select.c glm.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h
+tests/test_dsa_select$(EXE): tests/test_dsa_select.c colibri.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h quant.h sample.h kv_persist.h telemetry.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
# bench_dsa_select is a microbenchmark (old qsort vs new quickselect partial-select, #356),
# NOT a test gate -- intentionally absent from TEST_BINS. Build on demand: make tests/bench_dsa_select
-tests/bench_dsa_select$(EXE): tests/bench_dsa_select.c glm.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h
+tests/bench_dsa_select$(EXE): tests/bench_dsa_select.c colibri.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h quant.h sample.h kv_persist.h telemetry.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
-tests/test_uring$(EXE): tests/test_uring.c glm.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h
+tests/test_uring$(EXE): tests/test_uring.c colibri.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h quant.h sample.h kv_persist.h telemetry.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
test-c: $(TEST_BINS)
@@ -368,12 +369,12 @@ check:
$(MAKE) portable
$(MAKE) test
-install: glm$(EXE) olmoe$(EXE)
+install: colibri$(EXE) olmoe$(EXE)
$(INSTALL) -d $(DESTDIR)$(BINDIR)
$(INSTALL) -d $(DESTDIR)$(LIBEXECDIR)
$(INSTALL) -d $(DESTDIR)$(LIBEXECDIR)/tools
$(INSTALL) -m 755 coli $(DESTDIR)$(BINDIR)/coli
- $(INSTALL) -m 755 glm$(EXE) $(DESTDIR)$(LIBEXECDIR)/glm$(EXE)
+ $(INSTALL) -m 755 colibri$(EXE) $(DESTDIR)$(LIBEXECDIR)/colibri$(EXE)
$(INSTALL) -m 755 olmoe$(EXE) $(DESTDIR)$(LIBEXECDIR)/olmoe$(EXE)
$(INSTALL) -m 644 resource_plan.py doctor.py openai_server.py $(DESTDIR)$(LIBEXECDIR)/
$(INSTALL) -m 644 tools/*.py $(DESTDIR)$(LIBEXECDIR)/tools/
@@ -387,4 +388,4 @@ clean:
bench: iobench$(EXE)
@if [ -n "$(ARGS)" ]; then ./iobench$(EXE) $(ARGS); else echo "built iobench$(EXE) — run: ./iobench$(EXE) "; fi
-.PHONY: all glm cuda-test cuda-bench cuda-dll portable test-c test-python test check clean install uninstall bench
+.PHONY: all colibri glm cuda-test cuda-bench cuda-dll portable test-c test-python test check clean install uninstall bench
diff --git a/c/coli b/c/coli
index 803c22b..d6728fe 100755
--- a/c/coli
+++ b/c/coli
@@ -54,16 +54,20 @@ from version import __version__ as _version
# guess is right (e.g. a custom packaging layout).
_EXE = ".exe" if sys.platform == "win32" else ""
_LIBEXEC = os.path.join(os.path.dirname(HERE), "libexec", "colibri")
+_here_colibri = os.path.join(HERE, "colibri" + _EXE)
_here_glm = os.path.join(HERE, "glm" + _EXE)
if os.environ.get("COLI_ENGINE"):
GLM = os.environ["COLI_ENGINE"]
TOOLS = os.path.join(os.path.dirname(GLM), "tools")
+elif os.path.exists(_here_colibri):
+ GLM = _here_colibri
+ TOOLS = os.path.join(HERE, "tools")
elif os.path.exists(_here_glm):
GLM = _here_glm
TOOLS = os.path.join(HERE, "tools")
else:
- GLM = os.path.join(_LIBEXEC, "glm" + _EXE)
+ GLM = os.path.join(_LIBEXEC, "colibri" + _EXE)
TOOLS = os.path.join(_LIBEXEC, "tools")
sys.path.insert(0, _LIBEXEC) # so `import resource_plan`, `doctor`, `openai_server` still resolve
@@ -241,13 +245,13 @@ def env_for(a):
e["COLI_CUDA"]="0"; e.pop("CUDA_EXPERT_GB",None); e.pop("CUDA_DENSE",None)
else:
if not cuda_binary():
- sys.exit(f"{C.yel}--gpu needs the CUDA build:{C.r} make glm CUDA=1 (this binary is CPU-only)")
+ sys.exit(f"{C.yel}--gpu needs the CUDA build:{C.r} make colibri CUDA=1 (this binary is CPU-only)")
e["COLI_CUDA"]="1"
if a.gpu!="auto": e["COLI_GPUS"]=a.gpu
e.setdefault("CUDA_DENSE","1")
if a.vram and a.gpu!="none":
if not cuda_binary():
- sys.exit(f"{C.yel}--vram needs the CUDA build:{C.r} make glm CUDA=1 (this binary is CPU-only)")
+ sys.exit(f"{C.yel}--vram needs the CUDA build:{C.r} make colibri CUDA=1 (this binary is CPU-only)")
e["COLI_CUDA"]="1"; e["CUDA_EXPERT_GB"]=str(a.vram)
return e
@@ -421,8 +425,8 @@ def cmd_build(a):
banner("build")
if not os.path.exists(os.path.join(HERE, "Makefile")):
sys.exit(f"{C.yel}coli build{C.r} only works from a source checkout (this is an installed copy).\n"
- f" Clone https://github.com/JustVugg/colibri and run ./setup.sh, or make -C c glm.")
- sys.exit(subprocess.call(["make","-C",HERE,"glm"]))
+ f" Clone https://github.com/JustVugg/colibri and run ./setup.sh, or make -C c colibri.")
+ sys.exit(subprocess.call(["make","-C",HERE,"colibri"]))
def cmd_info(a):
banner("info")
@@ -802,7 +806,7 @@ def cmd_stop(a):
if "coli" in cmd and " serve" in cmd and pid!=os.getpid():
if not any(p==pid for p,_ in targets): targets.append((pid,"coli serve (cmdline)"))
comm=open(f"/proc/{pd}/comm").read().strip()
- if comm in ("glm","exe","olmoe"):
+ if comm in ("colibri","glm","exe","olmoe"):
env=open(f"/proc/{pd}/environ","rb").read().replace(b"\0",b"\n").decode("utf-8","replace")
if "SERVE=1" in env: targets.append((pid,f"engine `{comm}` (SERVE=1)"))
except (OSError,PermissionError): continue
diff --git a/c/glm.c b/c/colibri.c
similarity index 82%
rename from c/glm.c
rename to c/colibri.c
index 0f97ad2..554a7fa 100644
--- a/c/glm.c
+++ b/c/colibri.c
@@ -73,21 +73,6 @@ static int g_metal_gemm_min=16; /* COLI_METAL_GEMM_MIN: min rows to send a mat
static const int *g_pre_idx; static const float *g_pre_w; static const int *g_pre_keff;
static const float *g_pre_sh; /* output dello shared expert gia' calcolato su GPU */
#endif
-#ifdef __AVX2__
-#include
-static inline float hsum256(__m256 v){ /* somma orizzontale di 8 float */
- __m128 lo=_mm256_castps256_ps128(v), hi=_mm256_extractf128_ps(v,1);
- lo=_mm_add_ps(lo,hi); __m128 sh=_mm_movehl_ps(lo,lo); lo=_mm_add_ps(lo,sh);
- sh=_mm_shuffle_ps(lo,lo,1); lo=_mm_add_ss(lo,sh); return _mm_cvtss_f32(lo);
-}
-#elif defined(__ARM_NEON)
-#include /* Apple Silicon / aarch64: kernel NEON */
-#elif defined(__VSX__)
-#include /* POWER8+ (ppc64le): kernel VSX */
-#undef vector /* igiene: si usano __vector/__bool espliciti */
-#undef pixel
-#undef bool
-#endif
#ifdef __APPLE__
#include /* host_statistics64: MemAvailable di macOS */
#endif
@@ -218,13 +203,21 @@ typedef struct {
uint64_t bytes_mtp, bytes_main; /* byte letti da disco per tipo layer */
} Model;
-static void usage_save(Model *m); /* cache che impara: definita accanto a stats_dump */
-static void tiers_emit(Model *m);
-static void ehit_mark(Model *m, int layer, int eid);
-static void emap_emit(Model *m);
-static void hits_emit(Model *m);
-static void hwinfo_emit(Model *m);
-static int64_t expert_bytes_probe(Model *m, int ebits); /* PROF: tier sizes in the report */
+#include "quant.h"
+static int g_no_fused_pair=0;
+static int g_spec_pin=1;
+static int g_spec_live=0;
+static inline int spec_pinned(void){ return g_spec_pin && g_spec_live; }
+
+static void matmul_qt_ex(float *y, const float *x, QT *w, int S, int allow_idot);
+static void matmul_qt(float *y, const float *x, QT *w, int S){ matmul_qt_ex(y,x,w,S,1); }
+
+static void expert_gate_up(float *g,float *u,const float *x,QT *wg,QT *wu,int S){
+ if(!g_no_fused_pair&&!spec_pinned()&&S==1&&wg->fmt==2&&wu->fmt==2&&wg->I==wu->I&&wg->O==wu->O)
+ matmul_i4_pair(g,u,x,wg->q4,wg->s,wu->q4,wu->s,wg->I,wg->O);
+ else { matmul_qt(g,x,wg,S); matmul_qt(u,x,wu,S); }
+}
+
static int g_repin;
static uint64_t g_last_repin;
#ifdef COLI_CUDA
@@ -332,713 +325,15 @@ static float *falloc(int64_t n){
if(n<0 || (uint64_t)n > SIZE_MAX/sizeof(float)){ fprintf(stderr,"falloc: n=%lld is out of range\n",(long long)n); exit(1); }
float *p=malloc((size_t)n*sizeof(float)); if(!p){fprintf(stderr,"OOM\n");exit(1);} return p; }
-/* ---- Accumulatore int4->float a 512 bit / 512-bit int4->float accumulator ----
- * Stessa matematica lossless di matmul_i4 (nibble->f32, FMA), ma 32 pesi/iter su
- * due catene FMA indipendenti. NON bit-identico al vecchio ordine: la riduzione
- * ad albero accumula MENO errore della somma sequenziale (misurato 2-4x più
- * vicino all'oracolo double sulle forme reali; perplexity invariata, +4-7% sul
- * decode con routing CPU-heavy — vedi docs/experiments/glm52-6x5090-2026-07-12.md).
- * EN: same lossless math as matmul_i4, 32 weights/iter on two independent FMA
- * chains. Not bit-identical to the old order: tree reduction accumulates LESS
- * rounding than sequential summation. I4_ACC512=0 restores the old order (A/B). */
-#if defined(__AVX512F__) && defined(__AVX512BW__)
-static int g_i4_acc512=1;
-static inline float dot_i4f_avx512(const uint8_t *w,const float *x,int I){
- const __m128i m4=_mm_set1_epi8(0x0F); const __m512i b8=_mm512_set1_epi32(8);
- __m512 acc0=_mm512_setzero_ps(),acc1=_mm512_setzero_ps(); int i=0;
- for(;i+32<=I;i+=32){ __m128i by=_mm_loadu_si128((const __m128i*)(w+(i>>1)));
- __m128i lo=_mm_and_si128(by,m4),hi=_mm_and_si128(_mm_srli_epi16(by,4),m4);
- __m128i n0=_mm_unpacklo_epi8(lo,hi),n1=_mm_unpackhi_epi8(lo,hi);
- __m512 w0=_mm512_cvtepi32_ps(_mm512_sub_epi32(_mm512_cvtepu8_epi32(n0),b8));
- __m512 w1=_mm512_cvtepi32_ps(_mm512_sub_epi32(_mm512_cvtepu8_epi32(n1),b8));
- acc0=_mm512_fmadd_ps(_mm512_loadu_ps(x+i),w0,acc0);
- acc1=_mm512_fmadd_ps(_mm512_loadu_ps(x+i+16),w1,acc1);
- }
- return _mm512_reduce_add_ps(_mm512_add_ps(acc0,acc1));
-}
-/* selftest contro il riferimento scalare (I4_ACC512_TEST=1): copre l'ordine dei
- * nibble e ogni multiplo di 32. / selftest vs the scalar reference. */
-static int i4_acc512_selftest(void){
- enum { N=224 }; uint8_t w[(N+1)/2]; float x[N];
- for(int i=0;i>1]=(uint8_t)(q+8);
- else w[i>>1]|=(uint8_t)((q+8)<<4);
- x[i]=(float)(((i*29+7)%101)-50)/37.f;
- }
- for(int n=32;n<=N;n+=32){
- float ref=0; for(int i=0;i>1]>>((i&1)*4))&15)-8);
- float got=dot_i4f_avx512(w,x,n),tol=2e-5f*(1.f+fabsf(ref));
- if(fabsf(got-ref)>tol){ fprintf(stderr,"AVX512 i4 selftest n=%d: %.9g != %.9g\n",n,got,ref); return 0; }
- }
- return 1;
-}
-#endif
-/* y[S,O] = x[S,I] @ W^T, W[O,I] f32 */
-static void matmul(float *y, const float *x, const float *W, int S, int I, int O){
- #pragma omp parallel for schedule(static)
- for (int o=0;o>1))); /* 8 byte=16 nibble */
- __m128i lo=_mm_and_si128(by,m4), hi=_mm_and_si128(_mm_srli_epi16(by,4),m4);
- __m128i nib=_mm_unpacklo_epi8(lo,hi); /* nibble in ordine */
- __m256 w0=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(nib),b8));
- __m256 w1=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(_mm_srli_si128(nib,8)),b8));
- acc=_mm256_fmadd_ps(_mm256_loadu_ps(xs+i), w0, acc);
- acc=_mm256_fmadd_ps(_mm256_loadu_ps(xs+i+8), w1, acc); }
- a=hsum256(acc);
-#elif defined(__ARM_NEON)
- const uint8x8_t m4=vdup_n_u8(0x0F); const int8x8_t b8=vdup_n_s8(8);
- float32x4_t ac0=vdupq_n_f32(0), ac1=vdupq_n_f32(0);
- for(;i+16<=I;i+=16){ uint8x8_t by=vld1_u8(w+(i>>1)); /* 8 byte=16 nibble */
- uint8x8x2_t z=vzip_u8(vand_u8(by,m4), vshr_n_u8(by,4)); /* nibble in ordine */
- int16x8_t w0=vmovl_s8(vsub_s8(vreinterpret_s8_u8(z.val[0]),b8));
- int16x8_t w1=vmovl_s8(vsub_s8(vreinterpret_s8_u8(z.val[1]),b8));
- ac0=vfmaq_f32(ac0, vld1q_f32(xs+i), vcvtq_f32_s32(vmovl_s16(vget_low_s16(w0))));
- ac1=vfmaq_f32(ac1, vld1q_f32(xs+i+4), vcvtq_f32_s32(vmovl_s16(vget_high_s16(w0))));
- ac0=vfmaq_f32(ac0, vld1q_f32(xs+i+8), vcvtq_f32_s32(vmovl_s16(vget_low_s16(w1))));
- ac1=vfmaq_f32(ac1, vld1q_f32(xs+i+12), vcvtq_f32_s32(vmovl_s16(vget_high_s16(w1)))); }
- a=vaddvq_f32(vaddq_f32(ac0,ac1));
-#endif
-#if defined(__AVX512F__) && defined(__AVX512BW__)
- }
-#endif
- for(;i+1>1]; int lo=(int)(byte&0xF)-8, hi=(int)(byte>>4)-8;
- a += xs[i]*(float)lo + xs[i+1]*(float)hi; }
- if(i>1]; int lo=(int)(byte&0xF)-8; a += xs[i]*(float)lo; }
- y[(int64_t)s*O+o]=a*sc; } }
-}
-/* y[S,O] = x[S,I] @ W^T with W int4 packed (2/byte) + per-GROUP scales (fmt=4).
- * Same nibble math as matmul_i4, but the scale changes every `gs` elements along I.
- * The accumulator resets at each group boundary: dot(x[grp], w[grp]) * scale[grp].
- * gs MUST be a multiple of 16 (the AVX2 vector width). */
-static void matmul_i4_grouped(float *y, const float *x, const uint8_t *q4, const float *scale,
- int S, int I, int O, int gs){
- int rb=(I+1)/2; int ng=(I+gs-1)/gs;
- #pragma omp parallel for schedule(static)
- for(int o=0;oI) glen=I-base;
- float sc=scl[g];
- int i=base;
-#ifdef __AVX2__
- const __m128i m4=_mm_set1_epi8(0x0F); const __m256i b8=_mm256_set1_epi32(8);
- __m256 acc=_mm256_setzero_ps();
- for(; i+16<=base+glen; i+=16){ __m128i by=_mm_loadl_epi64((const __m128i*)(w+(i>>1)));
- __m128i lo=_mm_and_si128(by,m4),hi=_mm_and_si128(_mm_srli_epi16(by,4),m4);
- __m128i nib=_mm_unpacklo_epi8(lo,hi);
- __m256 w0=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(nib),b8));
- __m256 w1=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(_mm_srli_si128(nib,8)),b8));
- acc=_mm256_fmadd_ps(_mm256_loadu_ps(xs+i), w0, acc);
- acc=_mm256_fmadd_ps(_mm256_loadu_ps(xs+i+8), w1, acc); }
- a+=hsum256(acc)*sc;
-#endif
- /* scalar tail for the group remainder */
- for(; i >1];
- a+=(xs[i]*(float)((int)(byte&0xF)-8)+xs[i+1]*(float)((int)(byte>>4)-8))*sc; }
- else { uint8_t byte=w[i>>1]; a+=xs[i]*(float)((int)(byte&0xF)-8)*sc; }
- }
- }
- y[(int64_t)s*O+o]=a;
- }
- }
-}
-/* Decode hot path for gate+up: same exact q4 dot products as matmul_i4, but one
- * OpenMP dispatch covers both matrices. KTransformers uses persistent pools;
- * this keeps colibri dependency-free while removing one team launch/expert. */
-static void matmul_i4_pair(float *yg, float *yu, const float *x,
- const uint8_t *qg, const float *sg,
- const uint8_t *qu, const float *su, int I, int O){
- int rb=(I+1)/2;
- #pragma omp parallel for schedule(static)
- for(int z=0;z<2*O;z++){
- int o=z>1)));
- __m128i lo=_mm_and_si128(by,m4),hi=_mm_and_si128(_mm_srli_epi16(by,4),m4);
- __m128i nib=_mm_unpacklo_epi8(lo,hi);
- __m256 w0=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(nib),b8));
- __m256 w1=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(_mm_srli_si128(nib,8)),b8));
- acc=_mm256_fmadd_ps(_mm256_loadu_ps(x+i),w0,acc);
- acc=_mm256_fmadd_ps(_mm256_loadu_ps(x+i+8),w1,acc); }
- a=hsum256(acc);
-#elif defined(__ARM_NEON)
- const uint8x8_t m4=vdup_n_u8(0x0F); const int8x8_t b8=vdup_n_s8(8);
- float32x4_t ac0=vdupq_n_f32(0),ac1=vdupq_n_f32(0);
- for(;i+16<=I;i+=16){ uint8x8_t by=vld1_u8(w+(i>>1));
- uint8x8x2_t n=vzip_u8(vand_u8(by,m4),vshr_n_u8(by,4));
- int16x8_t w0=vmovl_s8(vsub_s8(vreinterpret_s8_u8(n.val[0]),b8));
- int16x8_t w1=vmovl_s8(vsub_s8(vreinterpret_s8_u8(n.val[1]),b8));
- ac0=vfmaq_f32(ac0,vld1q_f32(x+i),vcvtq_f32_s32(vmovl_s16(vget_low_s16(w0))));
- ac1=vfmaq_f32(ac1,vld1q_f32(x+i+4),vcvtq_f32_s32(vmovl_s16(vget_high_s16(w0))));
- ac0=vfmaq_f32(ac0,vld1q_f32(x+i+8),vcvtq_f32_s32(vmovl_s16(vget_low_s16(w1))));
- ac1=vfmaq_f32(ac1,vld1q_f32(x+i+12),vcvtq_f32_s32(vmovl_s16(vget_high_s16(w1)))); }
- a=vaddvq_f32(vaddq_f32(ac0,ac1));
-#endif
-#if defined(__AVX512F__) && defined(__AVX512BW__)
- }
-#endif
- for(;i+1>1]; a+=x[i]*(float)((b&15)-8)+x[i+1]*(float)((b>>4)-8); }
- if(i>1]&15)-8);
- (z=2) non calcolano la STESSA funzione. Tre interruttori dipendono da S: il gate
- * int4-IDOT (S>=g_i4s — asimmetrico proprio dove g_i4s>1), la fusione gate+up solo-S==1,
- * e la soglia righe del GEMM Metal. Con SPEC_PIN=1 (default) ogni forward emesso mentre
- * i draft del modello sono attivi resta sulla famiglia di kernel di S=1: draft e verifica
- * coincidono per costruzione. Prefill e decode non speculativo sono intoccati.
- * EN: MTP acceptance collapses when the draft (S=1) and verify (S>=2) forwards do not
- * compute the SAME function. Three switches are S-dependent: the int4 IDOT gate
- * (S>=g_i4s — asymmetric exactly on ISAs where g_i4s>1), the S==1-only gate+up fusion,
- * and the Metal GEMM row threshold. SPEC_PIN=1 (default) pins every forward issued
- * while model drafts are live to the platform's S=1 kernel family, so draft and verify
- * agree by construction; prefill and non-speculative decode are untouched.
- * SPEC_PIN=0 restores the S-dependent gates (A/B). */
-static int g_spec_pin=1;
-static int g_spec_live=0; /* set by spec_decode while drafts are live */
-static inline int spec_pinned(void){ return g_spec_pin && g_spec_live; }
-static void expert_gate_up(float *g,float *u,const float *x,QT *wg,QT *wu,int S){
- if(!g_no_fused_pair&&!spec_pinned()&&S==1&&wg->fmt==2&&wu->fmt==2&&wg->I==wu->I&&wg->O==wu->O)
- matmul_i4_pair(g,u,x,wg->q4,wg->s,wu->q4,wu->s,wg->I,wg->O);
- else { matmul_qt(g,x,wg,S); matmul_qt(u,x,wu,S); }
-}
-/* y[S,O] = x[S,I] @ W^T con W int2 impacchettato (4 valori/byte) + scala[O]. nibble 2-bit -> [-2,1]. */
-static void matmul_i2(float *y, const float *x, const uint8_t *q2, const float *scale, int S, int I, int O){
- int rb=(I+3)/4;
- #pragma omp parallel for schedule(static)
- for (int o=0;o>2))); /* 4 byte=16 valori */
- __m128i p0=_mm_and_si128(by,m2), p1=_mm_and_si128(_mm_srli_epi16(by,2),m2);
- __m128i p2=_mm_and_si128(_mm_srli_epi16(by,4),m2), p3=_mm_and_si128(_mm_srli_epi16(by,6),m2);
- __m128i lo=_mm_unpacklo_epi8(p0,p1), hi=_mm_unpacklo_epi8(p2,p3);
- __m128i nib=_mm_unpacklo_epi16(lo,hi); /* 16 valori in ordine */
- __m256 w0=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(nib),b2));
- __m256 w1=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(_mm_srli_si128(nib,8)),b2));
- acc=_mm256_fmadd_ps(_mm256_loadu_ps(xs+i), w0, acc);
- acc=_mm256_fmadd_ps(_mm256_loadu_ps(xs+i+8), w1, acc); }
- a=hsum256(acc);
-#elif defined(__ARM_NEON)
- const uint8x8_t m2v=vdup_n_u8(3); const int8x8_t b2v=vdup_n_s8(2);
- float32x4_t ac0=vdupq_n_f32(0), ac1=vdupq_n_f32(0);
- for(;i+16<=I;i+=16){ uint32_t wd; memcpy(&wd, w+(i>>2), 4); /* 4 byte=16 valori */
- uint8x8_t by=vreinterpret_u8_u32(vdup_n_u32(wd));
- uint8x8x2_t z01=vzip_u8(vand_u8(by,m2v), vand_u8(vshr_n_u8(by,2),m2v));
- uint8x8x2_t z23=vzip_u8(vand_u8(vshr_n_u8(by,4),m2v), vshr_n_u8(by,6));
- uint16x4x2_t zz=vzip_u16(vreinterpret_u16_u8(z01.val[0]), vreinterpret_u16_u8(z23.val[0]));
- int16x8_t w0=vmovl_s8(vsub_s8(vreinterpret_s8_u16(zz.val[0]),b2v)); /* 16 valori in ordine */
- int16x8_t w1=vmovl_s8(vsub_s8(vreinterpret_s8_u16(zz.val[1]),b2v));
- ac0=vfmaq_f32(ac0, vld1q_f32(xs+i), vcvtq_f32_s32(vmovl_s16(vget_low_s16(w0))));
- ac1=vfmaq_f32(ac1, vld1q_f32(xs+i+4), vcvtq_f32_s32(vmovl_s16(vget_high_s16(w0))));
- ac0=vfmaq_f32(ac0, vld1q_f32(xs+i+8), vcvtq_f32_s32(vmovl_s16(vget_low_s16(w1))));
- ac1=vfmaq_f32(ac1, vld1q_f32(xs+i+12), vcvtq_f32_s32(vmovl_s16(vget_high_s16(w1)))); }
- a=vaddvq_f32(vaddq_f32(ac0,ac1));
-#endif
- for(;i>2]; int sh=(i&3)*2; a += xs[i]*(float)((int)((byte>>sh)&3)-2); }
- y[(int64_t)s*O+o]=a*sc; } }
-}
-/* ---- KERNEL INTERI (IDOT): attivazioni quantizzate a int8 per riga (absmax/127,
- * stile Q8_0), prodotto scalare INTERO via maddubs/madd AVX2 — niente conversione
- * f32 dei pesi nel ciclo caldo. ~2-3x sui matmul quantizzati; errore aggiunto ~0.3%
- * RMS per matmul (attivazione int8), IDOT=0 torna al percorso f32 esatto. */
-#if defined(__AVX512VNNI__) && defined(__AVX512BW__)
-#define IDOT_KERNEL "avx512-vnni"
-#elif defined(__AVXVNNI__) && defined(__AVX2__)
-#define IDOT_KERNEL "avx-vnni"
-#elif defined(__AVX2__)
-#define IDOT_KERNEL "avx2"
-#elif defined(__ARM_NEON) && defined(__ARM_FEATURE_MATMUL_INT8)
-#define IDOT_KERNEL "neon-i8mm"
-#elif defined(__ARM_NEON)
-#define IDOT_KERNEL "neon"
-#elif defined(__VSX__)
-#define IDOT_KERNEL "vsx"
-#else
-#define IDOT_KERNEL "scalar"
-#endif
-static int g_idot=1;
-#if defined(__ARM_NEON) && defined(__ARM_FEATURE_DOTPROD)
-static int g_i4s=1; /* SDOT presente: int4 IDOT conviene anche a S=1 (decode). Misurato
- * su Apple M-series: +14%%, expert-matmul -16%%. EN: with SDOT, int4
- * IDOT pays even at S=1 (decode); measured on Apple M-series. */
-#elif defined(__VSX__)
-static int g_i4s=1; /* POWER8 vec_msum: qui il fallback f32 e' SCALARE, quindi l'IDOT
- * int4 conviene anche a S=1. Misurato su POWER8 S824 (vedi PR).
- * EN: on VSX the f32 fallback is plain scalar C, so int4 IDOT
- * pays even at S=1. Measured on a POWER8 S824 (see PR). */
-#else
-static int g_i4s=2; /* senza SDOT / altrove: soglia originale (misura AVX2 dell'autore).
- * EN: without SDOT / elsewhere: original threshold (author's AVX2). */
-#endif
-static inline float qrow_i8(const float *x, int8_t *q, int I){
- float amax=0; for(int i=0;iamax)amax=a; }
- float s=amax/127.f; if(s<1e-12f) s=1e-12f; float inv=1.f/s;
- for(int i=0;i s32 directly, 64 bytes/iter, no 16-bit intermediate.
- * AVX-512 has no vpsignb: |w| via abs, sign folded into x with a mask-negate
- * (w==0 -> product 0 either way). |x|<=127 (qrow_i8), |w|<=128 as u8: each
- * s32 lane adds <= 4*128*127, safe up to I=16384 like the AVX2 bound. */
- __m512i acc=_mm512_setzero_si512();
- for(;i+64<=I;i+=64){
- __m512i wv=_mm512_loadu_si512((const void*)(w+i));
- __m512i xv=_mm512_loadu_si512((const void*)(x+i));
- __mmask64 neg=_mm512_movepi8_mask(wv);
- __m512i xs=_mm512_mask_sub_epi8(xv,neg,_mm512_setzero_si512(),xv);
- acc=_mm512_dpbusd_epi32(acc,_mm512_abs_epi8(wv),xs);
- }
- sum=_mm512_reduce_add_epi32(acc);
-#elif defined(__AVXVNNI__) && defined(__AVX2__)
- /* AVX-VNNI 128-bit: vpdpbusd u8*s8 -> s32, 16 byte/iter. Stesso trucco del
- * segno della variante 512-bit: |w| via abs, segno piegato in x con maschera
- * (w==0 -> product 0). __AVX2__ serve per _mm_sign_epi8 / abs. */
- __m128i acc=_mm_setzero_si128();
- for(;i+16<=I;i+=16){
- __m128i wv=_mm_loadu_si128((const __m128i*)(w+i));
- __m128i xv=_mm_loadu_si128((const __m128i*)(x+i));
- __m128i xs=_mm_sign_epi8(xv,wv); /* x * sign(w); _mm_sign zona __AVX2__ */
- acc=_mm_dpbusd_epi32(acc,_mm_abs_epi8(wv),xs);
- }
- sum=hsum128_i32(acc);
-#elif defined(__AVX2__)
- __m256i acc=_mm256_setzero_si256(); const __m256i ones=_mm256_set1_epi16(1);
- for(;i+32<=I;i+=32){
- __m256i wv=_mm256_loadu_si256((const __m256i*)(w+i));
- __m256i xv=_mm256_loadu_si256((const __m256i*)(x+i));
- __m256i p=_mm256_maddubs_epi16(_mm256_sign_epi8(wv,wv),_mm256_sign_epi8(xv,wv));
- acc=_mm256_add_epi32(acc,_mm256_madd_epi16(p,ones));
- }
- sum=hsum256_i32(acc);
-#elif defined(__ARM_NEON)
- /* ARM: SDOT nativo se disponibile (Apple Silicon: sempre); altrimenti vmull/vpadal.
- * Stesso bound anti-overflow del trucco AVX2: coppie <= 128*127*2 = 32512 < 32767. */
-#if defined(__ARM_FEATURE_DOTPROD)
- /* 4 accumulatori indipendenti: SDOT ha latenza ~3-4 cicli, con un solo acc la
- * catena seriale strozza il core a ~26 GB/s di pesi; con 4 lane indipendenti il
- * dot diventa memory-bound (misurato su M4: 26 -> 63 GB/s per core, 2.4x). */
- int32x4_t a0=vdupq_n_s32(0),a1=vdupq_n_s32(0),a2=vdupq_n_s32(0),a3=vdupq_n_s32(0);
- for(;i+64<=I;i+=64){
- a0=vdotq_s32(a0,vld1q_s8(w+i), vld1q_s8(x+i));
- a1=vdotq_s32(a1,vld1q_s8(w+i+16),vld1q_s8(x+i+16));
- a2=vdotq_s32(a2,vld1q_s8(w+i+32),vld1q_s8(x+i+32));
- a3=vdotq_s32(a3,vld1q_s8(w+i+48),vld1q_s8(x+i+48));
- }
- int32x4_t acc=vaddq_s32(vaddq_s32(a0,a1),vaddq_s32(a2,a3));
- for(;i+16<=I;i+=16) acc=vdotq_s32(acc,vld1q_s8(w+i),vld1q_s8(x+i));
- sum=vaddvq_s32(acc);
-#else
- int32x4_t acc=vdupq_n_s32(0);
- for(;i+16<=I;i+=16){
- int8x16_t wv=vld1q_s8(w+i), xv=vld1q_s8(x+i);
- int16x8_t p=vmull_s8(vget_low_s8(wv),vget_low_s8(xv));
- p=vmlal_s8(p,vget_high_s8(wv),vget_high_s8(xv));
- acc=vpadalq_s16(acc,p);
- }
- sum=vaddvq_s32(acc);
-#endif
-#elif defined(__VSX__)
- /* POWER8: vec_msum (s8 x u8 -> s32) somma i prodotti byte DIRETTAMENTE in lane
- * s32, 16 byte/iter: il bound anti-saturazione a 16 bit di maddubs qui non serve.
- * Stesso trucco del segno (|w| u8 per x*sign(w) s8), ma |w| via select+sub MODULO
- * e non vec_abs: -128 deve diventare 128 u8, non saturare a 127.
- * EN: vec_msum accumulates byte products straight into s32 lanes; |w| is built
- * with a modulo subtract select instead of vec_abs so w=-128 wraps to 128 (u8)
- * rather than saturating to 127. |x|<=127 from qrow_i8, so x negation is safe. */
- __vector signed int acc=vec_splats(0);
- const __vector signed char vz=vec_splats((signed char)0);
- for(;i+16<=I;i+=16){
- __vector signed char wv=vec_xl(0,(const signed char*)(w+i));
- __vector signed char xv=vec_xl(0,(const signed char*)(x+i));
- __vector __bool char neg=vec_cmplt(wv,vz);
- __vector signed char xs=vec_sel(xv,vec_sub(vz,xv),neg);
- __vector unsigned char wa=(__vector unsigned char)vec_sel(wv,vec_sub(vz,wv),neg);
- acc=vec_msum(xs,wa,acc);
- }
- sum=vec_extract(acc,0)+vec_extract(acc,1)+vec_extract(acc,2)+vec_extract(acc,3);
-#endif
- for(;i int8 [-8,7] al volo, poi stesso trucco */
-static inline int32_t dot_i4i8(const uint8_t *w4, const int8_t *x, int I){
- int32_t sum=0; int i=0;
-#if defined(__AVX512VNNI__) && defined(__AVX512BW__)
- /* 32 bytes = 64 nibbles -> int8 in [-8,7], one vpdpbusd per 64 values.
- * 256-bit unpack leaves values in per-128-lane order [0-15][32-47]/[16-31][48-63];
- * dot pairing is order-invariant, so permute x's 128-bit blocks to match
- * instead of re-ordering w (one vpermq per iter, off the critical unpack path). */
- const __m256i m4v=_mm256_set1_epi8(0x0F);
- const __m512i b8v=_mm512_set1_epi8(8);
- const __m512i xidx=_mm512_setr_epi64(0,1,4,5,2,3,6,7);
- __m512i acc=_mm512_setzero_si512();
- for(;i+64<=I;i+=64){
- __m256i by=_mm256_loadu_si256((const __m256i*)(w4+(i>>1)));
- __m256i lo=_mm256_and_si256(by,m4v), hi=_mm256_and_si256(_mm256_srli_epi16(by,4),m4v);
- __m256i z0=_mm256_unpacklo_epi8(lo,hi), z1=_mm256_unpackhi_epi8(lo,hi);
- __m512i wv=_mm512_sub_epi8(_mm512_inserti64x4(_mm512_castsi256_si512(z0),z1,1),b8v);
- __m512i xv=_mm512_permutexvar_epi64(xidx,_mm512_loadu_si512((const void*)(x+i)));
- __mmask64 neg=_mm512_movepi8_mask(wv);
- __m512i xs=_mm512_mask_sub_epi8(xv,neg,_mm512_setzero_si512(),xv);
- acc=_mm512_dpbusd_epi32(acc,_mm512_abs_epi8(wv),xs);
- }
- sum=_mm512_reduce_add_epi32(acc);
-#elif defined(__AVXVNNI__) && defined(__AVX2__)
- /* AVX-VNNI 128-bit, int4: 16 byte = 32 nibble -> int8 [-8,7] in due half
- * (n0/n1), ciascuno alimentato a un vpdpbusd da 16 byte. Stesso unpack
- * 128-bit del ramo AVX2 sotto; 32 elementi/iter come li. */
- const __m128i m4=_mm_set1_epi8(0x0F); const __m128i b8=_mm_set1_epi8(8);
- __m128i acc=_mm_setzero_si128();
- for(;i+32<=I;i+=32){
- __m128i by=_mm_loadu_si128((const __m128i*)(w4+(i>>1))); /* 16 byte = 32 nibble */
- __m128i lo=_mm_and_si128(by,m4), hi=_mm_and_si128(_mm_srli_epi16(by,4),m4);
- __m128i n0=_mm_unpacklo_epi8(lo,hi), n1=_mm_unpackhi_epi8(lo,hi); /* nibble in ordine */
- __m128i w0=_mm_sub_epi8(n0,b8), w1=_mm_sub_epi8(n1,b8);
- __m128i x0=_mm_loadu_si128((const __m128i*)(x+i));
- __m128i x1=_mm_loadu_si128((const __m128i*)(x+i+16));
- acc=_mm_dpbusd_epi32(acc,_mm_abs_epi8(w0),_mm_sign_epi8(x0,w0));
- acc=_mm_dpbusd_epi32(acc,_mm_abs_epi8(w1),_mm_sign_epi8(x1,w1));
- }
- sum=hsum128_i32(acc);
-#elif defined(__AVX2__)
- const __m128i m4=_mm_set1_epi8(0x0F); const __m256i b8=_mm256_set1_epi8(8);
- const __m256i ones=_mm256_set1_epi16(1);
- __m256i acc=_mm256_setzero_si256();
- for(;i+32<=I;i+=32){
- __m128i by=_mm_loadu_si128((const __m128i*)(w4+(i>>1))); /* 16 byte = 32 nibble */
- __m128i lo=_mm_and_si128(by,m4), hi=_mm_and_si128(_mm_srli_epi16(by,4),m4);
- __m128i n0=_mm_unpacklo_epi8(lo,hi), n1=_mm_unpackhi_epi8(lo,hi); /* in ordine */
- __m256i wv=_mm256_sub_epi8(_mm256_set_m128i(n1,n0),b8);
- __m256i xv=_mm256_loadu_si256((const __m256i*)(x+i));
- __m256i p=_mm256_maddubs_epi16(_mm256_sign_epi8(wv,wv),_mm256_sign_epi8(xv,wv));
- acc=_mm256_add_epi32(acc,_mm256_madd_epi16(p,ones));
- }
- sum=hsum256_i32(acc);
-#elif defined(__ARM_NEON)
- const uint8x16_t m4q=vdupq_n_u8(0x0F); const int8x16_t b8q=vdupq_n_s8(8);
-#if defined(__ARM_FEATURE_DOTPROD)
- /* 4 accumulatori indipendenti (vedi dot_i8i8): spezza la catena seriale su acc.
- * Misurato su M4: 12.4 -> 29.9 GB/s di pesi per core (2.4x). */
- int32x4_t a0=vdupq_n_s32(0),a1=vdupq_n_s32(0),a2=vdupq_n_s32(0),a3=vdupq_n_s32(0);
- for(;i+64<=I;i+=64){
- uint8x16_t byA=vld1q_u8(w4+(i>>1)), byB=vld1q_u8(w4+(i>>1)+16);
- uint8x16x2_t zA=vzipq_u8(vandq_u8(byA,m4q), vshrq_n_u8(byA,4)); /* nibble in ordine */
- uint8x16x2_t zB=vzipq_u8(vandq_u8(byB,m4q), vshrq_n_u8(byB,4));
- a0=vdotq_s32(a0,vsubq_s8(vreinterpretq_s8_u8(zA.val[0]),b8q),vld1q_s8(x+i));
- a1=vdotq_s32(a1,vsubq_s8(vreinterpretq_s8_u8(zA.val[1]),b8q),vld1q_s8(x+i+16));
- a2=vdotq_s32(a2,vsubq_s8(vreinterpretq_s8_u8(zB.val[0]),b8q),vld1q_s8(x+i+32));
- a3=vdotq_s32(a3,vsubq_s8(vreinterpretq_s8_u8(zB.val[1]),b8q),vld1q_s8(x+i+48));
- }
- int32x4_t acc=vaddq_s32(vaddq_s32(a0,a1),vaddq_s32(a2,a3));
- for(;i+32<=I;i+=32){
- uint8x16_t by=vld1q_u8(w4+(i>>1)); /* 16 byte = 32 nibble */
- uint8x16x2_t z=vzipq_u8(vandq_u8(by,m4q), vshrq_n_u8(by,4)); /* nibble in ordine */
- acc=vdotq_s32(acc,vsubq_s8(vreinterpretq_s8_u8(z.val[0]),b8q),vld1q_s8(x+i));
- acc=vdotq_s32(acc,vsubq_s8(vreinterpretq_s8_u8(z.val[1]),b8q),vld1q_s8(x+i+16));
- }
- sum=vaddvq_s32(acc);
-#else
- int32x4_t acc=vdupq_n_s32(0);
- for(;i+32<=I;i+=32){
- uint8x16_t by=vld1q_u8(w4+(i>>1)); /* 16 byte = 32 nibble */
- uint8x16x2_t z=vzipq_u8(vandq_u8(by,m4q), vshrq_n_u8(by,4)); /* nibble in ordine */
- int8x16_t w0=vsubq_s8(vreinterpretq_s8_u8(z.val[0]),b8q);
- int8x16_t w1=vsubq_s8(vreinterpretq_s8_u8(z.val[1]),b8q);
- int8x16_t x0=vld1q_s8(x+i), x1=vld1q_s8(x+i+16);
- int16x8_t p=vmull_s8(vget_low_s8(w0),vget_low_s8(x0)); /* |w|<=8: nessun overflow */
- p=vmlal_s8(p,vget_high_s8(w0),vget_high_s8(x0));
- acc=vpadalq_s16(acc,p);
- p=vmull_s8(vget_low_s8(w1),vget_low_s8(x1));
- p=vmlal_s8(p,vget_high_s8(w1),vget_high_s8(x1));
- acc=vpadalq_s16(acc,p);
- }
- sum=vaddvq_s32(acc);
-#endif
-#elif defined(__VSX__)
- /* 16 byte = 32 nibble. vec_mergeh/vec_mergel su ppc64le (GCC) interallacciano come
- * unpacklo/unpackhi x86 (verificato empiricamente su POWER8): i nibble escono in
- * ordine di memoria. |w|<=8 dopo il -8, quindi stesso trucco del segno di dot_i8i8.
- * EN: vec_mergeh/l on ppc64le interleave like x86 unpacklo/hi (verified on POWER8),
- * so nibbles come out in memory order; then the same sign trick as dot_i8i8. */
- const __vector unsigned char m4v=vec_splats((unsigned char)0x0F);
- const __vector unsigned char sh4=vec_splats((unsigned char)4);
- const __vector signed char b8v=vec_splats((signed char)8);
- const __vector signed char vz=vec_splats((signed char)0);
- __vector signed int acc=vec_splats(0);
- for(;i+32<=I;i+=32){
- __vector unsigned char by=vec_xl(0,w4+(i>>1)); /* 16 byte = 32 nibble */
- __vector unsigned char lo=vec_and(by,m4v), hi=vec_sr(by,sh4);
- __vector signed char w0=vec_sub((__vector signed char)vec_mergeh(lo,hi),b8v);
- __vector signed char w1=vec_sub((__vector signed char)vec_mergel(lo,hi),b8v);
- __vector signed char x0=vec_xl(0,(const signed char*)(x+i));
- __vector signed char x1=vec_xl(0,(const signed char*)(x+i+16));
- __vector __bool char n0=vec_cmplt(w0,vz), n1=vec_cmplt(w1,vz);
- acc=vec_msum(vec_sel(x0,vec_sub(vz,x0),n0),
- (__vector unsigned char)vec_sel(w0,vec_sub(vz,w0),n0),acc);
- acc=vec_msum(vec_sel(x1,vec_sub(vz,x1),n1),
- (__vector unsigned char)vec_sel(w1,vec_sub(vz,w1),n1),acc);
- }
- sum=vec_extract(acc,0)+vec_extract(acc,1)+vec_extract(acc,2)+vec_extract(acc,3);
-#endif
- for(;i+1>1]; sum+=((int)(b&0xF)-8)*x[i]+((int)(b>>4)-8)*x[i+1]; }
- if(i>1]; sum+=((int)(b&0xF)-8)*x[i]; }
- return sum;
-}
-#if defined(__ARM_NEON) && defined(__ARM_FEATURE_MATMUL_INT8)
-/* SMMLA (i8mm): vmmlaq_s32 vede ogni int8x16_t come matrice 2x8 row-major (byte 0-7 =
- * riga 0, byte 8-15 = riga 1) e accumula C += A*B^T nel 2x2 int32: lane0=a0.b0,
- * lane1=a0.b1, lane2=a1.b0, lane3=a1.b1. vcombine di due mezze-righe costruisce la
- * matrice: A = due righe di peso (o,o+1), B = due righe di attivazione (s,s+1), quindi
- * meta' traffico pesi e doppio lavoro per istruzione a S>=2. EN: vmmlaq_s32 treats each
- * int8x16_t as a 2x8 row-major matrix and does C += A*B^T on a 2x2 int32 tile; vcombine
- * of vget_low/high halves builds the 2-row register from two weight/activation rows. */
-static inline int32x4_t mm_tile16(int32x4_t acc, int8x16_t wo, int8x16_t wo1,
- int8x16_t xs, int8x16_t xs1){
- acc=vmmlaq_s32(acc, vcombine_s8(vget_low_s8(wo), vget_low_s8(wo1)),
- vcombine_s8(vget_low_s8(xs), vget_low_s8(xs1)));
- return vmmlaq_s32(acc, vcombine_s8(vget_high_s8(wo), vget_high_s8(wo1)),
- vcombine_s8(vget_high_s8(xs), vget_high_s8(xs1)));
-}
-static void matmul_q_idot_mm(float *y, const int8_t *xq, const float *sx, const int8_t *q,
- const float *scale, int S, int I, int O){
- #pragma omp parallel for schedule(static)
- for(int o=0;o<(O&~1);o+=2){
- const int8_t *wo=q+(int64_t)o*I, *wo1=q+(int64_t)(o+1)*I;
- float sc0=scale[o], sc1=scale[o+1];
- for(int s=0;s<(S&~1);s+=2){
- const int8_t *xs=xq+(int64_t)s*I, *xs1=xq+(int64_t)(s+1)*I;
- /* 4 accumulatori indipendenti: una sola catena vmmla e' latency-bound.
- * EN: 4 independent accumulators; a single vmmla chain is latency-bound. */
- int32x4_t a0=vdupq_n_s32(0),a1=vdupq_n_s32(0),a2=vdupq_n_s32(0),a3=vdupq_n_s32(0); int i=0;
- for(;i+64<=I;i+=64){
- a0=mm_tile16(a0,vld1q_s8(wo+i), vld1q_s8(wo1+i), vld1q_s8(xs+i), vld1q_s8(xs1+i));
- a1=mm_tile16(a1,vld1q_s8(wo+i+16),vld1q_s8(wo1+i+16),vld1q_s8(xs+i+16),vld1q_s8(xs1+i+16));
- a2=mm_tile16(a2,vld1q_s8(wo+i+32),vld1q_s8(wo1+i+32),vld1q_s8(xs+i+32),vld1q_s8(xs1+i+32));
- a3=mm_tile16(a3,vld1q_s8(wo+i+48),vld1q_s8(wo1+i+48),vld1q_s8(xs+i+48),vld1q_s8(xs1+i+48));
- }
- for(;i+16<=I;i+=16)
- a0=mm_tile16(a0,vld1q_s8(wo+i),vld1q_s8(wo1+i),vld1q_s8(xs+i),vld1q_s8(xs1+i));
- int32x4_t acc=vaddq_s32(vaddq_s32(a0,a1),vaddq_s32(a2,a3));
- int32_t d00=vgetq_lane_s32(acc,0), d01=vgetq_lane_s32(acc,1);
- int32_t d10=vgetq_lane_s32(acc,2), d11=vgetq_lane_s32(acc,3);
- for(;i>1)), byo1=vld1q_u8(wo1+(i>>1));
- uint8x16_t cyo=vld1q_u8(wo+(i>>1)+16), cyo1=vld1q_u8(wo1+(i>>1)+16);
- uint8x16x2_t zo =vzipq_u8(vandq_u8(byo, m4q), vshrq_n_u8(byo, 4));
- uint8x16x2_t zo1=vzipq_u8(vandq_u8(byo1,m4q), vshrq_n_u8(byo1,4));
- uint8x16x2_t ko =vzipq_u8(vandq_u8(cyo, m4q), vshrq_n_u8(cyo, 4));
- uint8x16x2_t ko1=vzipq_u8(vandq_u8(cyo1,m4q), vshrq_n_u8(cyo1,4));
- a0=mm_tile16(a0, vsubq_s8(vreinterpretq_s8_u8(zo.val[0]),b8q),
- vsubq_s8(vreinterpretq_s8_u8(zo1.val[0]),b8q),
- vld1q_s8(xs+i), vld1q_s8(xs1+i));
- a1=mm_tile16(a1, vsubq_s8(vreinterpretq_s8_u8(zo.val[1]),b8q),
- vsubq_s8(vreinterpretq_s8_u8(zo1.val[1]),b8q),
- vld1q_s8(xs+i+16), vld1q_s8(xs1+i+16));
- a2=mm_tile16(a2, vsubq_s8(vreinterpretq_s8_u8(ko.val[0]),b8q),
- vsubq_s8(vreinterpretq_s8_u8(ko1.val[0]),b8q),
- vld1q_s8(xs+i+32), vld1q_s8(xs1+i+32));
- a3=mm_tile16(a3, vsubq_s8(vreinterpretq_s8_u8(ko.val[1]),b8q),
- vsubq_s8(vreinterpretq_s8_u8(ko1.val[1]),b8q),
- vld1q_s8(xs+i+48), vld1q_s8(xs1+i+48));
- }
- for(;i+32<=I;i+=32){
- uint8x16_t byo=vld1q_u8(wo+(i>>1)), byo1=vld1q_u8(wo1+(i>>1));
- uint8x16x2_t zo =vzipq_u8(vandq_u8(byo, m4q), vshrq_n_u8(byo, 4));
- uint8x16x2_t zo1=vzipq_u8(vandq_u8(byo1,m4q), vshrq_n_u8(byo1,4));
- a0=mm_tile16(a0, vsubq_s8(vreinterpretq_s8_u8(zo.val[0]),b8q),
- vsubq_s8(vreinterpretq_s8_u8(zo1.val[0]),b8q),
- vld1q_s8(xs+i), vld1q_s8(xs1+i));
- a1=mm_tile16(a1, vsubq_s8(vreinterpretq_s8_u8(zo.val[1]),b8q),
- vsubq_s8(vreinterpretq_s8_u8(zo1.val[1]),b8q),
- vld1q_s8(xs+i+16), vld1q_s8(xs1+i+16));
- }
- int32x4_t acc=vaddq_s32(vaddq_s32(a0,a1),vaddq_s32(a2,a3));
- int32_t d00=vgetq_lane_s32(acc,0), d01=vgetq_lane_s32(acc,1);
- int32_t d10=vgetq_lane_s32(acc,2), d11=vgetq_lane_s32(acc,3);
- for(;i+1>1], bo1=wo1[i>>1];
- int a0=(int)(bo&0xF)-8, a1=(int)(bo>>4)-8, b0=(int)(bo1&0xF)-8, b1=(int)(bo1>>4)-8;
- int u0=xs[i],u1=xs[i+1],v0=xs1[i],v1=xs1[i+1];
- d00+=a0*u0+a1*u1; d01+=a0*v0+a1*v1; d10+=b0*u0+b1*u1; d11+=b0*v0+b1*v1; }
- if(i>1], bo1=wo1[i>>1];
- int a0=(int)(bo&0xF)-8, b0=(int)(bo1&0xF)-8;
- d00+=a0*xs[i]; d01+=a0*xs1[i]; d10+=b0*xs[i]; d11+=b0*xs1[i]; }
- y[(int64_t)s*O+o] =(float)d00*sc0*sx[s];
- y[(int64_t)s*O+(o+1)] =(float)d10*sc1*sx[s];
- y[(int64_t)(s+1)*O+o] =(float)d01*sc0*sx[s+1];
- y[(int64_t)(s+1)*O+(o+1)]=(float)d11*sc1*sx[s+1];
- }
- if(S&1){ int s=S-1; const int8_t *xs=xq+(int64_t)s*I;
- y[(int64_t)s*O+o] =(float)dot_i4i8(wo, xs,I)*sc0*sx[s];
- y[(int64_t)s*O+(o+1)]=(float)dot_i4i8(wo1,xs,I)*sc1*sx[s]; }
- }
- if(O&1){ int o=O-1; const uint8_t *w=q4+(int64_t)o*rb; float sc=scale[o];
- #pragma omp parallel for schedule(static)
- for(int s=0;s=2){ matmul_q_idot_mm(y,xq,sx,q,scale,S,I,O); return; }
-#endif
- #pragma omp parallel for schedule(static)
- for(int o=0;o=2){ matmul_i4_idot_mm(y,xq,sx,q4,scale,S,I,O); return; }
-#endif
- #pragma omp parallel for schedule(static)
- for(int o=0;og_qscratch.xq_cap){
- int8_t *p=realloc(g_qscratch.xq,xn);
- if(!p){ fprintf(stderr,"OOM quant scratch\n"); exit(1); }
- g_qscratch.xq=p; g_qscratch.xq_cap=xn;
- }
- if(sn>g_qscratch.sx_cap){
- float *p=realloc(g_qscratch.sx,sn*sizeof(float));
- if(!p){ fprintf(stderr,"OOM quant scales\n"); exit(1); }
- g_qscratch.sx=p; g_qscratch.sx_cap=sn;
- }
- *xq=g_qscratch.xq; *sx=g_qscratch.sx;
-}
-
-/* allow_idot=0: forza il kernel int4/int8 ESATTO (attivazioni f32). Serve alle proiezioni di
- * attenzione: sono sensibili alla quantizzazione int8 delle attivazioni dell'IDOT. Misurato su
- * GLM-5.2 int4, 1023 token, log-lik -5040.33 (esatto) -> -5160.47 (IDOT) = +0.117 nat/token,
- * ~+12% perplexity. Gli altri matmul del prefill (o_proj, kv_b, expert) tengono l'IDOT.
- * EN: allow_idot=0 forces the EXACT int4/int8 kernel (f32 activations). The attention
- * projections need it: IDOT's int8 activation quantization costs +0.117 nats/token there
- * (~+12% perplexity), measured. Every other prefill matmul keeps IDOT as before. */
-static void matmul_qt_ex(float *y, const float *x, QT *w, int S, int allow_idot);
-static void matmul_qt(float *y, const float *x, QT *w, int S){ matmul_qt_ex(y,x,w,S,1); }
static void matmul_qt_ex(float *y, const float *x, QT *w, int S, int allow_idot){
#ifdef COLI_METAL
- /* Large row-batches (prefill: kv_b reconstruction, o_proj, dense MLP, step_all logits)
- * amortize Metal's ~5ms submit latency; small-S decode matmuls stay on CPU (NEON wins).
- * Weights must be registered (all dense QT allocs are, via qalloc). */
if(g_metal_enabled && S>=g_metal_gemm_min && !spec_pinned() && (w->fmt==1||w->fmt==2) && !omp_in_parallel()){
const void *wp = w->fmt==1 ? (const void*)w->q8 : (const void*)w->q4;
if(coli_metal_gemm(y,x,wp,w->s,w->fmt,S,w->I,w->O)) return;
}
#endif
#ifdef COLI_CUDA
- /* The CUDA backend owns persistent copies only for model-resident tensors.
- * Streaming expert slots are reused for different IDs and must never enter
- * this cache. Nested OpenMP calls stay on CPU because each device context
- * intentionally owns one synchronous scratch stream in this stage. */
if(g_cuda_enabled && w->cuda_eligible && !w->cuda_failed && !omp_in_parallel()){
const void *weights = w->fmt==0 ? (const void*)w->qf
: w->fmt==1 ? (const void*)w->q8 : (const void*)w->q4;
@@ -1049,21 +344,7 @@ static void matmul_qt_ex(float *y, const float *x, QT *w, int S, int allow_idot)
}
#endif
if(w->fmt==0){ matmul(y,x,w->qf,S,w->I,w->O); return; }
- /* fmt=4: grouped int4 — always use the exact grouped kernel (no IDOT approximation,
- * since the whole point of grouped scales is better quality). */
if(w->fmt==4){ matmul_i4_grouped(y,x,w->q4,w->s,S,w->I,w->O,w->gs); return; }
- /* int8 IDOT vince sempre (1.4-2.5x). int4 IDOT: l'autore su AVX2 trovo' che a S=1
- * non ripaga (soglia S>=2); ma su ARM/SDOT il singolo token CONVIENE (vedi g_i4s /
- * PR #9 per il gemello VNNI). Soglia configurabile con I4S.
- * EN: int8 IDOT always wins (1.4-2.5x). int4 IDOT: on AVX2 the author found S=1 didn't
- * pay (S>=2 gate); on ARM/SDOT single-token DOES pay (see g_i4s / PR #9 for the VNNI
- * twin). Threshold configurable via I4S. */
- /* #163: sotto SPEC_PIN il gate int4-IDOT usa la decisione di S=1 per OGNI S, cosi'
- * draft e verifica restano sulla stessa famiglia. (CUDA non e' toccato: la sua
- * condizione non dipende da S, quindi e' gia' coerente tra draft e verifica.)
- * EN: under SPEC_PIN the int4 IDOT gate uses the S=1 decision for EVERY S, so draft
- * and verify stay in one family. (CUDA untouched: its condition is S-independent,
- * hence already draft/verify-consistent.) */
if(allow_idot && g_idot && (w->fmt==1 || (w->fmt==2 && (spec_pinned() ? g_i4s<=1 : S>=g_i4s)))){
int I=w->I; int8_t *xq; float *sx;
if(S<0 || I<0 || (size_t)S>SIZE_MAX/(size_t)(I?I:1)){ fprintf(stderr,"matmul_qt: shape overflow\n"); exit(1); }
@@ -1078,49 +359,6 @@ static void matmul_qt_ex(float *y, const float *x, QT *w, int S, int allow_idot)
else matmul_i4(y,x,w->q4,w->s,S,w->I,w->O);
}
-/* quantizza w[O,I] f32 -> int8 q[O,I] + scala[O] simmetrica per riga */
-static void quantize_rows(const float *w, int8_t *q, float *scale, int O, int I, int bits){
- int qmax=(1<<(bits-1))-1;
- #pragma omp parallel for schedule(static)
- for(int o=0;oamax)amax=a; }
- float s=amax/qmax; if(s<1e-8f)s=1e-8f; scale[o]=s;
- int8_t *qr=q+(int64_t)o*I;
- for(int i=0;iqmax)v=qmax; if(v<-qmax-1)v=-qmax-1; qr[i]=(int8_t)v; }
- }
-}
-/* quantizza w[O,I] f32 -> int4 impacchettato (2/byte) + scala[O].
- * bits<=4: valori in [-qmax-1,qmax] stanno in un nibble [-8,7]; memorizzati come v+8 (0..15). */
-static void pack_int4(const float *w, uint8_t *q4, float *scale, int O, int I, int bits){
- int qmax=(1<<(bits-1))-1, rb=(I+1)/2;
- #pragma omp parallel for schedule(static)
- for(int o=0;oamax)amax=a; }
- float s=amax/qmax; if(s<1e-8f)s=1e-8f; scale[o]=s;
- uint8_t *qr=q4+(int64_t)o*rb;
- for(int i=0;iqmax)v0=qmax; if(v0<-8)v0=-8;
- int v1=0; if(i+1qmax)v1=qmax; if(v1<-8)v1=-8; }
- qr[i>>1] = (uint8_t)((v0+8) | ((v1+8)<<4));
- }
- }
-}
-
-/* quantizza w[O,I] f32 -> int2 impacchettato (4/byte) + scala[O]. valori nibble 2-bit in [-2,1]. */
-static void pack_int2(const float *w, uint8_t *q2, float *scale, int O, int I, int bits){
- int qmax=(1<<(bits-1))-1, rb=(I+3)/4;
- #pragma omp parallel for schedule(static)
- for(int o=0;oamax)amax=a; }
- float s=amax/qmax; if(s<1e-8f)s=1e-8f; scale[o]=s;
- uint8_t *qr=q2+(int64_t)o*rb;
- for(int i=0;iqmax)v=qmax; if(v<-2)v=-2; byte|=(uint8_t)((v+2)<<(k*2)); }
- qr[i>>2]=byte;
- }
- }
-}
-
static int g_nopack=0; /* NOPACK=1 -> tiene i valori <=4bit in contenitore int8 (per validare il packing) */
static int g_drop=0; /* DROP=1 -> scarta le pagine expart dopo l'uso. Default 0: le lascia in
* page-cache (buff/cache, NON RSS) come L2 gratuito -> sfrutta lo
@@ -1206,6 +444,11 @@ static int g_disk_split=0; /* DISK_SPLIT=1: contatori che spezzano i DISK LOAD (
* (int4), con i byte letti. Default OFF: a flag spento gli atomic
* non vengono MAI toccati (zero overhead), le righe extra di stats
* non vengono stampate. Solo misura: nessun effetto sull'output. */
+
+#include "sample.h"
+#include "kv_persist.h"
+#include "telemetry.h"
+
/* Aligned allocator for dense QT weights/scales: under METAL, page-align + register so the
* GPU reads them zero-copy (no upload duplicate). Plain malloc otherwise. */
/* ---- COLI_NUMA=1 (#82): interleave the expert slabs across NUMA nodes ----
@@ -4239,13 +3482,14 @@ static void mtp_absorb(Model *m, const int *next_ids, const float *x, int S, int
}
static inline int argmax_v(const float *lo, int V){
- /* skip NaN (x==x is false for NaN) so a poisoned logit can't pin the argmax
- * to index 0 — pick the max finite/+Inf entry instead. */
int b=-1; float bv=-INFINITY;
for(int i=0;ibv){ bv=x; b=i; } }
return b<0?0:b;
}
+static void repin_pass_limit(Model *m,int limit);
+static void repin_pass(Model *m){ repin_pass_limit(m,16); }
+
/* ---- METODO F: draft grammaticale (#48) ----
* gr_feed consuma i byte di ogni token EMESSO e tiene il walker in sync con l'output;
* grammar_draft propone lo span FORZATO successivo (un solo byte legale per posizione)
@@ -4326,113 +3570,6 @@ static int grammar_draft(int *draft, int cap){
* Rejection sampling di Leviathan: accetta il draft x_d con prob p(x_d); al rifiuto
* ricampiona da p con x_d azzerato e rinormalizzato. La distribuzione risultante e'
* ESATTAMENTE p: la speculazione resta invisibile all'output anche col sampling. */
-static uint64_t g_rng=0x9E3779B97F4A7C15ULL;
-static inline double rndu(void){ g_rng^=g_rng<<13; g_rng^=g_rng>>7; g_rng^=g_rng<<17;
- return (double)(g_rng>>11)*(1.0/9007199254740992.0); }
-static float *g_pbuf=NULL; static int *g_pidx=NULL; /* buffer riusati (decode single-thread) */
-/* sift-down su max-heap in h[0..n), chiave = g_pbuf[h[i]] (#335: partial top-p select).
- * Versione "a buco": porta il valore di radice e lo deposita solo alla fine, cosi'
- * heapify e' O(V) e ogni pop e' O(log n) senza qsort sull'intero vocabolario. */
-static void topp_siftdown(int *h, int n, int i){
- int iv=h[i]; float kv=g_pbuf[iv];
- for(;;){ int l=2*i+1;
- if(l>=n) break; /* foglia */
- int b=l; if(l+1g_pbuf[h[l]]) b=l+1; /* figlio maggiore */
- if(g_pbuf[h[b]]<=kv) break; /* nessun figlio supera la radice -> ferma */
- h[i]=h[b]; i=b; }
- h[i]=iv;
-}
-/* costruisce in g_pbuf la distribuzione target: softmax(lo/temp) troncata a top-p g_nuc.
- * Invariante per dist_sample: g_pbuf resta INDICIZZATO per token-id (mai riordinato);
- * la coda troncata va AZZERATA in g_pbuf (dist_sample la legge direttamente per id). */
-static void dist_build(const float *lo, int V){
- if(!g_pbuf){ g_pbuf=falloc(V); g_pidx=malloc(V*sizeof(int)); }
- /* Un solo logit NaN/+Inf avvelenava tutto (#369): +Inf diventava mx, NaN/Inf-mx
- * -> expf NaN -> s NaN -> ogni prob NaN -> dist_sample cade sul fallback
- * `g_pbuf[i]>0` (NaN>0 e' falso ovunque) e ritorna 0 PER SEMPRE, in silenzio.
- * Difesa: mx solo sui finiti; un logit non finito contribuisce prob 0;
- * se la distribuzione degenera (tutti non finiti / somma non valida) si
- * ripiega sull'argmax dei finiti e si avvisa UNA volta, mai in silenzio. */
- int mxi=-1; float mx=0;
- for(int i=0;imx)){ mx=lo[i]; mxi=i; }
- double s=0; float invt=1.f/(g_temp>1e-4f?g_temp:1e-4f);
- if(mxi>=0){
- for(int i=0;i=0)?mxi:0; /* mxi = argmax dei logit FINITI (robusto
- * anche se lo[0] e' NaN, dove argmax_v fallirebbe) */
- for(int i=0;i0 && g_nuc<1.f){
- for(int i=0;i=0;i--) topp_siftdown(g_pidx,V,i); /* heapify O(V) */
- /* pop verso la coda: i vincitori (testa top-p) cadono in g_pidx[out..V-1] in ordine
- * DECRESCENTE, come il vecchio qsort, quindi s2 accumula nello stesso ordine ->
- * head bit-identical sui casi senza pareggi (i pareggi erano gia' non specificati
- * sotto il qsort instabile e restano tali). Il prefisso g_pidx[0..out-1) e' la coda. */
- double s2=0, cum=0; int out=V;
- do{ int root=g_pidx[0]; /* massimo corrente */
- g_pidx[0]=g_pidx[--out]; g_pidx[out]=root; /* sposta il max in coda */
- s2+=g_pbuf[root]; cum+=g_pbuf[root];
- if(out>0) topp_siftdown(g_pidx,out,0);
- } while(cum0);
- for(int i=0;i=0 -> quel token e' escluso (rinormalizzando al volo) */
-static int dist_sample(int V, int ban){
- double z = 1.0 - (ban>=0 ? g_pbuf[ban] : 0.0); if(z<=1e-12) z=1e-12;
- double u = rndu()*z, cum=0;
- for(int i=0;i=u) return i; }
- for(int i=V-1;i>=0;i--) if(i!=ban && g_pbuf[i]>0) return i;
- return 0;
-}
-/* prossimo token dai logits: greedy se g_temp<=0, altrimenti sampling.
- * ban = token escluso perche' rifiutato dalla verifica speculativa precedente. */
-static int pick_tok(const float *lo, int V, int ban){
- if(g_temp<=0) return argmax_v(lo,V);
- dist_build(lo,V);
- return dist_sample(V,ban);
-}
-
-/* stop-set attivo (popolato da run_text/run_serve dal config; vuoto in validazione,
- * dove si genera un numero fisso di token da confrontare con l'oracolo) */
-static int g_stop[64], g_nstop=0; /* config eos + ogni added-token "special" del tokenizer */
-static void repin_pass_limit(Model *m,int limit);
-static void repin_pass(Model *m){ repin_pass_limit(m,16); }
-static inline int is_stop(int t){ for(int i=0;i solo gli stop del config (validazione/oracolo, dove il tokenizer non serve). */
-static void stops_arm_tok(const Cfg *c, int tok_eos, Tok *T){
- g_nstop=0;
- for(int i=0;in_stop && g_nstop<64;i++) g_stop[g_nstop++]=c->stop_ids[i];
- if(tok_eos>=0 && !is_stop(tok_eos) && g_nstop<64) g_stop[g_nstop++]=tok_eos;
- int nsp=0;
- /* DIFESA IN PROFONDITA' (woolcoxm, #298): il tokenizer marca "special":true i token di
- * CONTROLLO -- <|user|>, <|assistant|>, <|observation|>, , [gMASK], i marker
- * image/video/audio. Nessuno di questi e' contenuto legittimo di una risposta: se il
- * modello ne emette uno, il turno e' finito (infatti GLM ne elenca tre fra gli eos
- * ufficiali). Senza questo, uno di quei token non elencato nel config veniva
- * DETOKENIZZATO E STAMPATO IN CHAT come testo, e la generazione proseguiva oltre la
- * fine reale -- l'"added stuff on the end" riportato su un checkpoint convertito.
- * Fidarsi del config di pesi convertiti da terzi e' precisamente cio' che non
- * possiamo controllare; il flag del tokenizer lo possiamo leggere.
- * NB: // hanno "special":false e restano contenuto vero. */
- if(T) for(int id=0; idn_ids && g_nstop<64; id++)
- if(T->id_special[id] && !is_stop(id)){ g_stop[g_nstop++]=id; nsp++; }
- fprintf(stderr,"[stop] %d stop tokens:",g_nstop);
- for(int i=0;imx){mx=lo[i];best=i;} }
- double se=0; for(int i=0;i .. " (T=ctxlen+contlen)
* output: riga " " per richiesta.
@@ -5101,117 +4226,6 @@ static void repin_pass_limit(Model *m,int limit){
* si appendono SOLO le posizioni nuove e si riscrive nrec per ultimo: un crash a meta'
* append lascia nrec vecchio = file coerente. La riga KV del layer MTP non si salva:
* al resume kv_start=-1 e la finestra di draft riparte da sola. */
-static int g_kvsave=1;
-#define KV_MAGIC "COLIKV1\0"
-static void kv_hdr(Model *m, int32_t *h, int nrec){
- Cfg *c=&m->c; int nic=0;
- for(int i=0;in_layers;i++) if(m->Ic && m->Ic[i]) nic++;
- h[0]=c->n_layers; h[1]=c->kv_lora; h[2]=c->qk_rope;
- h[3]=m->has_dsa?c->index_hd:0; h[4]=nic; h[5]=c->vocab; h[6]=nrec; h[7]=0;
-}
-/* Bytes of one on-disk record: [tok i32][Lc+Rc per layer][Ic per DSA layer].
- * Layout matches what kv_disk_append writes and kv_disk_load reads. */
-static int64_t kv_rec_bytes(Model *m){
- Cfg *c=&m->c;
- int64_t rec = 4 + (int64_t)c->n_layers*(c->kv_lora+c->qk_rope)*4;
- if(m->has_dsa) for(int i=0;in_layers;i++) if(m->Ic[i]) rec+=(int64_t)c->index_hd*4;
- return rec;
-}
-/* Open the persistent handle lazily; write the header if the file is new. After
- * this returns successfully, k->disk_fp is valid for the engine's lifetime and
- * positioned at end-of-header (nrec==0 case) or wherever the caller seeks. */
-static int kv_disk_open(Model *m){
- KVState *k=m->kv;
- if(k->disk_fp) return 1;
- k->disk_fp=fopen(k->disk_path,"r+b");
- if(!k->disk_fp){ /* not there yet -> create + header */
- k->disk_fp=fopen(k->disk_path,"wb");
- if(!k->disk_fp) return 0;
- int32_t h[8]; kv_hdr(m,h,0);
- fwrite(KV_MAGIC,1,8,k->disk_fp); fwrite(h,4,8,k->disk_fp);
- fflush(k->disk_fp);
- fclose(k->disk_fp);
- k->disk_fp=fopen(k->disk_path,"r+b"); /* reopen r+b for append */
- if(!k->disk_fp) return 0;
- }
- return 1;
-}
-static void kv_disk_truncate(Model *m, int nrec){
- if(!g_kvsave) return;
- KVState *k=m->kv;
- if(k->disk_fp){ fclose(k->disk_fp); k->disk_fp=NULL; } /* drop to shrink on disc */
- FILE *f=fopen(k->disk_path,"r+b");
- if(!f){ k->disk_nrec=0; return; }
- k->disk_nrec=nrec;
- int32_t nr=nrec; fseek(f,8+6*4,SEEK_SET); fwrite(&nr,4,1,f);
- fflush(f); fclose(f);
-}
-static void kv_disk_reset(Model *m){ kv_disk_truncate(m,0); }
-static void kv_disk_append(Model *m, const int *hist, int len){
- KVState *k=m->kv;
- if(!g_kvsave || len<=k->disk_nrec) return;
- Cfg *c=&m->c;
- if(!kv_disk_open(m)) return;
- FILE *f=k->disk_fp;
- int64_t rec = kv_rec_bytes(m);
- /* grow the contiguous staging buffer if the record is larger (#1 batching) */
- if(rec > k->disk_buf_cap){
- uint8_t *nb=realloc(k->disk_buf, rec);
- if(!nb) return; /* OOM: skip this turn, retry next */
- k->disk_buf=nb; k->disk_buf_cap=rec;
- }
- fseek(f, 8+8*4 + (int64_t)k->disk_nrec*rec, SEEK_SET);
- for(int p=k->disk_nrec;pdisk_buf; /* pack token + every layer into one record */
- *(int32_t*)b = hist[p]; b+=4;
- for(int i=0;in_layers;i++){
- memcpy(b, m->Lc[i]+(int64_t)p*c->kv_lora, (size_t)c->kv_lora*4); b+=c->kv_lora*4;
- memcpy(b, m->Rc[i]+(int64_t)p*c->qk_rope,(size_t)c->qk_rope*4); b+=c->qk_rope*4;
- }
- if(m->has_dsa) for(int i=0;in_layers;i++) if(m->Ic[i]){
- memcpy(b, m->Ic[i]+(int64_t)p*c->index_hd, (size_t)c->index_hd*4); b+=c->index_hd*4;
- }
- fwrite(k->disk_buf, 1, (size_t)rec, f); /* one fwrite per position (was ~157) */
- }
- fflush(f); /* dati prima, contatore poi */
- int32_t nr=len; fseek(f,8+6*4,SEEK_SET); fwrite(&nr,4,1,f);
- fflush(f); /* persist the counter too */
- k->disk_nrec=len;
-}
-static int kv_disk_load(Model *m, int *hist, int maxctx){
- if(!g_kvsave) return 0;
- KVState *k=m->kv;
- Cfg *c=&m->c;
- FILE *f=fopen(k->disk_path,"rb"); if(!f) return 0;
- char mg[8]; int32_t h[8], w[8]; kv_hdr(m,w,0);
- if(fread(mg,1,8,f)!=8 || memcmp(mg,KV_MAGIC,8) || fread(h,4,8,f)!=8 ||
- h[0]!=w[0]||h[1]!=w[1]||h[2]!=w[2]||h[3]!=w[3]||h[4]!=w[4]||h[5]!=w[5]){
- fprintf(stderr,"[KV] ignoring .coli_kv from a different model or version\n"); fclose(f); return 0; }
- int nrec=h[6];
- if(nrec<1){ fclose(f); return 0; }
- if(nrec>=maxctx-8-g_draft){
- fprintf(stderr,"[KV] saved conversation (%d tokens) exceeds the context: starting over\n",nrec);
- fclose(f); return 0; }
- double t0=now_s();
- for(int p=0;pn_layers;i++){
- if(fread(m->Lc[i]+(int64_t)p*c->kv_lora, 4, c->kv_lora, f)!=(size_t)c->kv_lora ||
- fread(m->Rc[i]+(int64_t)p*c->qk_rope, 4, c->qk_rope, f)!=(size_t)c->qk_rope){ nrec=p; goto out; }
- }
- if(m->has_dsa) for(int i=0;in_layers;i++) if(m->Ic[i])
- if(fread(m->Ic[i]+(int64_t)p*c->index_hd, 4, c->index_hd, f)!=(size_t)c->index_hd){ nrec=p; goto out; }
- }
-out:
- fclose(f);
- if(nrec>0){
- if(m->has_mtp) m->kv_start[c->n_layers]=-1; /* la finestra MTP riparte da sola */
- fprintf(stderr,"[KV] resumed conversation from disk: %d tokens in %.1fs (no re-prefill)\n",
- nrec, now_s()-t0);
- }
- k->disk_nrec=nrec;
- return nrec;
-}
typedef struct { KVState kv; int *hist, len, first; } ServeCtx;
static double kv_pool_bytes(Model *m, int max_ctx);
@@ -5622,212 +4636,7 @@ static int *read_arr(jval*o,const char*k,int*n){
if(!r){ fprintf(stderr,"OOM read_arr\n"); exit(1); }
for(int i=0;ilen;i++) r[i]=(int)a->kids[i]->num; *n=a->len; return r; }
-/* byte residenti di un tensore [O,I] al numero di bit dato (specchio di qt_bytes) */
-static int64_t tbytes(int O,int I,int bits){
- if(bits>=16) return (int64_t)O*I*4;
- if(bits>=5) return (int64_t)O*I + (int64_t)O*4;
- return (int64_t)O*((I+1)/2) + (int64_t)O*4;
-}
-/* byte VERI di un expert: dal container se pre-quantizzato, altrimenti stima da ebits */
-static int64_t expert_bytes_probe(Model *m, int ebits){
- Cfg *c=&m->c; int64_t eb=0; char nm[256];
- snprintf(nm,sizeof(nm),"model.layers.%d.mlp.experts.0.gate_proj.weight",c->first_dense);
- if(st_nbytes(&m->S,nm)>0){
- const char *suf[3]={"gate_proj","up_proj","down_proj"};
- for(int k=0;k<3;k++){
- snprintf(nm,sizeof(nm),"model.layers.%d.mlp.experts.0.%s.weight",c->first_dense,suf[k]);
- eb+=st_nbytes(&m->S,nm);
- snprintf(nm,sizeof(nm),"model.layers.%d.mlp.experts.0.%s.weight.qs",c->first_dense,suf[k]);
- int64_t q=st_nbytes(&m->S,nm); if(q>0) eb+=q;
- }
- }
- if(eb<=0) eb = tbytes(c->moe_inter,c->hidden,ebits)*2 + tbytes(c->hidden,c->moe_inter,ebits);
- return eb;
-}
-
-/* TIERS: fotografia della piramide expert per la dashboard web —
- * "TIERS " sul canale di protocollo.
- * ram = pinnati non-VRAM + LRU corrente; disk = tutto il resto. */
-/* BRAIN MAP (dashboard): per-turn expert hit bitmap + full residency/heat map.
- * g_ehit[layer][eid]=1 quando l'expert viene instradato in questo turno;
- * hits_emit lo serializza e lo azzera. emap_emit fotografa tier+heat di TUTTI. */
-static uint8_t **g_ehit;
-static void ehit_mark(Model *m, int layer, int eid){
- if(!g_ehit){ Cfg *c=&m->c;
- g_ehit=calloc(c->n_layers+1,sizeof(uint8_t*));
- for(int i=0;i<=c->n_layers;i++) g_ehit[i]=calloc(c->n_experts,1);
- }
- g_ehit[layer][eid]=1;
-}
-
-/* HWINFO: hardware snapshot for the web dashboard — emitted once at READY. */
-/* CPU model + cores + RAM (GB); empty/zero where unavailable.
- * Shared by the dashboard HWINFO line and the PROF=1 header. */
-static void hw_probe(char *cpu, size_t cn, int *cores, double *ram_total, double *ram_avail){
- cpu[0]=0;
-#ifdef _WIN32
- /* niente /proc su Windows: brand string via CPUID (0x80000002..4), zero
- * dipendenze extra. La dashboard mostrava "0 GB RAM / 0 cores" perche'
- * tutto questo blocco era solo-Linux mentre il ramo CUDA funzionava. */
-#if defined(__x86_64__) || defined(__i386__)
- { unsigned int r[12]={0}; unsigned int *w=r;
- for(unsigned int f=0x80000002u; f<=0x80000004u; f++,w+=4)
- __get_cpuid(f,&w[0],&w[1],&w[2],&w[3]);
- char *b=(char*)r; b[47]=0; while(*b==' ')b++;
- snprintf(cpu,cn,"%s",b); }
-#endif
-#else
- FILE *ci=fopen("/proc/cpuinfo","r");
- if(ci){ char ln[256];
- while(fgets(ln,sizeof(ln),ci)) if(!strncmp(ln,"model name",10)){
- char *p=strchr(ln,':'); if(p){ p++; while(*p==' ')p++;
- int n=(int)strlen(p); if(n>0&&p[n-1]=='\n')p[--n]=0;
- snprintf(cpu,cn,"%s",p); } break; }
- fclose(ci); }
-#endif
- *cores=0;
-#ifdef _WIN32
- { SYSTEM_INFO si; GetSystemInfo(&si); *cores=(int)si.dwNumberOfProcessors; }
-#elif defined(_SC_NPROCESSORS_ONLN)
- *cores=(int)sysconf(_SC_NPROCESSORS_ONLN);
-#endif
- *ram_total=*ram_avail=0;
-#ifdef _WIN32
- compat_meminfo(ram_total,ram_avail); /* GlobalMemoryStatusEx, gia' in compat.h */
-#else
- FILE *mi=fopen("/proc/meminfo","r");
- if(mi){ char ln[256]; double mt=0,ma=0;
- while(fgets(ln,sizeof(ln),mi)){
- if(sscanf(ln,"MemTotal: %lf",&mt)==1) *ram_total=mt/1e6;
- if(sscanf(ln,"MemAvailable: %lf",&ma)==1) *ram_avail=ma/1e6;
- } fclose(mi); }
-#endif
-}
-
-static void hwinfo_emit(Model *m){
- Cfg *c=&m->c; (void)c; /* silence -Wunused on builds without /proc (#148 report) */
- char cpu[256]; int cores; double ram_total,ram_avail;
- hw_probe(cpu,sizeof(cpu),&cores,&ram_total,&ram_avail);
- /* GPU */
- int ngpu=0; double vram_total=0;
- char gpu_name[128]="";
-#ifdef COLI_CUDA
- ngpu=g_cuda_ndev; vram_total=m->gpu_expert_bytes/1e9;
- for(int i=0;i0){
- /* We don't have a device-name API; parse from the init log line stored in stderr.
- * Simpler: just read the nvidia driver sysfs or use a fixed label. */
- snprintf(gpu_name,sizeof(gpu_name),"CUDA device x%d",g_cuda_ndev);
- }
-#endif
- printf("HWINFO %d %.1f %.1f %d %.1f %s|%s\n",
- cores,ram_total,ram_avail,ngpu,vram_total,cpu,gpu_name);
- fflush(stdout);
-}
-
-static void tiers_emit(Model *m){
- Cfg *c=&m->c; int nsp=0;
- for(int i=0;in_layers;i++) if(m->L[i].sparse) nsp++;
- int total=(nsp+(m->has_mtp?1:0))*c->n_experts;
- int pinned=0,lru=0;
- for(int i=0;i<=c->n_layers;i++){ pinned+=m->npin?m->npin[i]:0; lru+=m->ecn?m->ecn[i]:0; }
- int vram=0; double vram_gb=0;
-#ifdef COLI_CUDA
- vram=m->gpu_expert_count; vram_gb=m->gpu_expert_bytes/1e9;
-#endif
- int ram=pinned-vram+lru; if(ram<0) ram=0;
- int disk=total-vram-ram; if(disk<0) disk=0;
- double eb=(double)expert_bytes_probe(m,m->ebits);
- printf("TIERS %d %d %d %.2f %.2f\n",vram,ram,disk,vram_gb,ram*eb/1e9);
- fflush(stdout);
-}
-
-/* EMAP: 1 byte/expert (2bit tier: 0=disk 1=RAM 2=VRAM | 6bit heat log2-bucket),
- * righe = layer sparsi in ordine (+MTP se presente), colonne = n_experts. Hex. */
-static void emap_emit(Model *m){
- Cfg *c=&m->c;
- int rows=0;
- for(int i=0;in_layers;i++) if(m->L[i].sparse) rows++;
- int has_mtp = m->has_mtp && m->eusage[c->n_layers];
- if(has_mtp) rows++;
- int cols=c->n_experts;
- char *hex=malloc((size_t)rows*cols*2+1); int w=0;
- for(int i=0;i<=c->n_layers;i++){
- int is_row = (in_layers && m->L[i].sparse) || (i==c->n_layers && has_mtp);
- if(!is_row) continue;
- for(int e=0;epin[i];
- for(int z=0;znpin[i];z++) if(P[z].eid==e){
-#ifdef COLI_CUDA
- tier = P[z].g.cuda?2:1;
-#else
- tier = 1;
-#endif
- break; }
- if(!tier && m->ecache && m->ecache[i])
- for(int z=0;zecn[i];z++) if(m->ecache[i][z].eid==e){ tier=1; break; }
- uint32_t u = m->eusage[i]?m->eusage[i][e]:0;
- int heat=0; while(u){ heat++; u>>=1; } if(heat>63) heat=63;
- int b=(tier<<6)|heat;
- hex[w++]="0123456789abcdef"[b>>4]; hex[w++]="0123456789abcdef"[b&15];
- }
- }
- hex[w]=0;
- printf("EMAP %d %d %s\n",rows,cols,hex); fflush(stdout); free(hex);
-}
-
-/* HITS: bitmap 1bit/expert (stesso ordine di EMAP), poi azzera per il turno dopo. */
-static void hits_emit(Model *m){
- Cfg *c=&m->c; if(!g_ehit) return;
- int rows=0;
- for(int i=0;in_layers;i++) if(m->L[i].sparse) rows++;
- int has_mtp = m->has_mtp && m->eusage[c->n_layers];
- if(has_mtp) rows++;
- int cols=c->n_experts, nb=(rows*cols+7)/8;
- uint8_t *bm=calloc(nb,1); int bit=0;
- for(int i=0;i<=c->n_layers;i++){
- int is_row = (in_layers && m->L[i].sparse) || (i==c->n_layers && has_mtp);
- if(!is_row) continue;
- for(int e=0;e>3]|=1<<(bit&7); g_ehit[i][e]=0; }
- }
- char *hex=malloc((size_t)nb*2+1); int w=0;
- for(int b=0;b>4]; hex[w++]="0123456789abcdef"[bm[b]&15]; }
- hex[w]=0;
- printf("HITS %d %d %s\n",rows,cols,hex); fflush(stdout); free(hex); free(bm);
-}
-
-/* scarica su file l'istogramma d'uso degli expert: righe "layer eid count" (per PIN).
- * Include la riga MTP (layer n_layers). Scrittura atomica (tmp+rename): viene chiamata
- * anche a ogni turno di serve e il processo puo' morire in qualsiasi momento. */
-static void stats_dump_q(Model *m, const char *path, int quiet){
- char tmp[2100]; snprintf(tmp,sizeof(tmp),"%s.tmp",path);
- FILE *f=fopen(tmp,"w"); if(!f){ if(!quiet) perror(tmp); return; }
- Cfg *c=&m->c; int64_t tot=0, nz=0;
- for(int i=0;i<=c->n_layers;i++){ if(!m->eusage[i]) continue;
- for(int e=0;en_experts;e++) if(m->eusage[i][e]){ fprintf(f,"%d %d %u\n",i,e,m->eusage[i][e]); tot+=m->eusage[i][e]; nz++; } }
- fclose(f); rename(tmp,path);
- if(!quiet) fprintf(stderr,"[STATS] %lld selections across %lld distinct experts -> %s\n",(long long)tot,(long long)nz,path);
-}
-static void stats_dump(Model *m, const char *path){ stats_dump_q(m,path,0); }
-
-/* CACHE CHE IMPARA: istogramma d'uso PERSISTENTE in /.coli_usage.
- * Caricato all'avvio (i contatori ripartono dalla storia), salvato a ogni turno:
- * piu' usi colibri', meglio l'auto-pin conosce i TUOI expert caldi. */
-static char g_usage_path[2100]="";
-static int64_t usage_load(Model *m, const char *path){
- FILE *f=fopen(path,"r"); if(!f) return 0;
- Cfg *c=&m->c; int l,e; uint32_t cnt; int64_t tot=0;
- while(fscanf(f,"%d %d %u",&l,&e,&cnt)==3)
- if(l>=0&&l<=c->n_layers&&e>=0&&en_experts&&m->eusage[l]){ m->eusage[l][e]+=cnt; tot+=cnt; }
- fclose(f); return tot;
-}
-static void usage_save(Model *m){ if(g_usage_path[0]) stats_dump_q(m,g_usage_path,1); }
+/* telemetry, stats, usage persistence — moved to telemetry.h */
/* HOT-STORE ("il redis del colibri'"): carica in RAM, UNA VOLTA e per sempre, i top expert
* per frequenza d'uso misurata (file STATS di un run precedente), entro un budget in GB.
diff --git a/c/kv_persist.h b/c/kv_persist.h
new file mode 100644
index 0000000..51289a8
--- /dev/null
+++ b/c/kv_persist.h
@@ -0,0 +1,121 @@
+/* kv_persist.h — .coli_kv on-disk KV cache persistence.
+ * Conversations reopen warm across engine restarts: the compressed MLA KV-cache
+ * is appended incrementally after every turn, crash-safe (nrec written last).
+ * Include after Model/KVState/Cfg are defined; requires now_s() and g_draft. */
+#ifndef KV_PERSIST_H
+#define KV_PERSIST_H
+
+static int g_kvsave=1;
+#define KV_MAGIC "COLIKV1\0"
+
+static void kv_hdr(Model *m, int32_t *h, int nrec){
+ Cfg *c=&m->c; int nic=0;
+ for(int i=0;in_layers;i++) if(m->Ic && m->Ic[i]) nic++;
+ h[0]=c->n_layers; h[1]=c->kv_lora; h[2]=c->qk_rope;
+ h[3]=m->has_dsa?c->index_hd:0; h[4]=nic; h[5]=c->vocab; h[6]=nrec; h[7]=0;
+}
+
+static int64_t kv_rec_bytes(Model *m){
+ Cfg *c=&m->c;
+ int64_t rec = 4 + (int64_t)c->n_layers*(c->kv_lora+c->qk_rope)*4;
+ if(m->has_dsa) for(int i=0;in_layers;i++) if(m->Ic[i]) rec+=(int64_t)c->index_hd*4;
+ return rec;
+}
+
+static int kv_disk_open(Model *m){
+ KVState *k=m->kv;
+ if(k->disk_fp) return 1;
+ k->disk_fp=fopen(k->disk_path,"r+b");
+ if(!k->disk_fp){
+ k->disk_fp=fopen(k->disk_path,"wb");
+ if(!k->disk_fp) return 0;
+ int32_t h[8]; kv_hdr(m,h,0);
+ fwrite(KV_MAGIC,1,8,k->disk_fp); fwrite(h,4,8,k->disk_fp);
+ fflush(k->disk_fp);
+ fclose(k->disk_fp);
+ k->disk_fp=fopen(k->disk_path,"r+b");
+ if(!k->disk_fp) return 0;
+ }
+ return 1;
+}
+
+static void kv_disk_truncate(Model *m, int nrec){
+ if(!g_kvsave) return;
+ KVState *k=m->kv;
+ if(k->disk_fp){ fclose(k->disk_fp); k->disk_fp=NULL; }
+ FILE *f=fopen(k->disk_path,"r+b");
+ if(!f){ k->disk_nrec=0; return; }
+ k->disk_nrec=nrec;
+ int32_t nr=nrec; fseek(f,8+6*4,SEEK_SET); fwrite(&nr,4,1,f);
+ fflush(f); fclose(f);
+}
+
+static void kv_disk_reset(Model *m){ kv_disk_truncate(m,0); }
+
+static void kv_disk_append(Model *m, const int *hist, int len){
+ KVState *k=m->kv;
+ if(!g_kvsave || len<=k->disk_nrec) return;
+ Cfg *c=&m->c;
+ if(!kv_disk_open(m)) return;
+ FILE *f=k->disk_fp;
+ int64_t rec = kv_rec_bytes(m);
+ if(rec > k->disk_buf_cap){
+ uint8_t *nb=realloc(k->disk_buf, rec);
+ if(!nb) return;
+ k->disk_buf=nb; k->disk_buf_cap=rec;
+ }
+ fseek(f, 8+8*4 + (int64_t)k->disk_nrec*rec, SEEK_SET);
+ for(int p=k->disk_nrec;pdisk_buf;
+ *(int32_t*)b = hist[p]; b+=4;
+ for(int i=0;in_layers;i++){
+ memcpy(b, m->Lc[i]+(int64_t)p*c->kv_lora, (size_t)c->kv_lora*4); b+=c->kv_lora*4;
+ memcpy(b, m->Rc[i]+(int64_t)p*c->qk_rope,(size_t)c->qk_rope*4); b+=c->qk_rope*4;
+ }
+ if(m->has_dsa) for(int i=0;in_layers;i++) if(m->Ic[i]){
+ memcpy(b, m->Ic[i]+(int64_t)p*c->index_hd, (size_t)c->index_hd*4); b+=c->index_hd*4;
+ }
+ fwrite(k->disk_buf, 1, (size_t)rec, f);
+ }
+ fflush(f);
+ int32_t nr=len; fseek(f,8+6*4,SEEK_SET); fwrite(&nr,4,1,f);
+ fflush(f);
+ k->disk_nrec=len;
+}
+
+static int kv_disk_load(Model *m, int *hist, int maxctx){
+ if(!g_kvsave) return 0;
+ KVState *k=m->kv;
+ Cfg *c=&m->c;
+ FILE *f=fopen(k->disk_path,"rb"); if(!f) return 0;
+ char mg[8]; int32_t h[8], w[8]; kv_hdr(m,w,0);
+ if(fread(mg,1,8,f)!=8 || memcmp(mg,KV_MAGIC,8) || fread(h,4,8,f)!=8 ||
+ h[0]!=w[0]||h[1]!=w[1]||h[2]!=w[2]||h[3]!=w[3]||h[4]!=w[4]||h[5]!=w[5]){
+ fprintf(stderr,"[KV] ignoring .coli_kv from a different model or version\n"); fclose(f); return 0; }
+ int nrec=h[6];
+ if(nrec<1){ fclose(f); return 0; }
+ if(nrec>=maxctx-8-g_draft){
+ fprintf(stderr,"[KV] saved conversation (%d tokens) exceeds the context: starting over\n",nrec);
+ fclose(f); return 0; }
+ double t0=now_s();
+ for(int p=0;pn_layers;i++){
+ if(fread(m->Lc[i]+(int64_t)p*c->kv_lora, 4, c->kv_lora, f)!=(size_t)c->kv_lora ||
+ fread(m->Rc[i]+(int64_t)p*c->qk_rope, 4, c->qk_rope, f)!=(size_t)c->qk_rope){ nrec=p; goto out; }
+ }
+ if(m->has_dsa) for(int i=0;in_layers;i++) if(m->Ic[i])
+ if(fread(m->Ic[i]+(int64_t)p*c->index_hd, 4, c->index_hd, f)!=(size_t)c->index_hd){ nrec=p; goto out; }
+ }
+out:
+ fclose(f);
+ if(nrec>0){
+ if(m->has_mtp) m->kv_start[c->n_layers]=-1;
+ fprintf(stderr,"[KV] resumed conversation from disk: %d tokens in %.1fs (no re-prefill)\n",
+ nrec, now_s()-t0);
+ }
+ k->disk_nrec=nrec;
+ return nrec;
+}
+
+#endif /* KV_PERSIST_H */
diff --git a/c/quant.h b/c/quant.h
new file mode 100644
index 0000000..928de96
--- /dev/null
+++ b/c/quant.h
@@ -0,0 +1,672 @@
+/* quant.h — quantized matmul kernels (header-only, all functions static).
+ * Multi-architecture SIMD: AVX2 / AVX-512 / AVX-VNNI / ARM NEON / NEON-SDOT /
+ * NEON-i8mm / POWER VSX. Pure compute — no Model or QT dependency. */
+#ifndef COLI_QUANT_H
+#define COLI_QUANT_H
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef _OPENMP
+#include
+#endif
+
+/* ---- SIMD includes -------------------------------------------------------- */
+#ifdef __AVX2__
+#include
+static inline float hsum256(__m256 v){
+ __m128 lo=_mm256_castps256_ps128(v), hi=_mm256_extractf128_ps(v,1);
+ lo=_mm_add_ps(lo,hi); __m128 sh=_mm_movehl_ps(lo,lo); lo=_mm_add_ps(lo,sh);
+ sh=_mm_shuffle_ps(lo,lo,1); lo=_mm_add_ss(lo,sh); return _mm_cvtss_f32(lo);
+}
+static inline int hsum256_i32(__m256i v){
+ __m128i lo=_mm256_castsi256_si128(v), hi=_mm256_extracti128_si256(v,1);
+ lo=_mm_add_epi32(lo,hi); lo=_mm_hadd_epi32(lo,lo); lo=_mm_hadd_epi32(lo,lo);
+ return _mm_cvtsi128_si32(lo);
+}
+#endif
+#if defined(__AVXVNNI__) && defined(__AVX2__)
+static inline int hsum128_i32(__m128i v){
+ v=_mm_hadd_epi32(v,v); v=_mm_hadd_epi32(v,v); return _mm_cvtsi128_si32(v);
+}
+#endif
+#ifdef __ARM_NEON
+#include
+#endif
+#ifdef __VSX__
+#include
+#undef vector
+#undef pixel
+#undef bool
+#endif
+
+/* ---- AVX-512 int4->float accumulator -------------------------------------- */
+#if defined(__AVX512F__) && defined(__AVX512BW__)
+static int g_i4_acc512=1;
+static inline float dot_i4f_avx512(const uint8_t *w,const float *x,int I){
+ const __m128i m4=_mm_set1_epi8(0x0F); const __m512i b8=_mm512_set1_epi32(8);
+ __m512 acc0=_mm512_setzero_ps(),acc1=_mm512_setzero_ps(); int i=0;
+ for(;i+32<=I;i+=32){ __m128i by=_mm_loadu_si128((const __m128i*)(w+(i>>1)));
+ __m128i lo=_mm_and_si128(by,m4),hi=_mm_and_si128(_mm_srli_epi16(by,4),m4);
+ __m128i n0=_mm_unpacklo_epi8(lo,hi),n1=_mm_unpackhi_epi8(lo,hi);
+ __m512 w0=_mm512_cvtepi32_ps(_mm512_sub_epi32(_mm512_cvtepu8_epi32(n0),b8));
+ __m512 w1=_mm512_cvtepi32_ps(_mm512_sub_epi32(_mm512_cvtepu8_epi32(n1),b8));
+ acc0=_mm512_fmadd_ps(_mm512_loadu_ps(x+i),w0,acc0);
+ acc1=_mm512_fmadd_ps(_mm512_loadu_ps(x+i+16),w1,acc1);
+ }
+ return _mm512_reduce_add_ps(_mm512_add_ps(acc0,acc1));
+}
+static int i4_acc512_selftest(void){
+ enum { N=224 }; uint8_t w[(N+1)/2]; float x[N];
+ for(int i=0;i>1]=(uint8_t)(q+8);
+ else w[i>>1]|=(uint8_t)((q+8)<<4);
+ x[i]=(float)(((i*29+7)%101)-50)/37.f;
+ }
+ for(int n=32;n<=N;n+=32){
+ float ref=0; for(int i=0;i>1]>>((i&1)*4))&15)-8);
+ float got=dot_i4f_avx512(w,x,n),tol=2e-5f*(1.f+fabsf(ref));
+ if(fabsf(got-ref)>tol){ fprintf(stderr,"AVX512 i4 selftest n=%d: %.9g != %.9g\n",n,got,ref); return 0; }
+ }
+ return 1;
+}
+#endif
+
+/* ---- y[S,O] = x[S,I] @ W^T, W[O,I] f32 ---------------------------------- */
+static void matmul(float *y, const float *x, const float *W, int S, int I, int O){
+ #pragma omp parallel for schedule(static)
+ for (int o=0;o>1)));
+ __m128i lo=_mm_and_si128(by,m4), hi=_mm_and_si128(_mm_srli_epi16(by,4),m4);
+ __m128i nib=_mm_unpacklo_epi8(lo,hi);
+ __m256 w0=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(nib),b8));
+ __m256 w1=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(_mm_srli_si128(nib,8)),b8));
+ acc=_mm256_fmadd_ps(_mm256_loadu_ps(xs+i), w0, acc);
+ acc=_mm256_fmadd_ps(_mm256_loadu_ps(xs+i+8), w1, acc); }
+ a=hsum256(acc);
+#elif defined(__ARM_NEON)
+ const uint8x8_t m4=vdup_n_u8(0x0F); const int8x8_t b8=vdup_n_s8(8);
+ float32x4_t ac0=vdupq_n_f32(0), ac1=vdupq_n_f32(0);
+ for(;i+16<=I;i+=16){ uint8x8_t by=vld1_u8(w+(i>>1));
+ uint8x8x2_t z=vzip_u8(vand_u8(by,m4), vshr_n_u8(by,4));
+ int16x8_t w0=vmovl_s8(vsub_s8(vreinterpret_s8_u8(z.val[0]),b8));
+ int16x8_t w1=vmovl_s8(vsub_s8(vreinterpret_s8_u8(z.val[1]),b8));
+ ac0=vfmaq_f32(ac0, vld1q_f32(xs+i), vcvtq_f32_s32(vmovl_s16(vget_low_s16(w0))));
+ ac1=vfmaq_f32(ac1, vld1q_f32(xs+i+4), vcvtq_f32_s32(vmovl_s16(vget_high_s16(w0))));
+ ac0=vfmaq_f32(ac0, vld1q_f32(xs+i+8), vcvtq_f32_s32(vmovl_s16(vget_low_s16(w1))));
+ ac1=vfmaq_f32(ac1, vld1q_f32(xs+i+12), vcvtq_f32_s32(vmovl_s16(vget_high_s16(w1)))); }
+ a=vaddvq_f32(vaddq_f32(ac0,ac1));
+#endif
+#if defined(__AVX512F__) && defined(__AVX512BW__)
+ }
+#endif
+ for(;i+1>1]; int lo=(int)(byte&0xF)-8, hi=(int)(byte>>4)-8;
+ a += xs[i]*(float)lo + xs[i+1]*(float)hi; }
+ if(i>1]; int lo=(int)(byte&0xF)-8; a += xs[i]*(float)lo; }
+ y[(int64_t)s*O+o]=a*sc; } }
+}
+
+/* ---- y[S,O] = x[S,I] @ W^T, W int4 packed + per-GROUP scales (fmt=4) ----- */
+static void matmul_i4_grouped(float *y, const float *x, const uint8_t *q4, const float *scale,
+ int S, int I, int O, int gs){
+ int rb=(I+1)/2; int ng=(I+gs-1)/gs;
+ #pragma omp parallel for schedule(static)
+ for(int o=0;oI) glen=I-base;
+ float sc=scl[g];
+ int i=base;
+#ifdef __AVX2__
+ const __m128i m4=_mm_set1_epi8(0x0F); const __m256i b8=_mm256_set1_epi32(8);
+ __m256 acc=_mm256_setzero_ps();
+ for(; i+16<=base+glen; i+=16){ __m128i by=_mm_loadl_epi64((const __m128i*)(w+(i>>1)));
+ __m128i lo=_mm_and_si128(by,m4),hi=_mm_and_si128(_mm_srli_epi16(by,4),m4);
+ __m128i nib=_mm_unpacklo_epi8(lo,hi);
+ __m256 w0=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(nib),b8));
+ __m256 w1=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(_mm_srli_si128(nib,8)),b8));
+ acc=_mm256_fmadd_ps(_mm256_loadu_ps(xs+i), w0, acc);
+ acc=_mm256_fmadd_ps(_mm256_loadu_ps(xs+i+8), w1, acc); }
+ a+=hsum256(acc)*sc;
+#endif
+ for(; i >1];
+ a+=(xs[i]*(float)((int)(byte&0xF)-8)+xs[i+1]*(float)((int)(byte>>4)-8))*sc; }
+ else { uint8_t byte=w[i>>1]; a+=xs[i]*(float)((int)(byte&0xF)-8)*sc; }
+ }
+ }
+ y[(int64_t)s*O+o]=a;
+ }
+ }
+}
+
+/* ---- fused gate+up: one OMP dispatch for both matrices -------------------- */
+static void matmul_i4_pair(float *yg, float *yu, const float *x,
+ const uint8_t *qg, const float *sg,
+ const uint8_t *qu, const float *su, int I, int O){
+ int rb=(I+1)/2;
+ #pragma omp parallel for schedule(static)
+ for(int z=0;z<2*O;z++){
+ int o=z>1)));
+ __m128i lo=_mm_and_si128(by,m4),hi=_mm_and_si128(_mm_srli_epi16(by,4),m4);
+ __m128i nib=_mm_unpacklo_epi8(lo,hi);
+ __m256 w0=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(nib),b8));
+ __m256 w1=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(_mm_srli_si128(nib,8)),b8));
+ acc=_mm256_fmadd_ps(_mm256_loadu_ps(x+i),w0,acc);
+ acc=_mm256_fmadd_ps(_mm256_loadu_ps(x+i+8),w1,acc); }
+ a=hsum256(acc);
+#elif defined(__ARM_NEON)
+ const uint8x8_t m4=vdup_n_u8(0x0F); const int8x8_t b8=vdup_n_s8(8);
+ float32x4_t ac0=vdupq_n_f32(0),ac1=vdupq_n_f32(0);
+ for(;i+16<=I;i+=16){ uint8x8_t by=vld1_u8(w+(i>>1));
+ uint8x8x2_t n=vzip_u8(vand_u8(by,m4),vshr_n_u8(by,4));
+ int16x8_t w0=vmovl_s8(vsub_s8(vreinterpret_s8_u8(n.val[0]),b8));
+ int16x8_t w1=vmovl_s8(vsub_s8(vreinterpret_s8_u8(n.val[1]),b8));
+ ac0=vfmaq_f32(ac0,vld1q_f32(x+i),vcvtq_f32_s32(vmovl_s16(vget_low_s16(w0))));
+ ac1=vfmaq_f32(ac1,vld1q_f32(x+i+4),vcvtq_f32_s32(vmovl_s16(vget_high_s16(w0))));
+ ac0=vfmaq_f32(ac0,vld1q_f32(x+i+8),vcvtq_f32_s32(vmovl_s16(vget_low_s16(w1))));
+ ac1=vfmaq_f32(ac1,vld1q_f32(x+i+12),vcvtq_f32_s32(vmovl_s16(vget_high_s16(w1)))); }
+ a=vaddvq_f32(vaddq_f32(ac0,ac1));
+#endif
+#if defined(__AVX512F__) && defined(__AVX512BW__)
+ }
+#endif
+ for(;i+1>1]; a+=x[i]*(float)((b&15)-8)+x[i+1]*(float)((b>>4)-8); }
+ if(i>1]&15)-8);
+ (z>2)));
+ __m128i p0=_mm_and_si128(by,m2), p1=_mm_and_si128(_mm_srli_epi16(by,2),m2);
+ __m128i p2=_mm_and_si128(_mm_srli_epi16(by,4),m2), p3=_mm_and_si128(_mm_srli_epi16(by,6),m2);
+ __m128i lo=_mm_unpacklo_epi8(p0,p1), hi=_mm_unpacklo_epi8(p2,p3);
+ __m128i nib=_mm_unpacklo_epi16(lo,hi);
+ __m256 w0=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(nib),b2));
+ __m256 w1=_mm256_cvtepi32_ps(_mm256_sub_epi32(_mm256_cvtepu8_epi32(_mm_srli_si128(nib,8)),b2));
+ acc=_mm256_fmadd_ps(_mm256_loadu_ps(xs+i), w0, acc);
+ acc=_mm256_fmadd_ps(_mm256_loadu_ps(xs+i+8), w1, acc); }
+ a=hsum256(acc);
+#elif defined(__ARM_NEON)
+ const uint8x8_t m2v=vdup_n_u8(3); const int8x8_t b2v=vdup_n_s8(2);
+ float32x4_t ac0=vdupq_n_f32(0), ac1=vdupq_n_f32(0);
+ for(;i+16<=I;i+=16){ uint32_t wd; memcpy(&wd, w+(i>>2), 4);
+ uint8x8_t by=vreinterpret_u8_u32(vdup_n_u32(wd));
+ uint8x8x2_t z01=vzip_u8(vand_u8(by,m2v), vand_u8(vshr_n_u8(by,2),m2v));
+ uint8x8x2_t z23=vzip_u8(vand_u8(vshr_n_u8(by,4),m2v), vshr_n_u8(by,6));
+ uint16x4x2_t zz=vzip_u16(vreinterpret_u16_u8(z01.val[0]), vreinterpret_u16_u8(z23.val[0]));
+ int16x8_t w0=vmovl_s8(vsub_s8(vreinterpret_s8_u16(zz.val[0]),b2v));
+ int16x8_t w1=vmovl_s8(vsub_s8(vreinterpret_s8_u16(zz.val[1]),b2v));
+ ac0=vfmaq_f32(ac0, vld1q_f32(xs+i), vcvtq_f32_s32(vmovl_s16(vget_low_s16(w0))));
+ ac1=vfmaq_f32(ac1, vld1q_f32(xs+i+4), vcvtq_f32_s32(vmovl_s16(vget_high_s16(w0))));
+ ac0=vfmaq_f32(ac0, vld1q_f32(xs+i+8), vcvtq_f32_s32(vmovl_s16(vget_low_s16(w1))));
+ ac1=vfmaq_f32(ac1, vld1q_f32(xs+i+12), vcvtq_f32_s32(vmovl_s16(vget_high_s16(w1)))); }
+ a=vaddvq_f32(vaddq_f32(ac0,ac1));
+#endif
+ for(;i>2]; int sh=(i&3)*2; a += xs[i]*(float)((int)((byte>>sh)&3)-2); }
+ y[(int64_t)s*O+o]=a*sc; } }
+}
+
+/* ---- IDOT: integer dot kernels (int8-quantized activations) --------------- */
+#if defined(__AVX512VNNI__) && defined(__AVX512BW__)
+#define IDOT_KERNEL "avx512-vnni"
+#elif defined(__AVXVNNI__) && defined(__AVX2__)
+#define IDOT_KERNEL "avx-vnni"
+#elif defined(__AVX2__)
+#define IDOT_KERNEL "avx2"
+#elif defined(__ARM_NEON) && defined(__ARM_FEATURE_MATMUL_INT8)
+#define IDOT_KERNEL "neon-i8mm"
+#elif defined(__ARM_NEON)
+#define IDOT_KERNEL "neon"
+#elif defined(__VSX__)
+#define IDOT_KERNEL "vsx"
+#else
+#define IDOT_KERNEL "scalar"
+#endif
+static int g_idot=1;
+#if defined(__ARM_NEON) && defined(__ARM_FEATURE_DOTPROD)
+static int g_i4s=1;
+#elif defined(__VSX__)
+static int g_i4s=1;
+#else
+static int g_i4s=2;
+#endif
+
+static inline float qrow_i8(const float *x, int8_t *q, int I){
+ float amax=0; for(int i=0;iamax)amax=a; }
+ float s=amax/127.f; if(s<1e-12f) s=1e-12f; float inv=1.f/s;
+ for(int i=0;i>1)));
+ __m256i lo=_mm256_and_si256(by,m4v), hi=_mm256_and_si256(_mm256_srli_epi16(by,4),m4v);
+ __m256i z0=_mm256_unpacklo_epi8(lo,hi), z1=_mm256_unpackhi_epi8(lo,hi);
+ __m512i wv=_mm512_sub_epi8(_mm512_inserti64x4(_mm512_castsi256_si512(z0),z1,1),b8v);
+ __m512i xv=_mm512_permutexvar_epi64(xidx,_mm512_loadu_si512((const void*)(x+i)));
+ __mmask64 neg=_mm512_movepi8_mask(wv);
+ __m512i xs=_mm512_mask_sub_epi8(xv,neg,_mm512_setzero_si512(),xv);
+ acc=_mm512_dpbusd_epi32(acc,_mm512_abs_epi8(wv),xs);
+ }
+ sum=_mm512_reduce_add_epi32(acc);
+#elif defined(__AVXVNNI__) && defined(__AVX2__)
+ const __m128i m4=_mm_set1_epi8(0x0F); const __m128i b8=_mm_set1_epi8(8);
+ __m128i acc=_mm_setzero_si128();
+ for(;i+32<=I;i+=32){
+ __m128i by=_mm_loadu_si128((const __m128i*)(w4+(i>>1)));
+ __m128i lo=_mm_and_si128(by,m4), hi=_mm_and_si128(_mm_srli_epi16(by,4),m4);
+ __m128i n0=_mm_unpacklo_epi8(lo,hi), n1=_mm_unpackhi_epi8(lo,hi);
+ __m128i w0=_mm_sub_epi8(n0,b8), w1=_mm_sub_epi8(n1,b8);
+ __m128i x0=_mm_loadu_si128((const __m128i*)(x+i));
+ __m128i x1=_mm_loadu_si128((const __m128i*)(x+i+16));
+ acc=_mm_dpbusd_epi32(acc,_mm_abs_epi8(w0),_mm_sign_epi8(x0,w0));
+ acc=_mm_dpbusd_epi32(acc,_mm_abs_epi8(w1),_mm_sign_epi8(x1,w1));
+ }
+ sum=hsum128_i32(acc);
+#elif defined(__AVX2__)
+ const __m128i m4=_mm_set1_epi8(0x0F); const __m256i b8=_mm256_set1_epi8(8);
+ const __m256i ones=_mm256_set1_epi16(1);
+ __m256i acc=_mm256_setzero_si256();
+ for(;i+32<=I;i+=32){
+ __m128i by=_mm_loadu_si128((const __m128i*)(w4+(i>>1)));
+ __m128i lo=_mm_and_si128(by,m4), hi=_mm_and_si128(_mm_srli_epi16(by,4),m4);
+ __m128i n0=_mm_unpacklo_epi8(lo,hi), n1=_mm_unpackhi_epi8(lo,hi);
+ __m256i wv=_mm256_sub_epi8(_mm256_set_m128i(n1,n0),b8);
+ __m256i xv=_mm256_loadu_si256((const __m256i*)(x+i));
+ __m256i p=_mm256_maddubs_epi16(_mm256_sign_epi8(wv,wv),_mm256_sign_epi8(xv,wv));
+ acc=_mm256_add_epi32(acc,_mm256_madd_epi16(p,ones));
+ }
+ sum=hsum256_i32(acc);
+#elif defined(__ARM_NEON)
+ const uint8x16_t m4q=vdupq_n_u8(0x0F); const int8x16_t b8q=vdupq_n_s8(8);
+#if defined(__ARM_FEATURE_DOTPROD)
+ int32x4_t a0=vdupq_n_s32(0),a1=vdupq_n_s32(0),a2=vdupq_n_s32(0),a3=vdupq_n_s32(0);
+ for(;i+64<=I;i+=64){
+ uint8x16_t byA=vld1q_u8(w4+(i>>1)), byB=vld1q_u8(w4+(i>>1)+16);
+ uint8x16x2_t zA=vzipq_u8(vandq_u8(byA,m4q), vshrq_n_u8(byA,4));
+ uint8x16x2_t zB=vzipq_u8(vandq_u8(byB,m4q), vshrq_n_u8(byB,4));
+ a0=vdotq_s32(a0,vsubq_s8(vreinterpretq_s8_u8(zA.val[0]),b8q),vld1q_s8(x+i));
+ a1=vdotq_s32(a1,vsubq_s8(vreinterpretq_s8_u8(zA.val[1]),b8q),vld1q_s8(x+i+16));
+ a2=vdotq_s32(a2,vsubq_s8(vreinterpretq_s8_u8(zB.val[0]),b8q),vld1q_s8(x+i+32));
+ a3=vdotq_s32(a3,vsubq_s8(vreinterpretq_s8_u8(zB.val[1]),b8q),vld1q_s8(x+i+48));
+ }
+ int32x4_t acc=vaddq_s32(vaddq_s32(a0,a1),vaddq_s32(a2,a3));
+ for(;i+32<=I;i+=32){
+ uint8x16_t by=vld1q_u8(w4+(i>>1));
+ uint8x16x2_t z=vzipq_u8(vandq_u8(by,m4q), vshrq_n_u8(by,4));
+ acc=vdotq_s32(acc,vsubq_s8(vreinterpretq_s8_u8(z.val[0]),b8q),vld1q_s8(x+i));
+ acc=vdotq_s32(acc,vsubq_s8(vreinterpretq_s8_u8(z.val[1]),b8q),vld1q_s8(x+i+16));
+ }
+ sum=vaddvq_s32(acc);
+#else
+ int32x4_t acc=vdupq_n_s32(0);
+ for(;i+32<=I;i+=32){
+ uint8x16_t by=vld1q_u8(w4+(i>>1));
+ uint8x16x2_t z=vzipq_u8(vandq_u8(by,m4q), vshrq_n_u8(by,4));
+ int8x16_t w0=vsubq_s8(vreinterpretq_s8_u8(z.val[0]),b8q);
+ int8x16_t w1=vsubq_s8(vreinterpretq_s8_u8(z.val[1]),b8q);
+ int8x16_t x0=vld1q_s8(x+i), x1=vld1q_s8(x+i+16);
+ int16x8_t p=vmull_s8(vget_low_s8(w0),vget_low_s8(x0));
+ p=vmlal_s8(p,vget_high_s8(w0),vget_high_s8(x0));
+ acc=vpadalq_s16(acc,p);
+ p=vmull_s8(vget_low_s8(w1),vget_low_s8(x1));
+ p=vmlal_s8(p,vget_high_s8(w1),vget_high_s8(x1));
+ acc=vpadalq_s16(acc,p);
+ }
+ sum=vaddvq_s32(acc);
+#endif
+#elif defined(__VSX__)
+ const __vector unsigned char m4v=vec_splats((unsigned char)0x0F);
+ const __vector unsigned char sh4=vec_splats((unsigned char)4);
+ const __vector signed char b8v=vec_splats((signed char)8);
+ const __vector signed char vz=vec_splats((signed char)0);
+ __vector signed int acc=vec_splats(0);
+ for(;i+32<=I;i+=32){
+ __vector unsigned char by=vec_xl(0,w4+(i>>1));
+ __vector unsigned char lo=vec_and(by,m4v), hi=vec_sr(by,sh4);
+ __vector signed char w0=vec_sub((__vector signed char)vec_mergeh(lo,hi),b8v);
+ __vector signed char w1=vec_sub((__vector signed char)vec_mergel(lo,hi),b8v);
+ __vector signed char x0=vec_xl(0,(const signed char*)(x+i));
+ __vector signed char x1=vec_xl(0,(const signed char*)(x+i+16));
+ __vector __bool char n0=vec_cmplt(w0,vz), n1=vec_cmplt(w1,vz);
+ acc=vec_msum(vec_sel(x0,vec_sub(vz,x0),n0),
+ (__vector unsigned char)vec_sel(w0,vec_sub(vz,w0),n0),acc);
+ acc=vec_msum(vec_sel(x1,vec_sub(vz,x1),n1),
+ (__vector unsigned char)vec_sel(w1,vec_sub(vz,w1),n1),acc);
+ }
+ sum=vec_extract(acc,0)+vec_extract(acc,1)+vec_extract(acc,2)+vec_extract(acc,3);
+#endif
+ for(;i+1>1]; sum+=((int)(b&0xF)-8)*x[i]+((int)(b>>4)-8)*x[i+1]; }
+ if(i>1]; sum+=((int)(b&0xF)-8)*x[i]; }
+ return sum;
+}
+
+/* ---- ARM i8mm SMMLA tiled kernels ---------------------------------------- */
+#if defined(__ARM_NEON) && defined(__ARM_FEATURE_MATMUL_INT8)
+static inline int32x4_t mm_tile16(int32x4_t acc, int8x16_t wo, int8x16_t wo1,
+ int8x16_t xs, int8x16_t xs1){
+ acc=vmmlaq_s32(acc, vcombine_s8(vget_low_s8(wo), vget_low_s8(wo1)),
+ vcombine_s8(vget_low_s8(xs), vget_low_s8(xs1)));
+ return vmmlaq_s32(acc, vcombine_s8(vget_high_s8(wo), vget_high_s8(wo1)),
+ vcombine_s8(vget_high_s8(xs), vget_high_s8(xs1)));
+}
+static void matmul_q_idot_mm(float *y, const int8_t *xq, const float *sx, const int8_t *q,
+ const float *scale, int S, int I, int O){
+ #pragma omp parallel for schedule(static)
+ for(int o=0;o<(O&~1);o+=2){
+ const int8_t *wo=q+(int64_t)o*I, *wo1=q+(int64_t)(o+1)*I;
+ float sc0=scale[o], sc1=scale[o+1];
+ for(int s=0;s<(S&~1);s+=2){
+ const int8_t *xs=xq+(int64_t)s*I, *xs1=xq+(int64_t)(s+1)*I;
+ int32x4_t a0=vdupq_n_s32(0),a1=vdupq_n_s32(0),a2=vdupq_n_s32(0),a3=vdupq_n_s32(0); int i=0;
+ for(;i+64<=I;i+=64){
+ a0=mm_tile16(a0,vld1q_s8(wo+i), vld1q_s8(wo1+i), vld1q_s8(xs+i), vld1q_s8(xs1+i));
+ a1=mm_tile16(a1,vld1q_s8(wo+i+16),vld1q_s8(wo1+i+16),vld1q_s8(xs+i+16),vld1q_s8(xs1+i+16));
+ a2=mm_tile16(a2,vld1q_s8(wo+i+32),vld1q_s8(wo1+i+32),vld1q_s8(xs+i+32),vld1q_s8(xs1+i+32));
+ a3=mm_tile16(a3,vld1q_s8(wo+i+48),vld1q_s8(wo1+i+48),vld1q_s8(xs+i+48),vld1q_s8(xs1+i+48));
+ }
+ for(;i+16<=I;i+=16)
+ a0=mm_tile16(a0,vld1q_s8(wo+i),vld1q_s8(wo1+i),vld1q_s8(xs+i),vld1q_s8(xs1+i));
+ int32x4_t acc=vaddq_s32(vaddq_s32(a0,a1),vaddq_s32(a2,a3));
+ int32_t d00=vgetq_lane_s32(acc,0), d01=vgetq_lane_s32(acc,1);
+ int32_t d10=vgetq_lane_s32(acc,2), d11=vgetq_lane_s32(acc,3);
+ for(;i>1)), byo1=vld1q_u8(wo1+(i>>1));
+ uint8x16_t cyo=vld1q_u8(wo+(i>>1)+16), cyo1=vld1q_u8(wo1+(i>>1)+16);
+ uint8x16x2_t zo =vzipq_u8(vandq_u8(byo, m4q), vshrq_n_u8(byo, 4));
+ uint8x16x2_t zo1=vzipq_u8(vandq_u8(byo1,m4q), vshrq_n_u8(byo1,4));
+ uint8x16x2_t ko =vzipq_u8(vandq_u8(cyo, m4q), vshrq_n_u8(cyo, 4));
+ uint8x16x2_t ko1=vzipq_u8(vandq_u8(cyo1,m4q), vshrq_n_u8(cyo1,4));
+ a0=mm_tile16(a0, vsubq_s8(vreinterpretq_s8_u8(zo.val[0]),b8q),
+ vsubq_s8(vreinterpretq_s8_u8(zo1.val[0]),b8q),
+ vld1q_s8(xs+i), vld1q_s8(xs1+i));
+ a1=mm_tile16(a1, vsubq_s8(vreinterpretq_s8_u8(zo.val[1]),b8q),
+ vsubq_s8(vreinterpretq_s8_u8(zo1.val[1]),b8q),
+ vld1q_s8(xs+i+16), vld1q_s8(xs1+i+16));
+ a2=mm_tile16(a2, vsubq_s8(vreinterpretq_s8_u8(ko.val[0]),b8q),
+ vsubq_s8(vreinterpretq_s8_u8(ko1.val[0]),b8q),
+ vld1q_s8(xs+i+32), vld1q_s8(xs1+i+32));
+ a3=mm_tile16(a3, vsubq_s8(vreinterpretq_s8_u8(ko.val[1]),b8q),
+ vsubq_s8(vreinterpretq_s8_u8(ko1.val[1]),b8q),
+ vld1q_s8(xs+i+48), vld1q_s8(xs1+i+48));
+ }
+ for(;i+32<=I;i+=32){
+ uint8x16_t byo=vld1q_u8(wo+(i>>1)), byo1=vld1q_u8(wo1+(i>>1));
+ uint8x16x2_t zo =vzipq_u8(vandq_u8(byo, m4q), vshrq_n_u8(byo, 4));
+ uint8x16x2_t zo1=vzipq_u8(vandq_u8(byo1,m4q), vshrq_n_u8(byo1,4));
+ a0=mm_tile16(a0, vsubq_s8(vreinterpretq_s8_u8(zo.val[0]),b8q),
+ vsubq_s8(vreinterpretq_s8_u8(zo1.val[0]),b8q),
+ vld1q_s8(xs+i), vld1q_s8(xs1+i));
+ a1=mm_tile16(a1, vsubq_s8(vreinterpretq_s8_u8(zo.val[1]),b8q),
+ vsubq_s8(vreinterpretq_s8_u8(zo1.val[1]),b8q),
+ vld1q_s8(xs+i+16), vld1q_s8(xs1+i+16));
+ }
+ int32x4_t acc=vaddq_s32(vaddq_s32(a0,a1),vaddq_s32(a2,a3));
+ int32_t d00=vgetq_lane_s32(acc,0), d01=vgetq_lane_s32(acc,1);
+ int32_t d10=vgetq_lane_s32(acc,2), d11=vgetq_lane_s32(acc,3);
+ for(;i+1>1], bo1=wo1[i>>1];
+ int a0=(int)(bo&0xF)-8, a1=(int)(bo>>4)-8, b0=(int)(bo1&0xF)-8, b1=(int)(bo1>>4)-8;
+ int u0=xs[i],u1=xs[i+1],v0=xs1[i],v1=xs1[i+1];
+ d00+=a0*u0+a1*u1; d01+=a0*v0+a1*v1; d10+=b0*u0+b1*u1; d11+=b0*v0+b1*v1; }
+ if(i>1], bo1=wo1[i>>1];
+ int a0=(int)(bo&0xF)-8, b0=(int)(bo1&0xF)-8;
+ d00+=a0*xs[i]; d01+=a0*xs1[i]; d10+=b0*xs[i]; d11+=b0*xs1[i]; }
+ y[(int64_t)s*O+o] =(float)d00*sc0*sx[s];
+ y[(int64_t)s*O+(o+1)] =(float)d10*sc1*sx[s];
+ y[(int64_t)(s+1)*O+o] =(float)d01*sc0*sx[s+1];
+ y[(int64_t)(s+1)*O+(o+1)]=(float)d11*sc1*sx[s+1];
+ }
+ if(S&1){ int s=S-1; const int8_t *xs=xq+(int64_t)s*I;
+ y[(int64_t)s*O+o] =(float)dot_i4i8(wo, xs,I)*sc0*sx[s];
+ y[(int64_t)s*O+(o+1)]=(float)dot_i4i8(wo1,xs,I)*sc1*sx[s]; }
+ }
+ if(O&1){ int o=O-1; const uint8_t *w=q4+(int64_t)o*rb; float sc=scale[o];
+ #pragma omp parallel for schedule(static)
+ for(int s=0;s=2){ matmul_q_idot_mm(y,xq,sx,q,scale,S,I,O); return; }
+#endif
+ #pragma omp parallel for schedule(static)
+ for(int o=0;o=2){ matmul_i4_idot_mm(y,xq,sx,q4,scale,S,I,O); return; }
+#endif
+ #pragma omp parallel for schedule(static)
+ for(int o=0;og_qscratch.xq_cap){
+ int8_t *p=realloc(g_qscratch.xq,xn);
+ if(!p){ fprintf(stderr,"OOM quant scratch\n"); exit(1); }
+ g_qscratch.xq=p; g_qscratch.xq_cap=xn;
+ }
+ if(sn>g_qscratch.sx_cap){
+ float *p=realloc(g_qscratch.sx,sn*sizeof(float));
+ if(!p){ fprintf(stderr,"OOM quant scales\n"); exit(1); }
+ g_qscratch.sx=p; g_qscratch.sx_cap=sn;
+ }
+ *xq=g_qscratch.xq; *sx=g_qscratch.sx;
+}
+
+/* ---- f32 -> quantized packing --------------------------------------------- */
+static void quantize_rows(const float *w, int8_t *q, float *scale, int O, int I, int bits){
+ int qmax=(1<<(bits-1))-1;
+ #pragma omp parallel for schedule(static)
+ for(int o=0;oamax)amax=a; }
+ float s=amax/qmax; if(s<1e-8f)s=1e-8f; scale[o]=s;
+ int8_t *qr=q+(int64_t)o*I;
+ for(int i=0;iqmax)v=qmax; if(v<-qmax-1)v=-qmax-1; qr[i]=(int8_t)v; }
+ }
+}
+static void pack_int4(const float *w, uint8_t *q4, float *scale, int O, int I, int bits){
+ int qmax=(1<<(bits-1))-1, rb=(I+1)/2;
+ #pragma omp parallel for schedule(static)
+ for(int o=0;oamax)amax=a; }
+ float s=amax/qmax; if(s<1e-8f)s=1e-8f; scale[o]=s;
+ uint8_t *qr=q4+(int64_t)o*rb;
+ for(int i=0;iqmax)v0=qmax; if(v0<-8)v0=-8;
+ int v1=0; if(i+1qmax)v1=qmax; if(v1<-8)v1=-8; }
+ qr[i>>1] = (uint8_t)((v0+8) | ((v1+8)<<4));
+ }
+ }
+}
+static void pack_int2(const float *w, uint8_t *q2, float *scale, int O, int I, int bits){
+ int qmax=(1<<(bits-1))-1, rb=(I+3)/4;
+ #pragma omp parallel for schedule(static)
+ for(int o=0;oamax)amax=a; }
+ float s=amax/qmax; if(s<1e-8f)s=1e-8f; scale[o]=s;
+ uint8_t *qr=q2+(int64_t)o*rb;
+ for(int i=0;iqmax)v=qmax; if(v<-2)v=-2; byte|=(uint8_t)((v+2)<<(k*2)); }
+ qr[i>>2]=byte;
+ }
+ }
+}
+
+#endif /* COLI_QUANT_H */
diff --git a/c/sample.h b/c/sample.h
new file mode 100644
index 0000000..abce88c
--- /dev/null
+++ b/c/sample.h
@@ -0,0 +1,143 @@
+/* sample.h — sampling (temperature + nucleus) and stop-set management.
+ * Header-only: all functions are static — include from the main engine file. */
+#ifndef SAMPLE_H
+#define SAMPLE_H
+
+#include
+#include
+#include
+#include "tok.h"
+
+/* ---- RNG (xorshift64*) -------------------------------------------------- */
+static uint64_t g_rng = 0x9E3779B97F4A7C15ULL;
+static inline double rndu(void){
+ g_rng ^= g_rng << 13; g_rng ^= g_rng >> 7; g_rng ^= g_rng << 17;
+ return (double)(g_rng >> 11) * (1.0 / 9007199254740992.0);
+}
+
+/* ---- argmax over a float vector ----------------------------------------- */
+static inline int argmax_v(const float *lo, int V){
+ int b = 0; float bv = lo[0];
+ for (int i = 1; i < V; i++) if (lo[i] > bv) { bv = lo[i]; b = i; }
+ return b;
+}
+
+/* ---- distribution buffers (reused, single-threaded decode) --------------- */
+static float *g_pbuf = NULL;
+static int *g_pidx = NULL;
+
+/* sift-down on max-heap in h[0..n), key = g_pbuf[h[i]] (#335: partial top-p).
+ * "hole" variant: carries the root value and deposits only at the end, so
+ * heapify is O(V) and each pop is O(log n) without qsort on the full vocab. */
+static void topp_siftdown(int *h, int n, int i){
+ int iv = h[i]; float kv = g_pbuf[iv];
+ for (;;) {
+ int l = 2*i + 1;
+ if (l >= n) break;
+ int b = l; if (l+1 < n && g_pbuf[h[l+1]] > g_pbuf[h[l]]) b = l+1;
+ if (g_pbuf[h[b]] <= kv) break;
+ h[i] = h[b]; i = b;
+ }
+ h[i] = iv;
+}
+
+/* build the target distribution in g_pbuf: softmax(lo/temp) truncated to
+ * top-p g_nuc. Invariant: g_pbuf stays indexed by token-id (never reordered);
+ * the truncated tail is zeroed (dist_sample reads by id directly).
+ * Requires: g_temp, g_nuc, falloc() — declared in the main engine file. */
+static void dist_build(const float *lo, int V){
+ if (!g_pbuf) { g_pbuf = falloc(V); g_pidx = malloc(V * sizeof(int)); }
+ int mxi = -1; float mx = 0;
+ for (int i = 0; i < V; i++)
+ if (isfinite(lo[i]) && (mxi < 0 || lo[i] > mx)) { mx = lo[i]; mxi = i; }
+ double s = 0; float invt = 1.f / (g_temp > 1e-4f ? g_temp : 1e-4f);
+ if (mxi >= 0) {
+ for (int i = 0; i < V; i++) {
+ g_pbuf[i] = isfinite(lo[i]) ? expf((lo[i] - mx) * invt) : 0.f;
+ s += g_pbuf[i];
+ }
+ }
+ if (mxi < 0 || !isfinite(s) || s <= 0.0) {
+ static int warned = 0;
+ if (!warned) { warned = 1; fprintf(stderr,
+ "[SAMPLE] warning: non-finite logits (NaN/Inf) — falling back to argmax; "
+ "output may be degraded. This usually means a numerical blow-up upstream.\n"); }
+ int a = (mxi >= 0) ? mxi : 0;
+ for (int i = 0; i < V; i++) g_pbuf[i] = 0.f;
+ g_pbuf[a] = 1.f;
+ return;
+ }
+ for (int i = 0; i < V; i++) g_pbuf[i] /= (float)s;
+ if (g_nuc > 0 && g_nuc < 1.f) {
+ for (int i = 0; i < V; i++) g_pidx[i] = i;
+ for (int i = V/2-1; i >= 0; i--) topp_siftdown(g_pidx, V, i);
+ double s2 = 0, cum = 0; int out = V;
+ do {
+ int root = g_pidx[0];
+ g_pidx[0] = g_pidx[--out]; g_pidx[out] = root;
+ s2 += g_pbuf[root]; cum += g_pbuf[root];
+ if (out > 0) topp_siftdown(g_pidx, out, 0);
+ } while (cum < g_nuc && out > 0);
+ for (int i = 0; i < out; i++) g_pbuf[g_pidx[i]] = 0;
+ float s2f = (float)s2;
+ for (int i = out; i < V; i++) g_pbuf[g_pidx[i]] /= s2f;
+ }
+}
+
+/* sample from g_pbuf; ban>=0 excludes that token (renormalizing on the fly) */
+static int dist_sample(int V, int ban){
+ double z = 1.0 - (ban >= 0 ? g_pbuf[ban] : 0.0);
+ if (z <= 1e-12) z = 1e-12;
+ double u = rndu() * z, cum = 0;
+ for (int i = 0; i < V; i++) { if (i == ban) continue; cum += g_pbuf[i]; if (cum >= u) return i; }
+ for (int i = V-1; i >= 0; i--) if (i != ban && g_pbuf[i] > 0) return i;
+ return 0;
+}
+
+/* next token from logits: greedy if g_temp<=0, sampling otherwise.
+ * ban = token excluded because it was rejected by speculative verification. */
+static int pick_tok(const float *lo, int V, int ban){
+ if (g_temp <= 0) return argmax_v(lo, V);
+ dist_build(lo, V);
+ return dist_sample(V, ban);
+}
+
+/* ---- stop set ----------------------------------------------------------- */
+static int g_stop[64], g_nstop = 0;
+static inline int is_stop(int t){
+ for (int i = 0; i < g_nstop; i++) if (t == g_stop[i]) return 1;
+ return 0;
+}
+/* T=NULL -> config stops only (validation/oracle, where the tokenizer is not needed). */
+static void stops_arm_tok(const Cfg *c, int tok_eos, Tok *T){
+ g_nstop = 0;
+ for (int i = 0; i < c->n_stop && g_nstop < 64; i++) g_stop[g_nstop++] = c->stop_ids[i];
+ if (tok_eos >= 0 && !is_stop(tok_eos) && g_nstop < 64) g_stop[g_nstop++] = tok_eos;
+ int nsp = 0;
+ if (T) for (int id = 0; id < T->n_ids && g_nstop < 64; id++)
+ if (T->id_special[id] && !is_stop(id)) { g_stop[g_nstop++] = id; nsp++; }
+ fprintf(stderr, "[stop] %d stop tokens:", g_nstop);
+ for (int i = 0; i < g_nstop; i++) fprintf(stderr, " %d", g_stop[i]);
+ if (nsp) fprintf(stderr, " (%d from the tokenizer's special set)", nsp);
+ fprintf(stderr, "\n");
+}
+static void stops_arm(const Cfg *c, int tok_eos){ stops_arm_tok(c, tok_eos, NULL); }
+
+/* ---- log-prob of a target token given the logit vector ------------------- */
+static double logprob_target(const float *lo, int V, int target, int *am){
+ float mx = lo[0]; int best = 0;
+ for (int i = 1; i < V; i++) if (lo[i] > mx) { mx = lo[i]; best = i; }
+ double se = 0;
+ for (int i = 0; i < V; i++) se += exp((double)lo[i] - mx);
+ if (am) *am = (best == target);
+ return (double)(lo[target] - mx) - log(se);
+}
+
+/* "glm" in model_type, case-insensitive */
+static int mt_is_glm(const char *s){
+ if (s) for (; *s; s++)
+ if ((s[0]|32) == 'g' && (s[1]|32) == 'l' && (s[2]|32) == 'm') return 1;
+ return 0;
+}
+
+#endif /* SAMPLE_H */
diff --git a/c/setup.sh b/c/setup.sh
index e31e60a..dcb851d 100755
--- a/c/setup.sh
+++ b/c/setup.sh
@@ -32,11 +32,11 @@ esac
# 2) build: nativa (veloce, per QUESTA macchina). Per un binario da distribuire: make portable
echo " building (ARCH=${ARCH:-native})…"
-make -s glm ARCH="${ARCH:-native}"
+make -s colibri ARCH="${ARCH:-native}"
# 3) self-test sull'oracolo tiny, se presente
if [ -d glm_tiny ] && [ -f ref_glm.json ]; then
- r=$(SNAP=./glm_tiny TF=1 ./glm 64 16 16 2>/dev/null | grep -oE "[0-9]+/[0-9]+ positions" || true)
+ r=$(SNAP=./glm_tiny TF=1 ./colibri 64 16 16 2>/dev/null | grep -oE "[0-9]+/[0-9]+ positions" || true)
echo " engine self-test: ${r:-?} (expected 32/32)"
fi
diff --git a/c/telemetry.h b/c/telemetry.h
new file mode 100644
index 0000000..3c3fe85
--- /dev/null
+++ b/c/telemetry.h
@@ -0,0 +1,189 @@
+/* telemetry.h — dashboard protocol lines, stats/usage persistence, hardware probe.
+ * Include after Model/Cfg/QT/ESlot/shards and st.h are defined; requires
+ * qt_bytes(), now_s(), rss_gb(), edisk_s(), and the g_cuda_* globals (ifdef). */
+#ifndef TELEMETRY_H
+#define TELEMETRY_H
+
+static int64_t tbytes(int O,int I,int bits){
+ if(bits>=16) return (int64_t)O*I*4;
+ if(bits>=5) return (int64_t)O*I + (int64_t)O*4;
+ return (int64_t)O*((I+1)/2) + (int64_t)O*4;
+}
+
+static int64_t expert_bytes_probe(Model *m, int ebits){
+ Cfg *c=&m->c; int64_t eb=0; char nm[256];
+ snprintf(nm,sizeof(nm),"model.layers.%d.mlp.experts.0.gate_proj.weight",c->first_dense);
+ if(st_nbytes(&m->S,nm)>0){
+ const char *suf[3]={"gate_proj","up_proj","down_proj"};
+ for(int k=0;k<3;k++){
+ snprintf(nm,sizeof(nm),"model.layers.%d.mlp.experts.0.%s.weight",c->first_dense,suf[k]);
+ eb+=st_nbytes(&m->S,nm);
+ snprintf(nm,sizeof(nm),"model.layers.%d.mlp.experts.0.%s.weight.qs",c->first_dense,suf[k]);
+ int64_t q=st_nbytes(&m->S,nm); if(q>0) eb+=q;
+ }
+ }
+ if(eb<=0) eb = tbytes(c->moe_inter,c->hidden,ebits)*2 + tbytes(c->hidden,c->moe_inter,ebits);
+ return eb;
+}
+
+/* BRAIN MAP: per-turn expert hit bitmap for the dashboard. */
+static uint8_t **g_ehit;
+static void ehit_mark(Model *m, int layer, int eid){
+ if(!g_ehit){ Cfg *c=&m->c;
+ g_ehit=calloc(c->n_layers+1,sizeof(uint8_t*));
+ for(int i=0;i<=c->n_layers;i++) g_ehit[i]=calloc(c->n_experts,1);
+ }
+ g_ehit[layer][eid]=1;
+}
+
+/* CPU model + cores + RAM (GB); empty/zero where unavailable. */
+static void hw_probe(char *cpu, size_t cn, int *cores, double *ram_total, double *ram_avail){
+ cpu[0]=0;
+#ifdef _WIN32
+#if defined(__x86_64__) || defined(__i386__)
+ { unsigned int r[12]={0}; unsigned int *w=r;
+ for(unsigned int f=0x80000002u; f<=0x80000004u; f++,w+=4)
+ __get_cpuid(f,&w[0],&w[1],&w[2],&w[3]);
+ char *b=(char*)r; b[47]=0; while(*b==' ')b++;
+ snprintf(cpu,cn,"%s",b); }
+#endif
+#else
+ FILE *ci=fopen("/proc/cpuinfo","r");
+ if(ci){ char ln[256];
+ while(fgets(ln,sizeof(ln),ci)) if(!strncmp(ln,"model name",10)){
+ char *p=strchr(ln,':'); if(p){ p++; while(*p==' ')p++;
+ int n=(int)strlen(p); if(n>0&&p[n-1]=='\n')p[--n]=0;
+ snprintf(cpu,cn,"%s",p); } break; }
+ fclose(ci); }
+#endif
+ *cores=0;
+#ifdef _WIN32
+ { SYSTEM_INFO si; GetSystemInfo(&si); *cores=(int)si.dwNumberOfProcessors; }
+#elif defined(_SC_NPROCESSORS_ONLN)
+ *cores=(int)sysconf(_SC_NPROCESSORS_ONLN);
+#endif
+ *ram_total=*ram_avail=0;
+#ifdef _WIN32
+ compat_meminfo(ram_total,ram_avail);
+#else
+ FILE *mi=fopen("/proc/meminfo","r");
+ if(mi){ char ln[256]; double mt=0,ma=0;
+ while(fgets(ln,sizeof(ln),mi)){
+ if(sscanf(ln,"MemTotal: %lf",&mt)==1) *ram_total=mt/1e6;
+ if(sscanf(ln,"MemAvailable: %lf",&ma)==1) *ram_avail=ma/1e6;
+ } fclose(mi); }
+#endif
+}
+
+static void hwinfo_emit(Model *m){
+ Cfg *c=&m->c; (void)c;
+ char cpu[256]; int cores; double ram_total,ram_avail;
+ hw_probe(cpu,sizeof(cpu),&cores,&ram_total,&ram_avail);
+ int ngpu=0; double vram_total=0;
+ char gpu_name[128]="";
+#ifdef COLI_CUDA
+ ngpu=g_cuda_ndev; vram_total=m->gpu_expert_bytes/1e9;
+ for(int i=0;i0)
+ snprintf(gpu_name,sizeof(gpu_name),"CUDA device x%d",g_cuda_ndev);
+#endif
+ printf("HWINFO %d %.1f %.1f %d %.1f %s|%s\n",
+ cores,ram_total,ram_avail,ngpu,vram_total,cpu,gpu_name);
+ fflush(stdout);
+}
+
+static void tiers_emit(Model *m){
+ Cfg *c=&m->c; int nsp=0;
+ for(int i=0;in_layers;i++) if(m->L[i].sparse) nsp++;
+ int total=(nsp+(m->has_mtp?1:0))*c->n_experts;
+ int pinned=0,lru=0;
+ for(int i=0;i<=c->n_layers;i++){ pinned+=m->npin?m->npin[i]:0; lru+=m->ecn?m->ecn[i]:0; }
+ int vram=0; double vram_gb=0;
+#ifdef COLI_CUDA
+ vram=m->gpu_expert_count; vram_gb=m->gpu_expert_bytes/1e9;
+#endif
+ int ram=pinned-vram+lru; if(ram<0) ram=0;
+ int disk=total-vram-ram; if(disk<0) disk=0;
+ double eb=(double)expert_bytes_probe(m,m->ebits);
+ printf("TIERS %d %d %d %.2f %.2f\n",vram,ram,disk,vram_gb,ram*eb/1e9);
+ fflush(stdout);
+}
+
+static void emap_emit(Model *m){
+ Cfg *c=&m->c;
+ int rows=0;
+ for(int i=0;in_layers;i++) if(m->L[i].sparse) rows++;
+ int has_mtp = m->has_mtp && m->eusage[c->n_layers];
+ if(has_mtp) rows++;
+ int cols=c->n_experts;
+ char *hex=malloc((size_t)rows*cols*2+1); int w=0;
+ for(int i=0;i<=c->n_layers;i++){
+ int is_row = (in_layers && m->L[i].sparse) || (i==c->n_layers && has_mtp);
+ if(!is_row) continue;
+ for(int e=0;epin[i];
+ for(int z=0;znpin[i];z++) if(P[z].eid==e){
+#ifdef COLI_CUDA
+ tier = P[z].g.cuda?2:1;
+#else
+ tier = 1;
+#endif
+ break; }
+ if(!tier && m->ecache && m->ecache[i])
+ for(int z=0;zecn[i];z++) if(m->ecache[i][z].eid==e){ tier=1; break; }
+ uint32_t u = m->eusage[i]?m->eusage[i][e]:0;
+ int heat=0; while(u){ heat++; u>>=1; } if(heat>63) heat=63;
+ int b=(tier<<6)|heat;
+ hex[w++]="0123456789abcdef"[b>>4]; hex[w++]="0123456789abcdef"[b&15];
+ }
+ }
+ hex[w]=0;
+ printf("EMAP %d %d %s\n",rows,cols,hex); fflush(stdout); free(hex);
+}
+
+static void hits_emit(Model *m){
+ Cfg *c=&m->c; if(!g_ehit) return;
+ int rows=0;
+ for(int i=0;in_layers;i++) if(m->L[i].sparse) rows++;
+ int has_mtp = m->has_mtp && m->eusage[c->n_layers];
+ if(has_mtp) rows++;
+ int cols=c->n_experts, nb=(rows*cols+7)/8;
+ uint8_t *bm=calloc(nb,1); int bit=0;
+ for(int i=0;i<=c->n_layers;i++){
+ int is_row = (in_layers && m->L[i].sparse) || (i==c->n_layers && has_mtp);
+ if(!is_row) continue;
+ for(int e=0;e>3]|=1<<(bit&7); g_ehit[i][e]=0; }
+ }
+ char *hex=malloc((size_t)nb*2+1); int w=0;
+ for(int b=0;b>4]; hex[w++]="0123456789abcdef"[bm[b]&15]; }
+ hex[w]=0;
+ printf("HITS %d %d %s\n",rows,cols,hex); fflush(stdout); free(hex); free(bm);
+}
+
+static void stats_dump_q(Model *m, const char *path, int quiet){
+ char tmp[2100]; snprintf(tmp,sizeof(tmp),"%s.tmp",path);
+ FILE *f=fopen(tmp,"w"); if(!f){ if(!quiet) perror(tmp); return; }
+ Cfg *c=&m->c; int64_t tot=0, nz=0;
+ for(int i=0;i<=c->n_layers;i++){ if(!m->eusage[i]) continue;
+ for(int e=0;en_experts;e++) if(m->eusage[i][e]){ fprintf(f,"%d %d %u\n",i,e,m->eusage[i][e]); tot+=m->eusage[i][e]; nz++; } }
+ fclose(f); rename(tmp,path);
+ if(!quiet) fprintf(stderr,"[STATS] %lld selections across %lld distinct experts -> %s\n",(long long)tot,(long long)nz,path);
+}
+static void stats_dump(Model *m, const char *path){ stats_dump_q(m,path,0); }
+
+static char g_usage_path[2100]="";
+static int64_t usage_load(Model *m, const char *path){
+ FILE *f=fopen(path,"r"); if(!f) return 0;
+ Cfg *c=&m->c; int l,e; uint32_t cnt; int64_t tot=0;
+ while(fscanf(f,"%d %d %u",&l,&e,&cnt)==3)
+ if(l>=0&&l<=c->n_layers&&e>=0&&en_experts&&m->eusage[l]){ m->eusage[l][e]+=cnt; tot+=cnt; }
+ fclose(f); return tot;
+}
+static void usage_save(Model *m){ if(g_usage_path[0]) stats_dump_q(m,g_usage_path,1); }
+
+#endif /* TELEMETRY_H */
diff --git a/c/tests/bench_dsa_select.c b/c/tests/bench_dsa_select.c
index a1f2014..676e4c2 100644
--- a/c/tests/bench_dsa_select.c
+++ b/c/tests/bench_dsa_select.c
@@ -24,7 +24,7 @@
* Run: make tests/bench_dsa_select && ./tests/bench_dsa_select (not in TEST_BINS)
*/
#define main coli_glm_main_unused
-#include "../glm.c"
+#include "../colibri.c"
#undef main
#include
diff --git a/c/tests/bench_topp.c b/c/tests/bench_topp.c
index 9c0682f..b284308 100644
--- a/c/tests/bench_topp.c
+++ b/c/tests/bench_topp.c
@@ -22,7 +22,7 @@
* Run: make tests/bench_topp && ./tests/bench_topp (not in TEST_BINS -- not a gate)
*/
#define main coli_glm_main_unused
-#include "../glm.c"
+#include "../colibri.c"
#undef main
#include
diff --git a/c/tests/test_dsa_select.c b/c/tests/test_dsa_select.c
index 99fecb0..ef3fd7b 100644
--- a/c/tests/test_dsa_select.c
+++ b/c/tests/test_dsa_select.c
@@ -31,7 +31,7 @@
* In-memory only (no scratch files), so it builds clean on the Windows MinGW CI
* job without the unmerged compat shim. */
#define main coli_glm_main_unused
-#include "../glm.c"
+#include "../colibri.c"
#undef main
#include
diff --git a/c/tests/test_i4_grouped.c b/c/tests/test_i4_grouped.c
index 2a9521e..04e8d88 100644
--- a/c/tests/test_i4_grouped.c
+++ b/c/tests/test_i4_grouped.c
@@ -18,7 +18,7 @@
* index, a wrong group boundary or a swapped nibble cannot hide under it —
* those are O(1) relative errors, not O(1e-6). */
#define main coli_glm_main_unused
-#include "../glm.c"
+#include "../colibri.c"
#undef main
static uint32_t rng_state=0xC0FFEEu;
diff --git a/c/tests/test_idot.c b/c/tests/test_idot.c
index b7109d4..343535c 100644
--- a/c/tests/test_idot.c
+++ b/c/tests/test_idot.c
@@ -7,7 +7,7 @@
* (sign-trick kernels must treat |−128| as 128 unsigned, not saturate to 127),
* and random data at qrow_i8's contract (|x| <= 127, w full int8 range). */
#define main coli_glm_main_unused
-#include "../glm.c"
+#include "../colibri.c"
#undef main
static uint32_t rng_state=0x12345678u;
diff --git a/c/tests/test_kv_alloc.c b/c/tests/test_kv_alloc.c
index 40ec00d..d080c08 100644
--- a/c/tests/test_kv_alloc.c
+++ b/c/tests/test_kv_alloc.c
@@ -4,7 +4,7 @@
* arrays twice on the second call -> allocator abort. No model file needed:
* the CPU path of kv_alloc only reads c->n_layers/kv_lora/qk_rope. */
#define main coli_glm_main_unused
-#include "../glm.c"
+#include "../colibri.c"
#undef main
int main(void){
diff --git a/c/tests/test_makefile_platform.py b/c/tests/test_makefile_platform.py
index 3c72880..50b4c7a 100644
--- a/c/tests/test_makefile_platform.py
+++ b/c/tests/test_makefile_platform.py
@@ -35,7 +35,7 @@ class MakefilePlatformTests(unittest.TestCase):
env["PATH"] = ""
result = subprocess.run(
- [MAKE, "--no-print-directory", "-B", "-n", "glm"],
+ [MAKE, "--no-print-directory", "-B", "-n", "colibri"],
cwd=C_DIR,
env=env,
text=True,
@@ -43,7 +43,7 @@ class MakefilePlatformTests(unittest.TestCase):
check=True,
)
- self.assertIn("-o glm.exe", result.stdout)
+ self.assertIn("-o colibri.exe", result.stdout)
self.assertIn("-fopenmp", result.stdout)
self.assertIn("-static", result.stdout)
diff --git a/c/tests/test_sample_nan.c b/c/tests/test_sample_nan.c
index fc33268..4f31def 100644
--- a/c/tests/test_sample_nan.c
+++ b/c/tests/test_sample_nan.c
@@ -6,7 +6,7 @@
* iniettato il token scelto e' l'argmax dei FINITI (mai 0 per default), su ogni posizione
* del NaN inclusa lo[0]; (c) nessun NaN sopravvive in g_pbuf. */
#define main coli_glm_main_unused
-#include "../glm.c"
+#include "../colibri.c"
#undef main
#include
diff --git a/c/tests/test_stops.c b/c/tests/test_stops.c
index 00f922b..5e7e9d2 100644
--- a/c/tests/test_stops.c
+++ b/c/tests/test_stops.c
@@ -20,7 +20,7 @@
* Defense 2 is what makes this robust against checkpoints we don't control:
* even with BOTH configs mutilated, a control token cannot leak into a reply. */
#define main coli_glm_main_unused
-#include "../glm.c"
+#include "../colibri.c"
#undef main
static const char *TOKJSON =
diff --git a/c/tests/test_topp.c b/c/tests/test_topp.c
index 9276507..49f45d6 100644
--- a/c/tests/test_topp.c
+++ b/c/tests/test_topp.c
@@ -24,7 +24,7 @@
* No scratch files: the test runs entirely in memory (no mkdtemp), so it builds clean on
* the Windows MinGW CI job without the unmerged compat shim (#352). */
#define main coli_glm_main_unused
-#include "../glm.c"
+#include "../colibri.c"
#undef main
#include
diff --git a/c/tests/test_uring.c b/c/tests/test_uring.c
index e64b9f2..625c79b 100644
--- a/c/tests/test_uring.c
+++ b/c/tests/test_uring.c
@@ -6,7 +6,7 @@
#include
#include
#define main coli_glm_main_unused
-#include "../glm.c"
+#include "../colibri.c"
#undef main
static int fail(const char *s){ fprintf(stderr,"FAIL: %s\n",s); return 1; }
From 420a0720c31808753d03784de56a5fcd1a51a61d Mon Sep 17 00:00:00 2001
From: ZacharyZcR
Date: Sun, 19 Jul 2026 04:02:00 +0800
Subject: [PATCH 18/22] docs: add simplified Chinese and Italian README, update
language nav
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
New files:
README.zh-CN.md — simplified Chinese (大陆用词)
README.it.md — Italian (the project's "mother tongue")
All four READMEs now link to each other in a consistent nav bar.
Updated zh-TW to reflect glm.c → colibri.c rename and new headers.
---
README.it.md | 260 ++++++++++++++++++++++++++++++++++++++++++++++++
README.md | 2 +-
README.zh-CN.md | 237 +++++++++++++++++++++++++++++++++++++++++++
README.zh-TW.md | 12 ++-
4 files changed, 506 insertions(+), 5 deletions(-)
create mode 100644 README.it.md
create mode 100644 README.zh-CN.md
diff --git a/README.it.md b/README.it.md
new file mode 100644
index 0000000..76e3c21
--- /dev/null
+++ b/README.it.md
@@ -0,0 +1,260 @@
+
+
+
+
+
+ English · 简体中文 · 繁體中文 · Italiano
+
+
+**Motore piccolo, modello immenso.** Esegui **GLM-5.2 (744 miliardi di parametri, MoE)** su un computer consumer con ~25 GB di RAM — in C puro, zero dipendenze, caricando gli expert dal disco in streaming.
+
+Colibrì è un runtime MoE leggero e che preserva la qualità: tratta VRAM, RAM e
+disco come un'unica gerarchia di memoria gestita. Se la memoria veloce non basta
+il modello rallenta, ma la policy predefinita **non cambia mai silenziosamente la
+precisione del modello né la semantica del router**.
+
+```
+$ ./coli chat
+ 🐦 colibrì v1.0 — GLM-5.2 · 744B MoE · int4 · streaming CPU
+ ✓ ready in 32s · resident 9.9 GB
+ › ciao!
+ ◆ Ciao! 😊 Come posso aiutarti oggi?
+```
+
+## Guardalo in azione
+
+
+
+
+La dashboard web (./coli web): un modello da 744B a 4 tok/s, TTFT 1.6 s, disco 0 —
+residenza completa degli expert su 6× RTX 5090, con metriche token in tempo reale, breakdown dei tempi per turno,
+la barra dei livelli VRAM/RAM/disco e il mini-cervello live nell'angolo.
+
+
+
+
+La pagina Brain : tutti i 19.456 expert come una corteccia vivente — il colore indica
+il livello di archiviazione, la luminosità il calore di routing, e ogni expert instradato in un turno
+lampeggia bianco. Passando il cursore si vede l'affinità
+tematica misurata dell'expert.
+
+
+
+
+La pagina Atlas : l'atlante
+misurato degli expert come una galassia 3D — 13.260 expert caratterizzati, 1.041 specialisti
+replicabili che si raggruppano per argomento (poesia, legge, cinese, SQL…). La posizione deriva
+dall'affinità di routing misurata, non da un embedding appreso. Trascinare per ruotare.
+
+## L'idea
+
+Un modello Mixture-of-Experts da 744B attiva solo ~40B parametri per token — e
+solo ~11 GB di quelli cambiano da un token all'altro (gli expert instradati):
+
+
+
+
+
+Il modello non ha bisogno di *stare* in memoria veloce — ha bisogno di essere
+**piazzato**:
+
+- la **parte densa** (attenzione, expert condivisi, embedding — ~17B parametri)
+ resta **residente in RAM a int4** (~9.9 GB);
+- i **19.456 expert instradati** (75 layer MoE × 256 + la testa MTP, ~19 MB
+ ciascuno a int4) stanno **su disco** (~370 GB) e vengono **caricati on demand
+ in streaming**, con una cache LRU per layer, un hot-store pinnato che impara,
+ e un livello VRAM opzionale.
+
+Il motore è un singolo file C (`c/colibri.c`) più header piccoli. Niente BLAS,
+niente Python a runtime, niente GPU obbligatoria.
+
+## Come funziona
+
+### Il percorso di ogni token
+
+
+
+
+
+Ogni layer di ogni token percorre gli stessi cinque passi. L'obiettivo
+progettuale è che **il piazzamento decide solo la velocità** — le decisioni
+del router e la precisione dei pesi sono identiche sia che un expert risponda
+dalla VRAM sia dal disco.
+
+### Una gerarchia di memoria, non un requisito di memoria
+
+
+
+
+
+Lo stesso motore copre l'intero spettro: su un portatile da 25 GB tutto viene
+caricato dal disco in streaming (lento, ma corretto); su un host grande l'intero
+set di expert diventa residente (`CUDA_EXPERT_GB=auto PIN_GB=all`) e il disco
+esce completamente dal percorso di decode. Tra i livelli c'è una **cache che
+impara**: il motore registra quali expert il *tuo* carico di lavoro instrada
+(`.coli_usage`, aggiornato a ogni turno) e fissa automaticamente i più caldi —
+colibrì diventa letteralmente più veloce man mano che lo usi. Sugli host
+multi-socket, `COLI_NUMA=1` interlaccia i pesi residenti tra i controller di
+memoria ([#82](https://github.com/JustVugg/colibri/issues/82)).
+
+### Mai aspettare il disco due volte
+
+I miss nella cache costano caro, quindi il motore investe la maggior parte
+della sua astuzia per evitarli e sovrapporli: le tre matrici di ogni expert sono
+memorizzate contigue e lette con un unico `pread`; un pool I/O asincrono
+limitato (`PIPE=1`, attivo per default) carica gli expert mancanti mentre quelli
+residenti calcolano; le posizioni in batch leggono ogni expert unico una sola
+volta (**batch-union**); un thread di lookahead del router (`PILOT=1`) fa il
+prefetch degli expert del layer successivo — il routing è misurabilmente
+**prevedibile al 71.6% un layer in anticipo**. Sulle GPU, la pipeline residente
+(`COLI_CUDA_PIPE=2`) mantiene il flusso residuo on-device tra i layer, così il
+loop CPU degli expert procede senza interruzioni; su Apple Silicon un backend
+[Metal](docs/metal.md) sperimentale esegue la matmul batch degli expert sulla
+GPU a memoria unificata.
+
+### Modello fedele, stato compresso
+
+Il forward pass è validato **token-esatto contro un oracle `transformers`**
+(teacher-forcing 32/32). L'attenzione MLA memorizza uno stato KV compresso — 576
+float/token invece di 32.768 (**57× più piccolo**) — e lo persiste tra i
+riavvii (`.coli_kv`): le conversazioni riaprono "calde", senza alcun re-prefill,
+byte-identiche a una sessione ininterrotta. L'attenzione sparsa DSA (il
+lightning indexer di GLM-5.2) è implementata fedelmente e validata forzando la
+selezione di tutte le chiavi per riprodurre esattamente l'attenzione densa.
+
+### Decodifica speculativa, onestamente
+
+La testa MTP nativa di GLM-5.2 propone token che il modello principale verifica
+in un unico forward batch — 2.2–2.8 token/forward quando conviene. Due regole
+conquistate a caro prezzo sono i default: la testa MTP deve essere **int8** (le
+teste int4 crollano al 0–4% di accettazione,
+[#8](https://github.com/JustVugg/colibri/issues/8)), e draft e verifica devono
+calcolare **la stessa funzione** — `SPEC_PIN=1` fissa entrambi sulla stessa
+famiglia di kernel ([#163](https://github.com/JustVugg/colibri/issues/163)
+contiene l'intera indagine forense). I draft forzati da grammatica
+([`GRAMMAR=file.gbnf`](docs/grammar-draft.md)) aggiungono accettazione quasi
+gratuita sull'output JSON vincolato. Se la speculazione conviene dipende dalla
+temperatura della cache — misura, e usa `DRAFT=0` quando non paga.
+
+## Cosa ottiene
+
+
+
+
+
+Stesso motore, stesso container int4 — cambia solo dove risiedono gli expert.
+Punti salienti dalle [tabelle benchmark complete](docs/benchmarks.md):
+
+- **6× RTX 5090, residenza completa:** 5.8–6.8 tok/s in decode, TTFT ~13 s
+ ([log dell'esperimento](docs/experiments/glm52-6x5090-2026-07-12.md));
+- **desktop solo-CPU da 128 GB:** ~1.8 tok/s a cache calda
+ ([#200](https://github.com/JustVugg/colibri/issues/200));
+- **singola RTX 5070 Ti, classe laptop:** 1.07 tok/s tramite la pipeline
+ GPU-residente ([#273](https://github.com/JustVugg/colibri/issues/273));
+- **macchina di sviluppo da 25 GB:** 0.05–0.1 tok/s a freddo — il punto di
+ partenza dimostrato da cui è nato il progetto, e ancora oggi la baseline onesta.
+
+La qualità è misurata, non presunta: il costo di quantizzazione del container
+int4 e le ablazioni su granularità delle scale e rotazione sono in
+[docs/benchmarks.md](docs/benchmarks.md#quality-benchmark) e
+[#108](https://github.com/JustVugg/colibri/issues/108)/[#81](https://github.com/JustVugg/colibri/issues/81).
+
+## Per iniziare
+
+### 1. Scarica il modello
+
+Un container **GLM-5.2 int4** pre-convertito è su Hugging Face — **usa la
+versione con le teste MTP int8**:
+
+**https://huggingface.co/mateogrgic/GLM-5.2-colibri-int4-with-int8-mtp**
+
+> ⚠️ Il mirror originale contiene teste MTP int4 → accettazione dei draft allo 0%
+> ([#8](https://github.com/JustVugg/colibri/issues/8)). Verifica la tua versione:
+> `ls -l /out-mtp-*` — int8 (corretto) è `3527131672 / 5366238584 / 1065950496`.
+
+Oppure converti tu stesso dalla sorgente FP8 — un unico comando riprendibile che
+non richiede mai i 756 GB completi su disco contemporaneamente:
+
+```bash
+cd c && ./setup.sh # verifica gcc/OpenMP, compila, autotest
+./coli convert --model /nvme/glm52_i4 # scarica e converti shard per shard (python, una tantum)
+```
+
+### 2. Esegui
+
+```bash
+COLI_MODEL=/nvme/glm52_i4 ./coli chat # budget RAM, cache e MTP rilevati automaticamente
+COLI_MODEL=/nvme/glm52_i4 ./coli plan # mostra il piazzamento pianificato VRAM/RAM/disco
+COLI_MODEL=/nvme/glm52_i4 ./coli doctor # controllo di idoneità (sola lettura)
+./coli web --model /nvme/glm52_i4 # API + dashboard web sulla stessa porta
+./coli serve --model /nvme/glm52_i4 # solo API compatibile OpenAI
+```
+
+Il motore a runtime è puro C — python si usa solo per il convertitore (una tantum)
+e per il gateway API opzionale.
+
+### 3. Approfondisci
+
+| argomento | documento |
+|---|---|
+| Benchmark, dati dalla comunità, misurazioni di qualità | [docs/benchmarks.md](docs/benchmarks.md) |
+| Parametri di tuning, policy, cache che impara, prefetch | [docs/tuning.md](docs/tuning.md) |
+| Build nativa su Windows 11 (con CUDA DLL) | [docs/windows.md](docs/windows.md) |
+| Backend CUDA, livello expert in VRAM, residenza completa | [docs/cuda.md](docs/cuda.md) |
+| Backend Metal per Apple Silicon | [docs/metal.md](docs/metal.md) |
+| API compatibile OpenAI, KV slot, dashboard web | [docs/api.md](docs/api.md) |
+| Draft forzati da grammatica (output strutturato) | [docs/grammar-draft.md](docs/grammar-draft.md) |
+| Inventario delle variabili d'ambiente | [docs/ENVIRONMENT.md](docs/ENVIRONMENT.md) |
+
+## Sostenere il progetto
+
+colibrì è nato come progetto di una sola persona su un portatile con 12 core
+e 25 GB di RAM; oggi i suoi numeri arrivano da una comunità di macchine reali.
+Se ti è utile:
+
+- ⭐ metti una stella al repository e condividilo;
+- 🐛 apri issue con i numeri di benchmark del tuo hardware — i datapoint
+ fanno avanzare questo progetto più di qualsiasi altra cosa;
+- 💬 contattaci via GitHub issues per sponsorizzare lo sviluppo o donare hardware.
+
+## Struttura del repository
+
+```
+Makefile punto d'ingresso root per build/check
+c/
+├── colibri.c motore principale
+├── quant.h kernel matmul quantizzati (SIMD multi-architettura)
+├── sample.h campionamento, RNG, set di stop
+├── kv_persist.h persistenza KV su disco (.coli_kv)
+├── telemetry.h protocollo dashboard, statistiche, usage
+├── st.h, tok.h, json.h header di runtime
+├── backend_cuda.* livello CUDA opzionale
+├── Makefile build e check locali
+├── coli CLI utente
+├── openai_server.py gateway HTTP compatibile OpenAI
+├── setup.sh setup locale in un solo comando
+├── tools/ conversione offline, fixture e benchmark
+├── scripts/ helper per conversioni lunghe
+└── tests/ test C e Python senza dipendenze
+web/ UI browser (puro client API OpenAI)
+desktop/ shell desktop Tauri v2 che racchiude la web UI
+docs/ documentazione di riferimento, esperimenti, media
+```
+
+Il percorso a runtime resta intenzionalmente piatto e leggibile: `colibri.c`
+più i suoi header. Dalla radice del repository, `make`, `make check` e
+`make clean` delegano al Makefile del motore.
+
+## Perché "colibrì"
+
+Il colibrì pesa pochi grammi, sta sospeso nel vuoto e visita un migliaio di
+fiori al giorno. Questo motore tiene in vita un gigante da 744 miliardi di
+parametri con le razioni di un colibrì: 25 GB di RAM, dodici core CPU e
+tanta pazienza col disco.
+
+Il nome è rimasto in italiano perché questa è la lingua in cui è stato scritto
+il primo prototipo — i commenti nel codice lo testimoniano ancora.
+
+## Licenza
+
+Apache 2.0. I pesi di GLM-5.2 sono rilasciati da Z.ai sotto licenza MIT.
diff --git a/README.md b/README.md
index f9cf3f0..d6f9bcb 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
- English · 繁體中文
+ English · 简体中文 · 繁體中文 · Italiano
**Tiny engine, immense model.** Run **GLM-5.2 (744B-parameter MoE)** on a consumer machine with ~25 GB of RAM — in pure C, with zero dependencies, by streaming experts from disk.
diff --git a/README.zh-CN.md b/README.zh-CN.md
new file mode 100644
index 0000000..352de33
--- /dev/null
+++ b/README.zh-CN.md
@@ -0,0 +1,237 @@
+
+
+
+
+
+ English · 简体中文 · 繁體中文 · Italiano
+
+
+**小巧引擎,庞大模型。**只需约 25 GB 内存,就能在消费级电脑上运行 **GLM-5.2(744B 参数的 MoE)**——以零依赖的纯 C 实现,从磁盘流式加载专家。
+
+Colibrì 是一套轻量、保持模型质量的 MoE 运行时,将 VRAM、RAM
+与存储设备视为统一管理的内存层级。高速内存不足可能降低速度,
+但默认策略**绝不会在未告知的情况下改变模型精度或路由语义**。
+
+```
+$ ./coli chat
+ 🐦 colibrì v1.0 — GLM-5.2 · 744B MoE · int4 · streaming CPU
+ ✓ ready in 32s · resident 9.9 GB
+ › ciao!
+ ◆ Ciao! 😊 Come posso aiutarti oggi?
+```
+
+## 实际运行效果
+
+
+
+
+网页仪表盘(./coli web):744B 模型达到 4 tok/s、TTFT 1.6 秒、磁盘读取 0 ——
+在 6× RTX 5090 上让所有专家常驻,并实时显示 token 指标、每轮耗时明细、
+VRAM/RAM/磁盘层级条,以及角落的实时迷你大脑。
+
+
+
+
+大脑(Brain) 页面:将全部 19,456 个专家呈现为活的皮层——颜色代表存储层级,
+亮度代表路由热度,每轮被路由到的专家都会闪白。将光标停在专家上,即可查看其
+实测主题亲和度 。
+
+
+
+
+图谱(Atlas) 页面:将实测专家图谱
+呈现为 3D 星系——共 13,260 个已分析专家,其中 1,041 个可复现的专门专家会按主题聚集
+(诗歌、法律、中文、SQL……)。位置取自实测路由亲和度,而非学习出的嵌入向量。拖拽即可旋转。
+
+## 核心概念
+
+744B 的专家混合(Mixture-of-Experts)模型,每个 token 只会激活约 40B 参数——
+其中每个 token 之间会变动的只有约 11 GB(被路由到的专家):
+
+
+
+
+
+所以模型不必完整**装进**高速内存,而是需要正确**放置**:
+
+- **稠密部分**(注意力、共享专家、嵌入——约 17B 参数)以 int4
+ **常驻 RAM**(约 9.9 GB);
+- **19,456 个路由专家**(75 个 MoE 层 × 256,加上 MTP head;每个在 int4 下约 19 MB)
+ **存放在磁盘**(约 370 GB),并**按需流式加载**,配合逐层 LRU 缓存、
+ 会学习的热门专家固定存储区,以及可选的 VRAM 层级。
+
+引擎是一个 C 主文件(`c/colibri.c`)加上若干头文件。不需要 BLAS,
+运行时不需要 Python,也不需要 GPU。
+
+## 工作原理
+
+### 每个 token 的处理路径
+
+
+
+
+
+每个 token 的每一层都会经过相同的五个步骤。设计目标是让
+**放置只决定速度**——无论专家是从 VRAM 还是磁盘响应,路由器的决策与权重精度都完全相同。
+
+### 统一内存层级,取代单一内存门槛
+
+
+
+
+
+同一套引擎覆盖完整硬件范围:在 25 GB 笔记本上,一切都从磁盘流式加载
+(慢,但结果正确);在大内存主机上,则可让整组专家常驻
+(`CUDA_EXPERT_GB=auto PIN_GB=all`),让磁盘完全退出解码路径。
+两端之间有一层**学习型缓存**:引擎会记录*你的*工作负载路由到哪些专家
+(`.coli_usage`,每轮更新),并自动固定最热门的专家——colibrì 确实会越用越快。
+在多路主机上,`COLI_NUMA=1` 会将常驻权重交错分配到各内存控制器
+([#82](https://github.com/JustVugg/colibri/issues/82))。
+
+### 绝不为同一次磁盘读取等待两遍
+
+缓存未命中的代价很高,因此引擎大部分的巧思都用来避免或重叠这些读取:
+每个专家的三个矩阵相邻存储,并以一次 `pread` 读取;有界异步 I/O 池
+(`PIPE=1`,默认启用)会在常驻专家计算时加载缺失的专家;批量位置只读取每个
+不重复专家一次(**批量并集**);路由前瞻线程(`PILOT=1`)则预取下一层专家——
+实测显示,路由结果提前一层时有 **71.6% 的可预测性**。
+在 GPU 上,常驻管线(`COLI_CUDA_PIPE=2`)让残差流跨层保留在设备端,
+使 CPU 专家循环不中断;在 Apple Silicon 上,实验性的
+[Metal 后端](docs/metal.md)会用统一内存 GPU 执行批量专家运算。
+
+### 忠实模型,压缩状态
+
+前向传播已通过 `transformers` oracle 验证为**逐 token 完全一致**
+(teacher-forcing 32/32)。MLA 注意力存储压缩后的 KV 状态——每个 token 为 576 个
+浮点数,而非 32,768 个(**缩小 57×**)——并跨重启持久保存
+(`.coli_kv`):对话可暖启恢复,不需重新 prefill,结果与不中断的会话
+逐字节相同。DSA 稀疏注意力(GLM-5.2 的 lightning indexer)已忠实实现,
+并通过强制选取所有 key,验证可精确复现稠密注意力。
+
+### 诚实的推测解码
+
+GLM-5.2 原生 MTP head 会起草 token,再由主模型以一次批量前向传播验证——
+条件合适时每次 forward 可产生 2.2–2.8 个 token。两条来之不易的规则已成为默认值:
+MTP head 必须是 **int8**(int4 head 的接受率会崩塌到 0–4%,见
+[#8](https://github.com/JustVugg/colibri/issues/8)),且草稿与验证必须计算
+**相同函数**——`SPEC_PIN=1` 会把两者固定在同一 kernel family
+(完整取证过程见 [#163](https://github.com/JustVugg/colibri/issues/163))。
+语法强制草稿([`GRAMMAR=file.gbnf`](docs/grammar-draft.md))可在受限 JSON 输出中,
+以近乎免费的代价提高接受率。推测解码是否带来净收益取决于缓存热度——请实测,
+若不划算就使用 `DRAFT=0`。
+
+## 实际成果
+
+
+
+
+
+同一套引擎、同一个 int4 容器——硬件只会改变专家的存放位置。
+[完整 benchmark 表格](docs/benchmarks.md)中的重点如下:
+
+- **6× RTX 5090,全部常驻:**解码 5.8–6.8 tok/s,TTFT 约 13 秒
+ ([实验记录](docs/experiments/glm52-6x5090-2026-07-12.md));
+- **128 GB、仅使用 CPU 的台式机:**热缓存后约 1.8 tok/s
+ ([#200](https://github.com/JustVugg/colibri/issues/200));
+- **单张 RTX 5070 Ti 的笔记本级主机:**通过 GPU 常驻管线达到 1.07 tok/s
+ ([#273](https://github.com/JustVugg/colibri/issues/273));
+- **25 GB 开发机:**冷启动 0.05–0.1 tok/s——这是项目起步时已证实的下限,
+ 也仍是诚实的基准。
+
+质量来自测量,而非假设:int4 容器的量化损失,以及 scale granularity/rotation
+消融实验,收录于 [docs/benchmarks.md](docs/benchmarks.md#quality-benchmark)、
+[#108](https://github.com/JustVugg/colibri/issues/108) 与
+[#81](https://github.com/JustVugg/colibri/issues/81)。
+
+## 开始使用
+
+### 1. 获取模型
+
+Hugging Face 上已有预转换的 **GLM-5.2 int4** 容器——请务必使用
+**含 int8 MTP head 的版本**:
+
+**https://huggingface.co/mateogrgic/GLM-5.2-colibri-int4-with-int8-mtp**
+
+> ⚠️ 原始镜像使用 int4 MTP head → 草稿接受率为 0%
+>([#8](https://github.com/JustVugg/colibri/issues/8))。请检查你的版本:
+> `ls -l /out-mtp-*`——正确的 int8 大小为 `3527131672 / 5366238584 / 1065950496`。
+
+你也可以自行从 FP8 源转换——只需一条可断点续传的命令,且任何时候都不需要
+在磁盘上同时存放完整的 756 GB:
+
+```bash
+cd c && ./setup.sh # 检查 gcc/OpenMP、构建并运行自测
+./coli convert --model /nvme/glm52_i4 # 逐 shard 下载并转换(仅此一次需要 python)
+```
+
+### 2. 运行
+
+```bash
+COLI_MODEL=/nvme/glm52_i4 ./coli chat # 自动检测 RAM 预算、缓存与 MTP
+COLI_MODEL=/nvme/glm52_i4 ./coli plan # 查看规划的 VRAM/RAM/磁盘配置
+COLI_MODEL=/nvme/glm52_i4 ./coli doctor # 只读就绪检查
+./coli web --model /nvme/glm52_i4 # 在同一端口提供 API 与网页仪表盘
+./coli serve --model /nvme/glm52_i4 # 仅提供 OpenAI 兼容 API
+```
+
+引擎运行时是纯 C——python 只供一次性转换工具与可选的 API gateway 使用。
+
+### 3. 深入了解
+
+| 主题 | 文档 |
+|---|---|
+| Benchmark、社区实测数据、质量测量 | [docs/benchmarks.md](docs/benchmarks.md) |
+| 调优选项、策略、学习型缓存、预取 | [docs/tuning.md](docs/tuning.md) |
+| Windows 11 原生构建(含 CUDA DLL) | [docs/windows.md](docs/windows.md) |
+| CUDA 后端、VRAM 专家层级、全部常驻 | [docs/cuda.md](docs/cuda.md) |
+| Apple Silicon Metal 后端 | [docs/metal.md](docs/metal.md) |
+| OpenAI 兼容 API、KV slots、网页仪表盘 | [docs/api.md](docs/api.md) |
+| 语法强制草稿(结构化输出) | [docs/grammar-draft.md](docs/grammar-draft.md) |
+| 环境变量完整清单 | [docs/ENVIRONMENT.md](docs/ENVIRONMENT.md) |
+
+## 支持项目
+
+colibrì 最初由一人使用 12 核心、25 GB RAM 的笔记本开发;
+如今它的数据来自社区中各种真实机器。如果这个项目对你有用:
+
+- ⭐ 为仓库加星并分享;
+- 🐛 以 issue 提交你的硬件 benchmark 数据——实测数据比任何其他事都更能推动项目;
+- 💬 若想赞助开发或捐赠硬件,请通过 GitHub issues 联系。
+
+## 仓库结构
+
+```
+Makefile 根目录构建/检查入口
+c/
+├── colibri.c 引擎主文件
+├── quant.h 量化 matmul 内核(SIMD 多架构)
+├── sample.h 采样与 stop-set 管理
+├── kv_persist.h .coli_kv 磁盘持久化
+├── telemetry.h 仪表盘协议、统计与用量持久化
+├── st.h, tok.h, json.h 运行时头文件
+├── backend_cuda.* 可选的 CUDA 层级
+├── Makefile 构建与本地检查
+├── coli 用户界面 CLI
+├── openai_server.py OpenAI 兼容 HTTP gateway
+├── setup.sh 一条命令完成本地设置
+├── tools/ 离线转换、fixtures 与 benchmarks
+├── scripts/ 长时间转换辅助工具
+└── tests/ 零依赖的 C 与 Python 测试
+web/ 浏览器 UI(纯 OpenAI API client)
+desktop/ 封装网页 UI 的 Tauri v2 桌面 shell
+docs/ 参考文档、实验与媒体文件
+```
+
+运行时路径刻意保持扁平、易读:`colibri.c` 加上若干头文件。
+在仓库根目录执行 `make`、`make check` 与 `make clean`,
+都会转发给引擎的 Makefile。
+
+## 为什么叫"colibrì"
+
+蜂鸟只有几克重,能在原地悬停,并在一天内造访上千朵花。
+这套引擎只用蜂鸟般的配给,就能让 744B 参数的巨人运转:
+25 GB RAM、十二个 CPU 核心,以及对磁盘的大量耐心。
+
+## 许可证
+
+Apache 2.0。GLM-5.2 权重由 Z.ai 以 MIT 许可发布。
diff --git a/README.zh-TW.md b/README.zh-TW.md
index 1662617..2576eba 100644
--- a/README.zh-TW.md
+++ b/README.zh-TW.md
@@ -3,7 +3,7 @@
- English · 繁體中文
+ English · 简体中文 · 繁體中文 · Italiano
**小巧引擎,龐大模型。**只要約 25 GB 記憶體,就能在消費級電腦上執行 **GLM-5.2(744B 參數的 MoE)**——以零相依套件的純 C 實作,從硬碟串流載入專家。
@@ -60,7 +60,7 @@ VRAM/RAM/硬碟層級長條,以及角落的即時迷你大腦。
**存放在硬碟**(約 370 GB),並**隨需串流載入**,搭配逐層 LRU 快取、
會學習的熱門專家固定儲存區,以及選用的 VRAM 層級。
-引擎由單一 C 檔(`c/glm.c`)與少量標頭檔組成。不需要 BLAS,
+引擎由主 C 檔(`c/colibri.c`)與多個標頭檔模組組成。不需要 BLAS,
執行階段不需要 Python,也不需要 GPU。
## 運作方式
@@ -203,7 +203,11 @@ colibrì 最初是由一人使用 12 核心、25 GB RAM 的筆電開發;
```
Makefile 根目錄建置/檢查入口
c/
-├── glm.c 單檔 GLM 引擎
+├── colibri.c GLM 引擎主檔
+├── quant.h 量化 matmul kernel
+├── sample.h 取樣與 stop-set
+├── kv_persist.h .coli_kv 磁碟持久化
+├── telemetry.h 儀表板協定、統計
├── st.h, tok.h, json.h 執行階段標頭檔
├── backend_cuda.* 選用的 CUDA 層級
├── Makefile 建置與本機檢查
@@ -218,7 +222,7 @@ desktop/ 包裝網頁 UI 的 Tauri v2 桌面 shell
docs/ 參考文件、實驗與媒體檔
```
-執行階段路徑刻意維持扁平、易讀:`glm.c` 加上少量標頭檔。
+執行階段路徑刻意維持扁平、易讀:`colibri.c` 加上模組化標頭檔。
在儲存庫根目錄執行 `make`、`make check` 與 `make clean`,
都會轉交給引擎的 Makefile。
From e486574442724ff4ba79808a1ede254368b0638a Mon Sep 17 00:00:00 2001
From: ZacharyZcR
Date: Sun, 19 Jul 2026 04:22:46 +0800
Subject: [PATCH 19/22] =?UTF-8?q?web:=20i18n=20support=20=E2=80=94=20Engli?=
=?UTF-8?q?sh,=20=E7=AE=80=E4=BD=93=E4=B8=AD=E6=96=87,=20=E7=B9=81?=
=?UTF-8?q?=E9=AB=94=E4=B8=AD=E6=96=87,=20Italiano?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Lightweight i18n without react-i18next: a LocaleProvider context +
useLocale() hook with {{var}} interpolation, auto-detect from
navigator.language, persisted to localStorage.
98 translation keys across 4 locale files (en/zh-CN/zh-TW/it).
All user-visible strings in App/Brain/Profiling/ErrorBoundary are
now t() calls. Language switcher added to the sidebar footer.
Build clean (tsc + vite), 18 tests pass.
---
web/src/App.tsx | 121 +++++++++++++++++++-----------------
web/src/Brain.tsx | 43 +++++++------
web/src/ErrorBoundary.tsx | 21 +++++--
web/src/Profiling.tsx | 58 ++++++++----------
web/src/i18n/en.ts | 125 ++++++++++++++++++++++++++++++++++++++
web/src/i18n/index.ts | 78 ++++++++++++++++++++++++
web/src/i18n/it.ts | 113 ++++++++++++++++++++++++++++++++++
web/src/i18n/zh-CN.ts | 113 ++++++++++++++++++++++++++++++++++
web/src/i18n/zh-TW.ts | 113 ++++++++++++++++++++++++++++++++++
web/src/index.css | 4 +-
web/src/main.tsx | 5 +-
11 files changed, 675 insertions(+), 119 deletions(-)
create mode 100644 web/src/i18n/en.ts
create mode 100644 web/src/i18n/index.ts
create mode 100644 web/src/i18n/it.ts
create mode 100644 web/src/i18n/zh-CN.ts
create mode 100644 web/src/i18n/zh-TW.ts
diff --git a/web/src/App.tsx b/web/src/App.tsx
index a33cc88..39c9f3b 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -9,6 +9,7 @@ import {
Database,
Feather,
Gauge,
+ Globe,
HardDrive,
KeyRound,
Layers,
@@ -34,6 +35,7 @@ import { Brain } from "./Brain"
import { Profiling } from "./Profiling"
import { persistPublicSettings, stored } from "@/lib/storage"
import { cn } from "@/lib/utils"
+import { useLocale } from "./i18n"
const message = (role: ChatMessage["role"], content: string): ChatMessage => {
let id: string
@@ -42,15 +44,12 @@ const message = (role: ChatMessage["role"], content: string): ChatMessage => {
}
export default function App() {
- // When the page is served by the engine itself (coli web), same-origin is the
- // right default: no CORS, no manual endpoint editing. The Vite dev server
- // (port 5173) keeps the classic default.
+ const { t, locale, setLocale, locales } = useLocale()
+
const servedByEngine = typeof window !== "undefined" && window.location.port !== "5173" && window.location.protocol.startsWith("http")
const defaultBase = servedByEngine ? `${window.location.origin}/v1` : "http://127.0.0.1:8000/v1"
const [baseUrl, setBaseUrl] = useState(() => {
const saved = stored(localStorage, "colibri.baseUrl", defaultBase)
- // migrate: a stored FACTORY default pointing at another origin would trip CORS
- // when the page is engine-served — upgrade it to same-origin once.
if (servedByEngine && saved === "http://127.0.0.1:8000/v1" && defaultBase !== saved) return defaultBase
return saved
})
@@ -120,7 +119,7 @@ export default function App() {
const result = await getHealth(baseUrl, apiKey)
if (!disposed) { setHealth(result); setHealthError("") }
} catch (cause) {
- if (!disposed) setHealthError(cause instanceof Error ? cause.message : "Runtime metrics unavailable")
+ if (!disposed) setHealthError(cause instanceof Error ? cause.message : "status.runtimeUnavailable")
}
}
const timer = window.setInterval(() => void poll(), 5000)
@@ -155,13 +154,13 @@ export default function App() {
} catch (cause) {
if (!controller.signal.aborted) {
setHealth(null)
- setHealthError(cause instanceof Error ? cause.message : "Runtime metrics unavailable")
+ setHealthError(cause instanceof Error ? cause.message : "status.runtimeUnavailable")
}
}
} catch (cause) {
if (controller.signal.aborted) return
setConnected(false)
- setError(cause instanceof Error ? cause.message : "Could not reach the server.")
+ setError(cause instanceof Error ? cause.message : "status.serverError")
} finally {
if (probeRef.current === controller) { probeRef.current = null; setConnecting(false) }
}
@@ -227,7 +226,7 @@ export default function App() {
if (controller.signal.aborted) {
updateMessages((current) => current.filter((item) => item.id !== assistant.id || item.content))
} else {
- setError(cause instanceof Error ? cause.message : "Generation failed.")
+ setError(cause instanceof Error ? cause.message : "status.generationFailed")
updateMessages((current) => current.filter((item) => item.id !== assistant.id || item.content))
}
} finally {
@@ -241,22 +240,22 @@ export default function App() {
-
colibrì local giant, tiny footprint
+
colibrì {t("brand.tagline")}
-
+
{health?.hwinfo ?
{health.hwinfo.cpu ?
{health.hwinfo.cpu}
: null}
{health.hwinfo.gpus > 0 ?
{health.hwinfo.gpus}× GPU{health.hwinfo.vram_total_gb.toFixed(0)} GB VRAM
: null}
@@ -265,66 +264,74 @@ export default function App() {
: null}
{health?.scheduler ? <>
-
Active {active} / {capacity}
-
Queued {health.scheduler.queued} / {health.scheduler.max_queue}
-
Completed {health.scheduler.completed}
-
Failures {failures}
+
{t("dashboard.active")} {active} / {capacity}
+
{t("dashboard.queued")} {health.scheduler.queued} / {health.scheduler.max_queue}
+
{t("dashboard.completed")} {health.scheduler.completed}
+
{t("dashboard.failures")} {failures}
{health.tiers ? (() => {
- const t = health.tiers
- const total = Math.max(t.vram + t.ram + t.disk, 1)
+ const ti = health.tiers
+ const total = Math.max(ti.vram + ti.ram + ti.disk, 1)
return
-
-
-
-
+
+
+
+
- VRAM {t.vram.toLocaleString()} {t.vram_gb.toFixed(1)} GB
- RAM {t.ram.toLocaleString()} {t.ram_gb.toFixed(1)} GB
- Disk {t.disk.toLocaleString()}
+ {t("tier.vram")} {ti.vram.toLocaleString()} {ti.vram_gb.toFixed(1)} GB
+ {t("tier.ram")} {ti.ram.toLocaleString()} {ti.ram_gb.toFixed(1)} GB
+ {t("tier.disk")} {ti.disk.toLocaleString()}
})() : null}
{totalTokens.prompt + totalTokens.completion > 0 ?
- Session: {totalTokens.prompt.toLocaleString()} prompt + {totalTokens.completion.toLocaleString()} completion
+ {t("dashboard.session")} {totalTokens.prompt.toLocaleString()} {t("dashboard.prompt")} + {totalTokens.completion.toLocaleString()} {t("dashboard.completion")}
: null}
-
Scheduler online {kvSlots} KV
- > :
{connected ? (healthError || "Runtime metrics unavailable") : "Probe the server to inspect runtime state."}
}
+
{t("sidebar.schedulerOnline")} {kvSlots} KV
+ > :
{connected ? (healthError ? t(healthError) : t("status.runtimeUnavailable")) : t("sidebar.runtimeProbe")}
}
-
OpenAI-compatible transport
+
+
{t("sidebar.transport")}
+
+
+ setLocale(e.target.value)}>
+ {locales.map((l) => {l.label} )}
+
+
+
- ACTIVE MODEL {model}
+ {t("topbar.activeModel")} {model}
- setView("chat")}> Chat
- setView("brain")}> Brain
- setView("profiling")}> Profiling
+ setView("chat")}> {t("nav.chat")}
+ setView("brain")}> {t("nav.brain")}
+ setView("profiling")}> {t("nav.profiling")}
- {loading && tokenCount > 0 ? {tokenCount} tokens : null}
- {!loading && tokPerSec != null ? {tokPerSec.toFixed(1)} tok/s : null}
+ {loading && tokenCount > 0 ? {t("topbar.tokens", { n: tokenCount })} : null}
+ {!loading && tokPerSec != null ? {t("topbar.tokPerSec", { n: tokPerSec.toFixed(1) })} : null}
{!loading && ttft != null ? TTFT {(ttft/1000).toFixed(1)}s : null}
{!loading && lastRun?.usage ? {lastRun.usage.prompt_tokens}→{lastRun.usage.completion_tokens} : null}
{lastRun?.queueWaitMs != null ? queue {Math.round(lastRun.queueWaitMs)}ms : null}
- slot {cacheSlot + 1}
- { updateMessages([]); setTokPerSec(null); setTtft(null); setTokenCount(0); setTotalTokens({prompt:0,completion:0}) }} disabled={!messages.length || loading}> Clear
+ {t("topbar.slot", { n: cacheSlot + 1 })}
+ { updateMessages([]); setTokPerSec(null); setTtft(null); setTokenCount(0); setTotalTokens({prompt:0,completion:0}) }} disabled={!messages.length || loading}> {t("topbar.clear")}
@@ -335,11 +342,11 @@ export default function App() {
{!messages.length ? (
-
COLIBRÌ ENGINE
-
Ask the giant.Keep the machine yours.
-
Connect to a local colibrì server and stream responses directly from your hardware. Nothing leaves the endpoint you choose.
+
{t("hero.title")}
+
{t("hero.subtitle")}{t("hero.tagline")}
+
{t("hero.description")}
- {["Explain how expert routing works", "Write a small C benchmark", "Compare RAM and VRAM caching"].map((item) =>
setDraft(item)}>{item} )}
+ {[t("prompts.routing"), t("prompts.benchmark"), t("prompts.caching")].map((item) =>
setDraft(item)}>{item} )}
) : (
@@ -347,7 +354,7 @@ export default function App() {
{messages.map((item) => (
{item.role === "user" ? "Y" : }
- {item.role === "user" ? "You" : "colibrì"}
{item.content || }
+ {item.role === "user" ? t("chat.you") : t("chat.colibri")}
{item.content || }
))}
@@ -356,10 +363,10 @@ export default function App() {
- {error &&
{error}
}
+ {error &&
{t(error)}
}
>}
diff --git a/web/src/Brain.tsx b/web/src/Brain.tsx
index 6811238..6b4f70e 100644
--- a/web/src/Brain.tsx
+++ b/web/src/Brain.tsx
@@ -2,27 +2,26 @@ import { useEffect, useRef, useState } from "react"
import { BrainCircuit, Flame, Layers } from "lucide-react"
import { endpoint } from "@/lib/api"
+import { useLocale } from "./i18n"
interface ExpertMap { rows: number; cols: number; map: string; hits: string; seq: number }
interface AtlasEntry { affinity: Record; entropy: number; top: string; label: string }
-const TIER_NAME = ["Disk", "RAM", "VRAM"]
+const TIER_KEYS = ["tier.disk", "tier.ram", "tier.vram"] as const
const TIER_RGB: [number, number, number][] = [[58, 71, 80], [90, 155, 216], [78, 214, 165]]
-/* Layer-depth heuristic: what this region of the network tends to specialise in.
- * Honest framing — these are the depth roles observed across MoE interpretability
- * work, not per-expert ground truth (that needs co-activation analysis, #119). */
-function depthRole(row: number, rows: number, isMtp: boolean): string {
- if (isMtp) return "MTP head — drafts the next token for speculative decoding"
+function depthRoleKey(row: number, rows: number, isMtp: boolean): string {
+ if (isMtp) return "brain.mtp"
const f = row / Math.max(rows - 1, 1)
- if (f < 0.2) return "early layers — surface features: tokens, spelling, local syntax"
- if (f < 0.45) return "lower-middle — phrase structure, word relations, simple facts"
- if (f < 0.7) return "upper-middle — semantics, long-range context, reasoning steps"
- if (f < 0.9) return "late layers — planning the answer, style, coherence"
- return "final layers — output shaping: picks the actual next-token distribution"
+ if (f < 0.2) return "brain.early"
+ if (f < 0.45) return "brain.lowerMiddle"
+ if (f < 0.7) return "brain.upperMiddle"
+ if (f < 0.9) return "brain.late"
+ return "brain.final"
}
export function Brain({ baseUrl, apiKey, connected }: { baseUrl: string; apiKey: string; connected: boolean }) {
+ const { t } = useLocale()
const canvasRef = useRef(null)
const wrapRef = useRef(null)
const [wrapSize, setWrapSize] = useState({ w: 1200, h: 700 })
@@ -142,18 +141,18 @@ export function Brain({ baseUrl, apiKey, connected }: { baseUrl: string; apiKey:
return (
-
Expert Cortex — {data ? `${data.rows} layers × ${data.cols} experts` : "waiting for engine"}
+
{t("brain.title")} — {data ? t("brain.layers", { rows: data.rows, cols: data.cols }) : t("brain.waiting")}
- VRAM {totals[2].toLocaleString()}
- RAM {totals[1].toLocaleString()}
- Disk {totals[0].toLocaleString()}
- brightness = routing heat
- ⚡ white flash = routed this turn
+ {t("tier.vram")} {totals[2].toLocaleString()}
+ {t("tier.ram")} {totals[1].toLocaleString()}
+ {t("tier.disk")} {totals[0].toLocaleString()}
+ {t("brain.brightnessHint")}
+ {t("brain.flashHint")}
setTip(null)} />
- {!connected && Connect to the engine to see the cortex.
}
+ {!connected && {t("brain.connectHint")}
}
{tip && data && (() => {
const isMtp = tip.row === data.rows - 1
@@ -162,16 +161,16 @@ export function Brain({ baseUrl, apiKey, connected }: { baseUrl: string; apiKey:
return (
Layer {realLayer}{isMtp ? " (MTP)" : ""} · Expert {tip.col}
-
Tier: {TIER_NAME[tip.tier]}
-
Heat: {tip.heat === 0 ? "never routed" : `~2^${tip.heat} selections`}
+
Tier: {t(TIER_KEYS[tip.tier])}
+
Heat: {tip.heat === 0 ? t("brain.neverRouted") : t("brain.selections", { heat: tip.heat })}
{entry ? <>
- {entry.label.startsWith("specialist") ? `⭐ Specialist: ${entry.top}` : "Generalist"}
+ {entry.label.startsWith("specialist") ? t("brain.specialist", { top: entry.top }) : t("brain.generalist")}
(entropy {entry.entropy})
{Object.entries(entry.affinity).sort((a, b) => b[1] - a[1]).slice(0, 3)
.map(([c, p]) => `${c} ${Math.round(p * 100)}%`).join(" · ")}
- > :
{depthRole(tip.row, data.rows, isMtp)}
}
+ > :
{t(depthRoleKey(tip.row, data.rows, isMtp))}
}
)
})()}
diff --git a/web/src/ErrorBoundary.tsx b/web/src/ErrorBoundary.tsx
index f88b8a9..1538bf9 100644
--- a/web/src/ErrorBoundary.tsx
+++ b/web/src/ErrorBoundary.tsx
@@ -1,4 +1,18 @@
import { Component, type ReactNode } from "react"
+import { useLocale } from "./i18n"
+
+function ErrorFallback({ error, onRetry }: { error: Error; onRetry: () => void }) {
+ const { t } = useLocale()
+ return (
+
+
{t("error.title")}
+
{t("error.hint")}
+
{String(error)}
+
{t("error.retry")}
+
+ )
+}
+
interface State { error: Error | null; stack: string }
export class ErrorBoundary extends Component<{ children: ReactNode }, State> {
state: State = { error: null, stack: "" }
@@ -9,11 +23,6 @@ export class ErrorBoundary extends Component<{ children: ReactNode }, State> {
}
render() {
if (!this.state.error) return this.props.children
- return
-
colibrì UI hit an error
-
The engine is unaffected. Try refreshing.
-
{String(this.state.error)}
-
this.setState({ error: null, stack: "" })} style={{ marginTop: "1rem", padding: "0.5rem 1rem", background: "#1f2937", color: "#e5e7eb", border: "1px solid #374151", borderRadius: 8, cursor: "pointer" }}>Retry
-
+ return
this.setState({ error: null, stack: "" })} />
}
}
diff --git a/web/src/Profiling.tsx b/web/src/Profiling.tsx
index 9fdaebb..06aa6ca 100644
--- a/web/src/Profiling.tsx
+++ b/web/src/Profiling.tsx
@@ -2,19 +2,14 @@ import { useEffect, useState } from "react"
import { Activity, Gauge, HardDrive, Timer } from "lucide-react"
import { getProfile, type ProfileTurn } from "@/lib/api"
+import { useLocale } from "./i18n"
-/* Wall-time phases stacked per turn. The order is the palette's CVD-safe slot
- * order (validated as a set on this surface) — identity never leans on colour
- * alone: segments keep 2px gaps, the legend is always shown and the table
- * carries the exact numbers. Disk *service* time is reported separately: it
- * runs on I/O threads overlapped with compute, so only the stall the compute
- * thread actually felt (I/O wait) belongs inside the wall-time stack. */
const PHASES = [
- { key: "expert_wait_s", name: "I/O wait", color: "#3987e5" },
- { key: "expert_matmul_s", name: "Expert matmul", color: "#199e70" },
- { key: "attention_s", name: "Attention", color: "#c98500" },
- { key: "lm_head_s", name: "LM head", color: "#008300" },
- { key: "other_s", name: "Other", color: "#9085e9" },
+ { key: "expert_wait_s", i18n: "profile.ioWait", color: "#3987e5" },
+ { key: "expert_matmul_s", i18n: "profile.expertMatmul", color: "#199e70" },
+ { key: "attention_s", i18n: "profile.attention", color: "#c98500" },
+ { key: "lm_head_s", i18n: "profile.lmHead", color: "#008300" },
+ { key: "other_s", i18n: "profile.other", color: "#9085e9" },
] as const
interface Turn extends ProfileTurn { other_s: number; toks: number }
@@ -28,8 +23,9 @@ const derive = (turn: ProfileTurn): Turn => ({
const seconds = (value: number) => (value >= 10 ? value.toFixed(1) : value.toFixed(2)) + "s"
function ShareBar({ label, turns }: { label: string; turns: Turn[] }) {
+ const { t } = useLocale()
const total = turns.reduce((sum, turn) => sum + turn.wall_s, 0)
- const parts = PHASES.map((phase) => ({ ...phase, value: turns.reduce((sum, turn) => sum + turn[phase.key], 0) }))
+ const parts = PHASES.map((phase) => ({ ...phase, name: t(phase.i18n), value: turns.reduce((sum, turn) => sum + turn[phase.key], 0) }))
return (
{label} {seconds(total)}
@@ -47,9 +43,7 @@ function ShareBar({ label, turns }: { label: string; turns: Turn[] }) {
)
}
-/* Column chart over the recent turns; oldest on the left. Stacked mode draws the
- * wall-time composition, plain mode a single series (no legend — the title names it). */
-function TurnColumns({ turns, stacked, height, format }: { turns: Turn[]; stacked: boolean; height: number; format: (turn: Turn) => string }) {
+function TurnColumns({ turns, stacked, height, format, footLabel, footLabelOne }: { turns: Turn[]; stacked: boolean; height: number; format: (turn: Turn) => string; footLabel: string; footLabelOne: string }) {
const [hover, setHover] = useState
(null)
const peak = Math.max(...turns.map((turn) => (stacked ? turn.wall_s : turn.toks)), 1e-9)
const gap = 2
@@ -71,11 +65,10 @@ function TurnColumns({ turns, stacked, height, format }: { turns: Turn[]; stacke
return h > 0.1 ? : null
})
})}
- {/* hit targets bigger than the marks */}
{turns.map((_, index) => setHover(index)} />)}
- {turns.length > 1 ? `${turns.length} turns · oldest → newest` : "1 turn"}
+ {turns.length > 1 ? footLabel : footLabelOne}
{hover !== null && turns[hover] ? format(turns[hover]) : `peak ${stacked ? seconds(peak) : peak.toFixed(1) + " tok/s"}`}
@@ -83,6 +76,7 @@ function TurnColumns({ turns, stacked, height, format }: { turns: Turn[]; stacke
}
export function Profiling({ baseUrl, apiKey, connected }: { baseUrl: string; apiKey: string; connected: boolean }) {
+ const { t } = useLocale()
const [turns, setTurns] = useState([])
useEffect(() => {
@@ -107,42 +101,42 @@ export function Profiling({ baseUrl, apiKey, connected }: { baseUrl: string; api
return (
-
Profiling — where the engine spends each turn
+
{t("profile.title")}
- {PHASES.map((phase) => {phase.name} )}
+ {PHASES.map((phase) => {t(phase.i18n)} )}
{!latest ? (
-
{connected ? "No profiled turns yet — send a chat message and the breakdown appears here." : "Connect to the engine to collect per-turn timings."}
+
{connected ? t("profile.empty") : t("profile.connectHint")}
) : (
<>
-
Last turn{latest.toks.toFixed(1)} tok/s
-
Wall time{seconds(latest.wall_s)} {latest.prompt_tokens} → {latest.completion_tokens} tokens
-
Batching{latest.forwards > 0 ? (latest.completion_tokens / latest.forwards).toFixed(2) : "—"} tokens / forward
-
Disk service{seconds(latest.expert_disk_s)} overlapped with compute
+
{t("profile.lastTurn")}{latest.toks.toFixed(1)} tok/s
+
{t("profile.wallTime")}{seconds(latest.wall_s)} {latest.prompt_tokens} → {latest.completion_tokens} tokens
+
{t("profile.batching")}{latest.forwards > 0 ? (latest.completion_tokens / latest.forwards).toFixed(2) : "—"} {t("profile.tokensPerForward")}
+
{t("profile.diskService")}{seconds(latest.expert_disk_s)} {t("profile.overlapped")}
-
- {turns.length > 1 ? : null}
+
+ {turns.length > 1 ? : null}
-
Throughput per turn (tok/s)
-
`${turn.toks.toFixed(1)} tok/s · ${turn.completion_tokens} tokens`} />
+ {t("profile.throughputTitle")}
+ `${turn.toks.toFixed(1)} tok/s · ${turn.completion_tokens} tokens`} />
-
Turn wall time by phase (s)
-
`${seconds(turn.wall_s)} · ${PHASES.map((phase) => `${phase.name} ${seconds(turn[phase.key])}`).join(" · ")}`} />
+ {t("profile.phaseTitle")}
+ `${seconds(turn.wall_s)} · ${PHASES.map((phase) => `${t(phase.i18n)} ${seconds(turn[phase.key])}`).join(" · ")}`} />
- Turn Tokens tok/s Wall {PHASES.map((phase) => {phase.name} )}Disk service
+ {t("profile.turnCol")} {t("profile.tokensCol")} tok/s {t("profile.wallCol")} {PHASES.map((phase) => {t(phase.i18n)} )}{t("profile.diskService")}
{recent.slice().reverse().map((turn, index) => (
@@ -156,7 +150,7 @@ export function Profiling({ baseUrl, apiKey, connected }: { baseUrl: string; api
))}
- {diskService > 0 ?
Disk service is time spent reading experts on I/O threads; it overlaps with compute, so only the I/O wait the compute thread felt counts inside the wall-time stack. With multiple KV sessions the shares describe the whole engine over the turn's window.
: null}
+ {diskService > 0 ?
{t("profile.diskNote")}
: null}
>
)}
diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts
new file mode 100644
index 0000000..f3cdd01
--- /dev/null
+++ b/web/src/i18n/en.ts
@@ -0,0 +1,125 @@
+const en: Record
= {
+ // nav
+ "nav.chat": "Chat",
+ "nav.brain": "Brain",
+ "nav.profiling": "Profiling",
+
+ // brand
+ "brand.tagline": "local giant, tiny footprint",
+
+ // sidebar — connection
+ "sidebar.connection": "Connection",
+ "sidebar.endpoint": "API endpoint",
+ "sidebar.apiKey": "API key",
+ "sidebar.apiKeyPlaceholder": "optional",
+ "sidebar.apiKeyHelp": "Kept in memory only · sent to this endpoint",
+ "sidebar.probe": "Probe server",
+ "status.connected": "Engine reachable",
+ "status.notConnected": "Not connected",
+ "status.runtimeUnavailable": "Runtime metrics unavailable",
+ "status.serverError": "Could not reach the server.",
+ "status.generationFailed": "Generation failed.",
+
+ // sidebar — runtime
+ "sidebar.runtime": "Runtime",
+ "sidebar.runtimeProbe": "Probe the server to inspect runtime state.",
+ "sidebar.schedulerOnline": "Scheduler online",
+ "dashboard.active": "Active",
+ "dashboard.queued": "Queued",
+ "dashboard.completed": "Completed",
+ "dashboard.failures": "Failures",
+ "dashboard.session": "Session:",
+ "dashboard.prompt": "prompt",
+ "dashboard.completion": "completion",
+
+ // sidebar — tiers
+ "tier.vram": "VRAM",
+ "tier.ram": "RAM",
+ "tier.disk": "Disk",
+ "tier.ariaLabel": "Experts: {{vram}} VRAM, {{ram}} RAM, {{disk}} disk",
+
+ // sidebar — inference
+ "sidebar.inference": "Inference",
+ "sidebar.model": "Model",
+ "sidebar.kvSession": "KV session",
+ "sidebar.kvSessionHelp": "Isolated context · conversation follows the selected slot",
+ "sidebar.sessionLabel": "Session {{slot}}",
+ "sidebar.temperature": "Temperature",
+ "sidebar.maxTokens": "Max output tokens",
+ "sidebar.reasoning": "Reasoning",
+ "sidebar.transport": "OpenAI-compatible transport",
+
+ // top bar
+ "topbar.activeModel": "ACTIVE MODEL",
+ "topbar.tokens": "{{n}} tokens",
+ "topbar.tokPerSec": "{{n}} tok/s",
+ "topbar.slot": "slot {{n}}",
+ "topbar.clear": "Clear",
+
+ // hero / empty state
+ "hero.title": "COLIBRÌ ENGINE",
+ "hero.subtitle": "Ask the giant.",
+ "hero.tagline": "Keep the machine yours.",
+ "hero.description": "Connect to a local colibrì server and stream responses directly from your hardware. Nothing leaves the endpoint you choose.",
+ "prompts.routing": "Explain how expert routing works",
+ "prompts.benchmark": "Write a small C benchmark",
+ "prompts.caching": "Compare RAM and VRAM caching",
+
+ // chat
+ "chat.you": "You",
+ "chat.colibri": "colibrì",
+ "chat.placeholder": "Message colibrì…",
+ "chat.inputHint": "Enter to send · Shift+Enter for newline",
+ "chat.stop": "Stop generation",
+ "chat.send": "Send message",
+
+ // brain
+ "brain.title": "Expert Cortex",
+ "brain.waiting": "waiting for engine",
+ "brain.layers": "{{rows}} layers × {{cols}} experts",
+ "brain.brightnessHint": "brightness = routing heat",
+ "brain.flashHint": "⚡ white flash = routed this turn",
+ "brain.connectHint": "Connect to the engine to see the cortex.",
+ "brain.neverRouted": "never routed",
+ "brain.selections": "~2^{{heat}} selections",
+ "brain.specialist": "⭐ Specialist: {{top}}",
+ "brain.generalist": "Generalist",
+ "brain.mtp": "MTP head — drafts the next token for speculative decoding",
+ "brain.early": "early layers — surface features: tokens, spelling, local syntax",
+ "brain.lowerMiddle": "lower-middle — phrase structure, word relations, simple facts",
+ "brain.upperMiddle": "upper-middle — semantics, long-range context, reasoning steps",
+ "brain.late": "late layers — planning the answer, style, coherence",
+ "brain.final": "final layers — output shaping: picks the actual next-token distribution",
+
+ // profiling
+ "profile.title": "Profiling — where the engine spends each turn",
+ "profile.ioWait": "I/O wait",
+ "profile.expertMatmul": "Expert matmul",
+ "profile.attention": "Attention",
+ "profile.lmHead": "LM head",
+ "profile.other": "Other",
+ "profile.empty": "No profiled turns yet — send a chat message and the breakdown appears here.",
+ "profile.connectHint": "Connect to the engine to collect per-turn timings.",
+ "profile.lastTurn": "Last turn",
+ "profile.wallTime": "Wall time",
+ "profile.batching": "Batching",
+ "profile.tokensPerForward": "tokens / forward",
+ "profile.diskService": "Disk service",
+ "profile.overlapped": "overlapped with compute",
+ "profile.window": "Window · last {{n}} turns",
+ "profile.throughputTitle": "Throughput per turn (tok/s)",
+ "profile.phaseTitle": "Turn wall time by phase (s)",
+ "profile.turnCol": "Turn",
+ "profile.tokensCol": "Tokens",
+ "profile.wallCol": "Wall",
+ "profile.turnsLabel": "{{n}} turns · oldest → newest",
+ "profile.oneTurn": "1 turn",
+ "profile.diskNote": "Disk service is time spent reading experts on I/O threads; it overlaps with compute, so only the I/O wait the compute thread felt counts inside the wall-time stack. With multiple KV sessions the shares describe the whole engine over the turn's window.",
+
+ // error boundary
+ "error.title": "colibrì UI hit an error",
+ "error.hint": "The engine is unaffected. Try refreshing.",
+ "error.retry": "Retry",
+}
+
+export default en
diff --git a/web/src/i18n/index.ts b/web/src/i18n/index.ts
new file mode 100644
index 0000000..1f9bb74
--- /dev/null
+++ b/web/src/i18n/index.ts
@@ -0,0 +1,78 @@
+import { createContext, useContext, useState, useCallback, useMemo, type ReactNode } from "react"
+import { createElement } from "react"
+import en from "./en"
+import zhCN from "./zh-CN"
+import zhTW from "./zh-TW"
+import it from "./it"
+
+const LOCALES = [
+ { code: "en", label: "English" },
+ { code: "zh-CN", label: "简体中文" },
+ { code: "zh-TW", label: "繁體中文" },
+ { code: "it", label: "Italiano" },
+] as const
+
+const DICTS: Record> = {
+ "en": en,
+ "zh-CN": zhCN,
+ "zh-TW": zhTW,
+ "it": it,
+}
+
+const STORAGE_KEY = "colibri-locale"
+
+function detectLocale(): string {
+ try {
+ const saved = localStorage.getItem(STORAGE_KEY)
+ if (saved && DICTS[saved]) return saved
+ } catch {}
+ const nav = navigator.language || ""
+ if (DICTS[nav]) return nav
+ const prefix = nav.split("-")[0]
+ if (prefix === "zh") return nav.includes("TW") || nav.includes("Hant") ? "zh-TW" : "zh-CN"
+ for (const { code } of LOCALES) if (code.startsWith(prefix)) return code
+ return "en"
+}
+
+function interpolate(template: string, vars?: Record): string {
+ if (!vars) return template
+ return template.replace(/\{\{(\w+)\}\}/g, (_, key) => String(vars[key] ?? `{{${key}}}`))
+}
+
+interface LocaleContext {
+ locale: string
+ setLocale: (code: string) => void
+ t: (key: string, vars?: Record) => string
+ locales: readonly { code: string; label: string }[]
+}
+
+const Ctx = createContext({
+ locale: "en",
+ setLocale: () => {},
+ t: (key) => key,
+ locales: LOCALES,
+})
+
+export function LocaleProvider({ children }: { children: ReactNode }) {
+ const [locale, setLocaleState] = useState(detectLocale)
+
+ const setLocale = useCallback((code: string) => {
+ if (!DICTS[code]) return
+ setLocaleState(code)
+ try { localStorage.setItem(STORAGE_KEY, code) } catch {}
+ }, [])
+
+ const t = useCallback((key: string, vars?: Record) => {
+ const dict = DICTS[locale] || en
+ const template = dict[key] ?? en[key] ?? key
+ return interpolate(template, vars)
+ }, [locale])
+
+ const value = useMemo(() => ({ locale, setLocale, t, locales: LOCALES }), [locale, setLocale, t])
+
+ return createElement(Ctx.Provider, { value }, children)
+}
+
+export function useLocale() {
+ return useContext(Ctx)
+}
diff --git a/web/src/i18n/it.ts b/web/src/i18n/it.ts
new file mode 100644
index 0000000..de9fee8
--- /dev/null
+++ b/web/src/i18n/it.ts
@@ -0,0 +1,113 @@
+const it: Record = {
+ "nav.chat": "Chat",
+ "nav.brain": "Cervello",
+ "nav.profiling": "Profiling",
+
+ "brand.tagline": "gigante locale, impronta minima",
+
+ "sidebar.connection": "Connessione",
+ "sidebar.endpoint": "Endpoint API",
+ "sidebar.apiKey": "Chiave API",
+ "sidebar.apiKeyPlaceholder": "opzionale",
+ "sidebar.apiKeyHelp": "Conservata solo in memoria · inviata a questo endpoint",
+ "sidebar.probe": "Sonda il server",
+ "status.connected": "Motore raggiungibile",
+ "status.notConnected": "Non connesso",
+ "status.runtimeUnavailable": "Metriche runtime non disponibili",
+ "status.serverError": "Impossibile raggiungere il server.",
+ "status.generationFailed": "Generazione fallita.",
+
+ "sidebar.runtime": "Runtime",
+ "sidebar.runtimeProbe": "Sonda il server per ispezionare lo stato runtime.",
+ "sidebar.schedulerOnline": "Scheduler online",
+ "dashboard.active": "Attive",
+ "dashboard.queued": "In coda",
+ "dashboard.completed": "Completate",
+ "dashboard.failures": "Fallite",
+ "dashboard.session": "Sessione:",
+ "dashboard.prompt": "prompt",
+ "dashboard.completion": "completion",
+
+ "tier.vram": "VRAM",
+ "tier.ram": "RAM",
+ "tier.disk": "Disco",
+ "tier.ariaLabel": "Expert: {{vram}} VRAM, {{ram}} RAM, {{disk}} disco",
+
+ "sidebar.inference": "Inferenza",
+ "sidebar.model": "Modello",
+ "sidebar.kvSession": "Sessione KV",
+ "sidebar.kvSessionHelp": "Contesto isolato · la conversazione segue lo slot selezionato",
+ "sidebar.sessionLabel": "Sessione {{slot}}",
+ "sidebar.temperature": "Temperatura",
+ "sidebar.maxTokens": "Token di output massimi",
+ "sidebar.reasoning": "Ragionamento",
+ "sidebar.transport": "Trasporto compatibile OpenAI",
+
+ "topbar.activeModel": "MODELLO ATTIVO",
+ "topbar.tokens": "{{n}} token",
+ "topbar.tokPerSec": "{{n}} tok/s",
+ "topbar.slot": "slot {{n}}",
+ "topbar.clear": "Pulisci",
+
+ "hero.title": "MOTORE COLIBRÌ",
+ "hero.subtitle": "Interroga il gigante.",
+ "hero.tagline": "La macchina resta tua.",
+ "hero.description": "Connettiti a un server colibrì locale e ricevi le risposte in streaming direttamente dal tuo hardware. Nulla lascia l'endpoint che scegli.",
+ "prompts.routing": "Spiega come funziona il routing degli expert",
+ "prompts.benchmark": "Scrivi un piccolo benchmark in C",
+ "prompts.caching": "Confronta il caching RAM e VRAM",
+
+ "chat.you": "Tu",
+ "chat.colibri": "colibrì",
+ "chat.placeholder": "Scrivi a colibrì…",
+ "chat.inputHint": "Invio per inviare · Shift+Invio per andare a capo",
+ "chat.stop": "Ferma la generazione",
+ "chat.send": "Invia messaggio",
+
+ "brain.title": "Corteccia degli expert",
+ "brain.waiting": "in attesa del motore",
+ "brain.layers": "{{rows}} layer × {{cols}} expert",
+ "brain.brightnessHint": "luminosità = calore di routing",
+ "brain.flashHint": "⚡ flash bianco = instradato in questo turno",
+ "brain.connectHint": "Connettiti al motore per vedere la corteccia.",
+ "brain.neverRouted": "mai instradato",
+ "brain.selections": "~2^{{heat}} selezioni",
+ "brain.specialist": "⭐ Specialista: {{top}}",
+ "brain.generalist": "Generalista",
+ "brain.mtp": "Testa MTP — prepara il prossimo token per la decodifica speculativa",
+ "brain.early": "layer iniziali — caratteristiche superficiali: token, ortografia, sintassi locale",
+ "brain.lowerMiddle": "layer medio-bassi — struttura frasale, relazioni tra parole, fatti semplici",
+ "brain.upperMiddle": "layer medio-alti — semantica, contesto a lungo raggio, passi di ragionamento",
+ "brain.late": "layer avanzati — pianificazione della risposta, stile, coerenza",
+ "brain.final": "layer finali — formazione dell'output: scelta della distribuzione next-token",
+
+ "profile.title": "Profiling — dove il motore spende ogni turno",
+ "profile.ioWait": "Attesa I/O",
+ "profile.expertMatmul": "Matmul expert",
+ "profile.attention": "Attenzione",
+ "profile.lmHead": "LM head",
+ "profile.other": "Altro",
+ "profile.empty": "Nessun turno profilato — invia un messaggio e i dettagli appariranno qui.",
+ "profile.connectHint": "Connettiti al motore per raccogliere i tempi per turno.",
+ "profile.lastTurn": "Ultimo turno",
+ "profile.wallTime": "Tempo totale",
+ "profile.batching": "Batching",
+ "profile.tokensPerForward": "token / forward",
+ "profile.diskService": "Servizio disco",
+ "profile.overlapped": "sovrapposto al calcolo",
+ "profile.window": "Finestra · ultimi {{n}} turni",
+ "profile.throughputTitle": "Throughput per turno (tok/s)",
+ "profile.phaseTitle": "Tempo per turno per fase (s)",
+ "profile.turnCol": "Turno",
+ "profile.tokensCol": "Token",
+ "profile.wallCol": "Totale",
+ "profile.turnsLabel": "{{n}} turni · dal meno al più recente",
+ "profile.oneTurn": "1 turno",
+ "profile.diskNote": "Il servizio disco è il tempo speso a leggere gli expert sui thread I/O; si sovrappone al calcolo, quindi solo l'attesa I/O effettivamente percepita dal thread di calcolo conta nella ripartizione del tempo totale. Con più sessioni KV, le quote descrivono l'intero motore nella finestra del turno.",
+
+ "error.title": "L'interfaccia colibrì ha riscontrato un errore",
+ "error.hint": "Il motore non è stato coinvolto. Prova a ricaricare la pagina.",
+ "error.retry": "Riprova",
+}
+
+export default it
diff --git a/web/src/i18n/zh-CN.ts b/web/src/i18n/zh-CN.ts
new file mode 100644
index 0000000..fc0a42f
--- /dev/null
+++ b/web/src/i18n/zh-CN.ts
@@ -0,0 +1,113 @@
+const zhCN: Record = {
+ "nav.chat": "对话",
+ "nav.brain": "大脑",
+ "nav.profiling": "性能分析",
+
+ "brand.tagline": "本地巨人,极小足迹",
+
+ "sidebar.connection": "连接",
+ "sidebar.endpoint": "API 端点",
+ "sidebar.apiKey": "API 密钥",
+ "sidebar.apiKeyPlaceholder": "可选",
+ "sidebar.apiKeyHelp": "仅保存在内存中 · 发送到此端点",
+ "sidebar.probe": "探测服务器",
+ "status.connected": "引擎已连接",
+ "status.notConnected": "未连接",
+ "status.runtimeUnavailable": "运行时指标不可用",
+ "status.serverError": "无法连接到服务器。",
+ "status.generationFailed": "生成失败。",
+
+ "sidebar.runtime": "运行时",
+ "sidebar.runtimeProbe": "探测服务器以查看运行时状态。",
+ "sidebar.schedulerOnline": "调度器在线",
+ "dashboard.active": "活跃",
+ "dashboard.queued": "排队",
+ "dashboard.completed": "已完成",
+ "dashboard.failures": "失败",
+ "dashboard.session": "会话:",
+ "dashboard.prompt": "提示词",
+ "dashboard.completion": "补全",
+
+ "tier.vram": "VRAM",
+ "tier.ram": "RAM",
+ "tier.disk": "磁盘",
+ "tier.ariaLabel": "专家分布:{{vram}} VRAM、{{ram}} RAM、{{disk}} 磁盘",
+
+ "sidebar.inference": "推理",
+ "sidebar.model": "模型",
+ "sidebar.kvSession": "KV 会话",
+ "sidebar.kvSessionHelp": "独立上下文 · 对话跟随所选槽位",
+ "sidebar.sessionLabel": "会话 {{slot}}",
+ "sidebar.temperature": "温度",
+ "sidebar.maxTokens": "最大输出 token 数",
+ "sidebar.reasoning": "推理模式",
+ "sidebar.transport": "OpenAI 兼容协议",
+
+ "topbar.activeModel": "当前模型",
+ "topbar.tokens": "{{n}} tokens",
+ "topbar.tokPerSec": "{{n}} tok/s",
+ "topbar.slot": "槽位 {{n}}",
+ "topbar.clear": "清空",
+
+ "hero.title": "COLIBRÌ 引擎",
+ "hero.subtitle": "向巨人提问。",
+ "hero.tagline": "让机器属于你。",
+ "hero.description": "连接到本地 colibrì 服务器,直接从你的硬件流式获取响应。所有数据都留在你选择的端点内。",
+ "prompts.routing": "解释专家路由是如何工作的",
+ "prompts.benchmark": "写一个简单的 C 基准测试",
+ "prompts.caching": "比较 RAM 和 VRAM 缓存",
+
+ "chat.you": "你",
+ "chat.colibri": "colibrì",
+ "chat.placeholder": "给 colibrì 发消息…",
+ "chat.inputHint": "回车发送 · Shift+回车换行",
+ "chat.stop": "停止生成",
+ "chat.send": "发送消息",
+
+ "brain.title": "专家皮层",
+ "brain.waiting": "等待引擎连接",
+ "brain.layers": "{{rows}} 层 × {{cols}} 专家",
+ "brain.brightnessHint": "亮度 = 路由热度",
+ "brain.flashHint": "⚡ 白色闪烁 = 本轮被路由",
+ "brain.connectHint": "连接引擎以查看皮层。",
+ "brain.neverRouted": "从未被路由",
+ "brain.selections": "约 2^{{heat}} 次选择",
+ "brain.specialist": "⭐ 专精:{{top}}",
+ "brain.generalist": "通用型",
+ "brain.mtp": "MTP 头 — 为投机解码起草下一个 token",
+ "brain.early": "早期层 — 表面特征:token、拼写、局部语法",
+ "brain.lowerMiddle": "中低层 — 短语结构、词语关系、简单事实",
+ "brain.upperMiddle": "中高层 — 语义、长距离上下文、推理步骤",
+ "brain.late": "后期层 — 规划答案、风格、连贯性",
+ "brain.final": "末尾层 — 输出成型:选择实际的 next-token 分布",
+
+ "profile.title": "性能分析 — 引擎每轮的时间花在哪里",
+ "profile.ioWait": "I/O 等待",
+ "profile.expertMatmul": "专家矩阵乘",
+ "profile.attention": "注意力",
+ "profile.lmHead": "LM head",
+ "profile.other": "其他",
+ "profile.empty": "暂无性能数据 — 发送一条消息,分析结果将显示在这里。",
+ "profile.connectHint": "连接引擎以采集每轮耗时。",
+ "profile.lastTurn": "最近一轮",
+ "profile.wallTime": "总耗时",
+ "profile.batching": "批处理",
+ "profile.tokensPerForward": "tokens / 前向",
+ "profile.diskService": "磁盘服务",
+ "profile.overlapped": "与计算重叠",
+ "profile.window": "窗口 · 最近 {{n}} 轮",
+ "profile.throughputTitle": "每轮吞吐量 (tok/s)",
+ "profile.phaseTitle": "每轮各阶段耗时 (s)",
+ "profile.turnCol": "轮次",
+ "profile.tokensCol": "Tokens",
+ "profile.wallCol": "总耗时",
+ "profile.turnsLabel": "{{n}} 轮 · 从旧到新",
+ "profile.oneTurn": "1 轮",
+ "profile.diskNote": "磁盘服务是在 I/O 线程上读取专家的时间;它与计算重叠,因此只有计算线程实际感受到的 I/O 等待 才计入总耗时分解。多 KV 会话时,份额描述的是整个引擎在该轮窗口内的表现。",
+
+ "error.title": "colibrì UI 遇到错误",
+ "error.hint": "引擎不受影响。请尝试刷新页面。",
+ "error.retry": "重试",
+}
+
+export default zhCN
diff --git a/web/src/i18n/zh-TW.ts b/web/src/i18n/zh-TW.ts
new file mode 100644
index 0000000..6435be2
--- /dev/null
+++ b/web/src/i18n/zh-TW.ts
@@ -0,0 +1,113 @@
+const zhTW: Record = {
+ "nav.chat": "對話",
+ "nav.brain": "大腦",
+ "nav.profiling": "效能分析",
+
+ "brand.tagline": "本地巨人,極小足跡",
+
+ "sidebar.connection": "連線",
+ "sidebar.endpoint": "API 端點",
+ "sidebar.apiKey": "API 金鑰",
+ "sidebar.apiKeyPlaceholder": "選填",
+ "sidebar.apiKeyHelp": "僅保存在記憶體中 · 傳送到此端點",
+ "sidebar.probe": "探測伺服器",
+ "status.connected": "引擎已連線",
+ "status.notConnected": "未連線",
+ "status.runtimeUnavailable": "執行階段指標不可用",
+ "status.serverError": "無法連線到伺服器。",
+ "status.generationFailed": "生成失敗。",
+
+ "sidebar.runtime": "執行階段",
+ "sidebar.runtimeProbe": "探測伺服器以檢視執行階段狀態。",
+ "sidebar.schedulerOnline": "排程器上線",
+ "dashboard.active": "進行中",
+ "dashboard.queued": "排隊中",
+ "dashboard.completed": "已完成",
+ "dashboard.failures": "失敗",
+ "dashboard.session": "工作階段:",
+ "dashboard.prompt": "提示詞",
+ "dashboard.completion": "補全",
+
+ "tier.vram": "VRAM",
+ "tier.ram": "RAM",
+ "tier.disk": "磁碟",
+ "tier.ariaLabel": "專家分佈:{{vram}} VRAM、{{ram}} RAM、{{disk}} 磁碟",
+
+ "sidebar.inference": "推論",
+ "sidebar.model": "模型",
+ "sidebar.kvSession": "KV 工作階段",
+ "sidebar.kvSessionHelp": "獨立上下文 · 對話跟隨所選插槽",
+ "sidebar.sessionLabel": "工作階段 {{slot}}",
+ "sidebar.temperature": "溫度",
+ "sidebar.maxTokens": "最大輸出 token 數",
+ "sidebar.reasoning": "推理模式",
+ "sidebar.transport": "OpenAI 相容協定",
+
+ "topbar.activeModel": "目前模型",
+ "topbar.tokens": "{{n}} tokens",
+ "topbar.tokPerSec": "{{n}} tok/s",
+ "topbar.slot": "插槽 {{n}}",
+ "topbar.clear": "清除",
+
+ "hero.title": "COLIBRÌ 引擎",
+ "hero.subtitle": "向巨人提問。",
+ "hero.tagline": "讓機器屬於你。",
+ "hero.description": "連線到本地 colibrì 伺服器,直接從你的硬體串流取得回應。所有資料都留在你選擇的端點內。",
+ "prompts.routing": "解釋專家路由如何運作",
+ "prompts.benchmark": "撰寫一個簡單的 C 基準測試",
+ "prompts.caching": "比較 RAM 與 VRAM 快取",
+
+ "chat.you": "你",
+ "chat.colibri": "colibrì",
+ "chat.placeholder": "傳送訊息給 colibrì…",
+ "chat.inputHint": "Enter 傳送 · Shift+Enter 換行",
+ "chat.stop": "停止生成",
+ "chat.send": "傳送訊息",
+
+ "brain.title": "專家皮層",
+ "brain.waiting": "等待引擎連線",
+ "brain.layers": "{{rows}} 層 × {{cols}} 專家",
+ "brain.brightnessHint": "亮度 = 路由熱度",
+ "brain.flashHint": "⚡ 白色閃爍 = 本輪被路由",
+ "brain.connectHint": "連線引擎以檢視皮層。",
+ "brain.neverRouted": "從未被路由",
+ "brain.selections": "約 2^{{heat}} 次選擇",
+ "brain.specialist": "⭐ 專精:{{top}}",
+ "brain.generalist": "通用型",
+ "brain.mtp": "MTP 頭 — 為推測式解碼起草下一個 token",
+ "brain.early": "早期層 — 表面特徵:token、拼寫、局部語法",
+ "brain.lowerMiddle": "中低層 — 片語結構、詞語關係、簡單事實",
+ "brain.upperMiddle": "中高層 — 語意、長距離上下文、推理步驟",
+ "brain.late": "後期層 — 規劃答案、風格、連貫性",
+ "brain.final": "末尾層 — 輸出成型:選擇實際的 next-token 分佈",
+
+ "profile.title": "效能分析 — 引擎每輪的時間花在哪裡",
+ "profile.ioWait": "I/O 等待",
+ "profile.expertMatmul": "專家矩陣乘",
+ "profile.attention": "注意力",
+ "profile.lmHead": "LM head",
+ "profile.other": "其他",
+ "profile.empty": "尚無效能數據 — 傳送一則訊息,分析結果將顯示在這裡。",
+ "profile.connectHint": "連線引擎以採集每輪耗時。",
+ "profile.lastTurn": "最近一輪",
+ "profile.wallTime": "總耗時",
+ "profile.batching": "批次處理",
+ "profile.tokensPerForward": "tokens / 前向",
+ "profile.diskService": "磁碟服務",
+ "profile.overlapped": "與運算重疊",
+ "profile.window": "視窗 · 最近 {{n}} 輪",
+ "profile.throughputTitle": "每輪吞吐量 (tok/s)",
+ "profile.phaseTitle": "每輪各階段耗時 (s)",
+ "profile.turnCol": "輪次",
+ "profile.tokensCol": "Tokens",
+ "profile.wallCol": "總耗時",
+ "profile.turnsLabel": "{{n}} 輪 · 從舊到新",
+ "profile.oneTurn": "1 輪",
+ "profile.diskNote": "磁碟服務是在 I/O 執行緒上讀取專家的時間;它與運算重疊,因此只有運算執行緒實際感受到的 I/O 等待 才計入總耗時分解。多 KV 工作階段時,份額描述的是整個引擎在該輪視窗內的表現。",
+
+ "error.title": "colibrì UI 遇到錯誤",
+ "error.hint": "引擎不受影響。請嘗試重新整理頁面。",
+ "error.retry": "重試",
+}
+
+export default zhTW
diff --git a/web/src/index.css b/web/src/index.css
index 8d777f4..4a98b18 100644
--- a/web/src/index.css
+++ b/web/src/index.css
@@ -64,7 +64,9 @@ button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-
.toggle-row { display: flex; align-items: center; justify-content: space-between; height: 42px; padding: 0 11px; border: 1px solid var(--border); border-radius: 9px; color: #a9b4b8; background: var(--input); }
.toggle-row > span { display: flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 600; }.toggle-row.active { border-color: rgba(78,214,165,.35); color: var(--foreground); }
.toggle-row i { width: 30px; height: 17px; padding: 2px; border-radius: 20px; background: #293136; transition: .2s; }.toggle-row i b { display: block; width: 13px; height: 13px; border-radius: 50%; background: #78858a; transition: .2s; }.toggle-row.active i { background: rgba(78,214,165,.28); }.toggle-row.active i b { transform: translateX(13px); background: var(--primary); }
-.sidebar-foot { margin-top: auto; display: flex; align-items: center; gap: 7px; color: #59666b; font-size: 10px; }
+.sidebar-foot { margin-top: auto; display: flex; flex-direction: column; gap: 6px; color: #59666b; font-size: 10px; }
+.sidebar-foot > div { display: flex; align-items: center; gap: 7px; }
+.locale-switcher select { background: transparent; border: 1px solid var(--border); border-radius: 4px; color: inherit; font-size: 10px; padding: 2px 4px; cursor: pointer; }
.chat-panel { min-width: 0; height: 100vh; display: grid; grid-template-rows: 72px minmax(0, 1fr) auto; }
.topbar { display: flex; align-items: center; justify-content: space-between; padding: 0 32px; border-bottom: 1px solid var(--border); }
diff --git a/web/src/main.tsx b/web/src/main.tsx
index 9b964f6..894f4f4 100644
--- a/web/src/main.tsx
+++ b/web/src/main.tsx
@@ -2,10 +2,13 @@ import { createRoot } from "react-dom/client"
import App from "./App"
import { ErrorBoundary } from "./ErrorBoundary"
+import { LocaleProvider } from "./i18n"
import "./index.css"
createRoot(document.getElementById("root")!).render(
-
+
+
+
,
)
From 93b4a8e78efeea4a70f6abc8cf6f840210fff860 Mon Sep 17 00:00:00 2001
From: ZacharyZcR
Date: Sun, 19 Jul 2026 04:27:10 +0800
Subject: [PATCH 20/22] =?UTF-8?q?ci:=20fix=20Windows=20CUDA=20DLL=20check?=
=?UTF-8?q?=20=E2=80=94=20glm.exe=20=E2=86=92=20colibri.exe?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/ci.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index eb7e872..f213363 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -110,8 +110,8 @@ jobs:
run: |
cd c
make colibri CUDA_DLL=1
- test -f glm.exe || { echo "glm CUDA_DLL=1 reported success but produced no exe" >&2; exit 1; }
- echo "glm.exe built against the DLL loader"
+ test -f colibri.exe || { echo "colibri CUDA_DLL=1 reported success but produced no exe" >&2; exit 1; }
+ echo "colibri.exe built against the DLL loader"
web:
name: Web UI
From bc69a9a6d030442e86cdfd22e488ccbc5387aee2 Mon Sep 17 00:00:00 2001
From: ZacharyZcR
Date: Sun, 19 Jul 2026 21:15:54 +0800
Subject: [PATCH 21/22] =?UTF-8?q?fix:=20remove=20duplicate=20argmax=5Fv=20?=
=?UTF-8?q?=E2=80=94=20use=20NaN-safe=20version=20from=20sample.h?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
c/colibri.c | 5 -----
c/sample.h | 6 +++---
2 files changed, 3 insertions(+), 8 deletions(-)
diff --git a/c/colibri.c b/c/colibri.c
index 554a7fa..03eb878 100644
--- a/c/colibri.c
+++ b/c/colibri.c
@@ -3481,11 +3481,6 @@ static void mtp_absorb(Model *m, const int *next_ids, const float *x, int S, int
free(hx); free(cat); free(e); free(hn); free(hf); free(nrm); free(tmp);
}
-static inline int argmax_v(const float *lo, int V){
- int b=-1; float bv=-INFINITY;
- for(int i=0;ibv){ bv=x; b=i; } }
- return b<0?0:b;
-}
static void repin_pass_limit(Model *m,int limit);
static void repin_pass(Model *m){ repin_pass_limit(m,16); }
diff --git a/c/sample.h b/c/sample.h
index abce88c..26db529 100644
--- a/c/sample.h
+++ b/c/sample.h
@@ -17,9 +17,9 @@ static inline double rndu(void){
/* ---- argmax over a float vector ----------------------------------------- */
static inline int argmax_v(const float *lo, int V){
- int b = 0; float bv = lo[0];
- for (int i = 1; i < V; i++) if (lo[i] > bv) { bv = lo[i]; b = i; }
- return b;
+ int b=-1; float bv=-INFINITY;
+ for(int i=0;ibv){ bv=x; b=i; } }
+ return b<0?0:b;
}
/* ---- distribution buffers (reused, single-threaded decode) --------------- */
From 083fda5b0aac1b4a1a62c84dde5ce8a6e64d3088 Mon Sep 17 00:00:00 2001
From: ZacharyZcR
Date: Sun, 19 Jul 2026 21:18:33 +0800
Subject: [PATCH 22/22] fix: update test_logit_nan to include colibri.c instead
of glm.c
---
c/Makefile | 2 +-
c/tests/test_logit_nan.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/c/Makefile b/c/Makefile
index e33ad37..af17357 100644
--- a/c/Makefile
+++ b/c/Makefile
@@ -335,7 +335,7 @@ tests/test_sample_nan$(EXE): tests/test_sample_nan.c colibri.c st.h uring.h json
tests/test_kv_alloc$(EXE): tests/test_kv_alloc.c colibri.c st.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h quant.h sample.h kv_persist.h telemetry.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
-tests/test_logit_nan$(EXE): tests/test_logit_nan.c glm.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h
+tests/test_logit_nan$(EXE): tests/test_logit_nan.c colibri.c st.h uring.h json.h tok.h tok_unicode.h compat.h grammar.h tier.h
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
tests/test_i4_acc512$(EXE): tests/test_i4_acc512.c
diff --git a/c/tests/test_logit_nan.c b/c/tests/test_logit_nan.c
index 981d74d..de6dc2d 100644
--- a/c/tests/test_logit_nan.c
+++ b/c/tests/test_logit_nan.c
@@ -15,7 +15,7 @@
#include
#include
#define main coli_glm_main_unused
-#include "../glm.c"
+#include "../colibri.c"
#undef main
static int approx1(double x){ return x > 0.999 && x < 1.001; }