10 Commits

Author SHA1 Message Date
JustVugg 153e6f4c89 site: fix license — Apache 2.0, not MIT
Footer said 'MIT license'; repo is Apache 2.0 (see LICENSE). Fix + link.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 23:40:20 +02:00
JustVugg 00ed8d5aa4 docs: surface the website (badge + header link) in README
Site (justvugg.github.io/colibri) is live but linked nowhere. Add website +
latest-release badges and a Website header link. README-only, no code change.

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

Refs #424

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

Refs #450

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:36:06 +02:00
Vincenzo Fornaro 86766bf989 Merge pull request #427 from DavidePapero/main
Added Dockerfile and instructions for using Colibrì through a docker container
2026-07-20 17:20:26 +02:00
Davide Quack 27b4c4263a Added Dockerfile and instructions for using Colibrì through a docker container 2026-07-19 16:49:29 +02:00
Vincenzo Fornaro 72874f38a2 Merge pull request #411 from JustVugg/dev
release: fix the Windows job shell in release.yml (v1.0.0 tag build)
2026-07-19 12:30:32 +02:00
Vincenzo Fornaro 819941ee2c Merge pull request #400 from JustVugg/dev
Release v1.0.0
2026-07-19 12:25:43 +02:00
23 changed files with 1889 additions and 1087 deletions
-10
View File
@@ -1,10 +0,0 @@
BasedOnStyle: LLVM
IndentWidth: 4
ColumnLimit: 120
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortLoopsOnASingleLine: true
BreakBeforeBraces: Attach
PointerAlignment: Right
SpaceAfterCStyleCast: false
SortIncludes: false
-24
View File
@@ -1,24 +0,0 @@
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
+36
View File
@@ -0,0 +1,36 @@
name: Deploy website
# Publishes site/ to GitHub Pages. One-time repo setup:
# Settings → Pages → Build and deployment → Source: "GitHub Actions".
# Custom domain later: add site/CNAME with the bare domain, point DNS
# (A/AAAA to GitHub Pages IPs or CNAME to <org>.github.io), done.
on:
push:
branches: [main]
paths: ['site/**', '.github/workflows/site.yml']
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
with:
path: site
- id: deployment
uses: actions/deploy-pages@v4
-4
View File
@@ -68,7 +68,3 @@ c/tests/test_decode_batch
c/tests/test_i4_acc512
c/tests/test_idot
c/tests/test_uring
olmoe_merged/
olmoe_i4/
c/olmoe_merged/
c/olmoe_i4/
+14 -7
View File
@@ -3,6 +3,12 @@
</p>
<p align="center">
<a href="https://justvugg.github.io/colibri"><img src="https://img.shields.io/badge/website-justvugg.github.io%2Fcolibri-1f6feb" alt="Website"></a>
<a href="https://github.com/JustVugg/colibri/releases"><img src="https://img.shields.io/github/v/release/JustVugg/colibri?color=2ea043" alt="Latest release"></a>
</p>
<p align="center">
<a href="https://justvugg.github.io/colibri"><b>Website</b></a> ·
English · <a href="README.zh-TW.md">繁體中文</a>
</p>
@@ -151,9 +157,14 @@ 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.
> **New here, or on Windows?** The [Quick Start guide](docs/quickstart.md) walks
> through install → build → model → first chat step by step for Linux, Windows,
> and macOS. On **Windows** you don't even need to build: download the
> `colibri-<version>-windows-x86_64.zip` from
> [Releases](https://github.com/JustVugg/colibri/releases), unzip it, rename
> `colibri-*-windows-x86_64.exe` → `glm.exe`, install
> [Python 3](https://www.python.org/downloads/), and run `coli chat` — full
> details in the [Windows section](docs/quickstart.md#windows).
### 1. Get the model
@@ -187,10 +198,6 @@ 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 |
+15 -41
View File
@@ -1336,12 +1336,7 @@ 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);
/* 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);
char *b=malloc(n+1); size_t got=fread(b,1,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);
}
@@ -1380,9 +1375,8 @@ 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);
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;
if(gn>0){
char *gb=malloc(gn+1); size_t gg=fread(gb,1,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){
@@ -1451,27 +1445,6 @@ 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)
@@ -1481,11 +1454,12 @@ 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) */
/* 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). */
/* 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;
int gs=0;
int fmt = qt_resolve_fmt(name,O,I,nb,ns,&gs);
if(fmt==2) gs=detect_group_size(O,I,ns);
if(gs>0) fmt=4;
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); }
@@ -1828,8 +1802,11 @@ 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;
int fmt=qt_resolve_fmt(tw[k]->name,OO[k],II[k],nb,tq[k]->nbytes,&gs);
if(fmt==2) gs=detect_group_size(OO[k],II[k],tq[k]->nbytes);
if(gs>0) fmt=4;
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);
@@ -1954,8 +1931,10 @@ 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;
int fmt=qt_resolve_fmt(tw[k]->name,OO[k],II[k],nb,tq[k]->nbytes,&gs);
if(fmt==2) gs=detect_group_size(OO[k],II[k],tq[k]->nbytes);
if(gs>0) fmt=4;
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];
}
@@ -2931,11 +2910,6 @@ 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]]<UINT32_MAX) m->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<D;d++) out[(int64_t)s*D+d]=0;
}
+38 -529
View File
@@ -5,15 +5,6 @@
* 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/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 <stdio.h>
@@ -21,22 +12,11 @@
#include <string.h>
#include <math.h>
#include <time.h>
#include <pthread.h>
#if defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__)
#include <sys/resource.h>
#include <unistd.h>
#endif
#include "st.h"
#ifdef _WIN32
#include <windows.h>
#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;
@@ -53,61 +33,22 @@ 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. */
/* 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 { int eid; int8_t *g, *u, *d; float *gs, *us, *ds; uint64_t used; } Slot;
typedef struct { Slot *slots; int n, cap; } LCache;
typedef struct {
Cfg c;
shards S;
int quant_bits;
int quant_bits; /* bit di quantizzazione degli expert (2..8); storage int8, niente f32 (#134) */
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;
int token_count;
/* 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 */
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;
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);
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;
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);
}
}
/* ---------- 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__)
@@ -269,224 +210,51 @@ static void model_init(Model *m, const char *snap, int cap, int bits) {
#undef LD
}
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;
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->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;
for (int i = 0; i < c->n_layers; i++) { m->cache[i].cap = cap; m->cache[i].slots = calloc(cap, sizeof(Slot)); }
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;
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);
}
}
}
}
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);
}
}
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);
/* 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;
}
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_f32(&m->S, qsnm, s->gs, 0); /* scales are F32; use typed reader for dtype safety */
st_read_f32(&m->S, name, tmp, 1); /* pread + fadvise DONTNEED */
quantize_rows(tmp, q, scale, O, I, m->quant_bits);
}
/* ---------- 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];
pthread_mutex_unlock(&g_pilot_mx);
return;
m->hits++; lc->slots[i].used = ++m->clock; *out = &lc->slots[i]; return;
}
m->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++];
slot_ensure_allocated(m, s);
} else {
/* 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 || lc->slots[i].eid < 0) continue;
if (lru < 0 || lc->slots[i].used < lc->slots[lru].used) lru = i;
}
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;
}
s->eid = -1;
s->used = ++m->clock;
pthread_mutex_unlock(&g_pilot_mx);
load_expert_merged(m, layer, eid, s);
pthread_mutex_lock(&g_pilot_mx);
s->eid = eid;
s->pinned = m->is_pinned[layer * c->n_experts + eid];
s->used = ++m->clock;
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]; }
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;
*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 is_dynamic = (m->hot_n >= 100);
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);
if (hn > 256) hn = 256;
int hot_eids[256];
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; }
if (!already && freq_l[e] > bv) { bv = freq_l[e]; best = e; }
}
if (best < 0 || bv == 0) break;
if (is_dynamic && bv < thresh * layer_total) break;
hot_eids[k] = best;
actual_hn++;
}
for (int k = 0; k < actual_hn; k++) {
int eid = hot_eids[k];
m->is_pinned[l * c->n_experts + eid] = 1;
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 && 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);
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++;
}
}
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);
}
}
/* ---------- RoPE su un vettore di una testa (head_dim) a posizione assoluta pos ---------- */
static void rope_head(float *x, int pos, const Cfg *c) {
int h = c->head_dim / 2;
@@ -557,19 +325,6 @@ 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 (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) {
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];
@@ -582,11 +337,6 @@ 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;kk<K;kk++) sm+=val[kk]; for(int kk=0;kk<K;kk++) val[kk]/=sm; }
/* IMPROVEMENT 2: update activation heatmap (before pinning activates) */
if (!m->hot_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);
@@ -602,20 +352,9 @@ 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 && m->token_count > 0) {
/* 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);
}
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);
@@ -624,26 +363,12 @@ 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);
if (g_pilot >= 3 && S <= 8 && i + 3 < c->n_layers)
pilot_prefetch(m, i + 3, x, S);
}
/* 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;
/* 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);
@@ -652,192 +377,6 @@ 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;
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;
pthread_mutex_unlock(&g_pilot_mx);
return;
}
}
Slot *s;
if (lc->n < lc->cap) {
s = &lc->slots[lc->n++];
slot_ensure_allocated(m, s);
} else {
/* 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 || 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/in-flight, skip */
}
s = &lc->slots[lru]; s->pinned = 0;
}
s->eid = -1; s->used = ++m->clock;
pthread_mutex_unlock(&g_pilot_mx);
load_expert_merged(m, layer, eid, s);
pthread_mutex_lock(&g_pilot_mx);
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);
}
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;
ensure_pilot_worker_started(m);
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 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] - max_logit);
sum_exps += exps[e];
}
float cum_sum = 0.f;
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++) {
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 && 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);
/* 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) {
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);
}
}
}
}
}
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;
@@ -903,32 +442,22 @@ 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=<snapshot directory>\n"); return 1; }
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 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);
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);
return 1;
}
const char *refpath = argc > 3 ? argv[3] : "ref.json";
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 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; }
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());
@@ -958,26 +487,6 @@ 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);
// 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;
-28
View File
@@ -1,28 +0,0 @@
{
"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
]
}
+1 -22
View File
@@ -200,19 +200,7 @@ 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); }
/* 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); }
int64_t numel = 1; for (int k = 0; k < shp->len; k++) numel *= (int64_t)shp->kids[k]->num;
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;
@@ -261,15 +249,6 @@ 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");
-145
View File
@@ -1,145 +0,0 @@
#!/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
import huggingface_hub
except ImportError as exc:
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"
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()
-70
View File
@@ -1,70 +0,0 @@
"""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_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_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_ID} ...")
print(" (this will use ~14 GB RAM — please be patient)")
model = OlmoeForCausalLM.from_pretrained(
MODEL_ID,
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}")
-91
View File
@@ -1,91 +0,0 @@
"""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
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"
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 : <id> <id> ..." 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)
-5
View File
@@ -1,5 +0,0 @@
"""colibrì — tiny engine, immense model."""
from colibri._version import __version__
__all__ = ["__version__"]
-23
View File
@@ -1,23 +0,0 @@
"""Version accessor for the pip package.
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"
-30
View File
@@ -1,30 +0,0 @@
"""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()
+15
View File
@@ -0,0 +1,15 @@
FROM debian:stable-slim
# We install the necessary packages to download and compile the code
RUN apt-get update && \
apt-get install -y locales git build-essential python3 python3-pip && \
rm -rf /var/lib/apt/lists/* && \
mkdir /app
# Let's clone the repository
WORKDIR /app
RUN git clone https://github.com/JustVugg/colibri.git .
WORKDIR /app/c
# It's time to compile
RUN ./setup.sh && ./coli build
+454
View File
@@ -0,0 +1,454 @@
_Read the read me in [English](README.md)._
# Guida a Colibrì - Motore di Inferenza Locale
Una guida semplice per eseguire **Colibrì**, un motore di inferenza locale basato su GLM 5.2, senza conoscenze di programmazione. Se hai già Docker installato, sei a buon punto.
---
## 📋 Sommario
- [Cosa è Colibrì?](#cosa-è-colibrì)
- [Cosa serve](#cosa-serve)
- [Hardware](#hardware)
- [Software](#software)
- [Come iniziare](#come-iniziare)
- [Passo 1: Scarica il modello](#passo-1-scarica-il-modello)
- [Passo 2: Scarica il Dockerfile di Colibrì](#passo-2-scarica-il-dockerfile-di-colibrì)
- [Passo 3: Compila l'immagine Docker](#passo-3-compila-limmagine-docker)
- [Passo 4: Avvia Colibrì](#passo-4-avvia-colibr%C3%AC)
- [Cosa significa quel comando?](#cosa-significa-quel-comando)
- [Usare Colibrì](#usare-colibrì)
- [Entrare in una console Linux dentro il container](#entrare-in-una-console-linux-dentro-il-container)
- [Risoluzione dei problemi](#risoluzione-dei-problemi)
- [Note tecniche](#note-tecniche)
- [Domande frequenti](#domande-frequenti)
- [Supporto e contributi](#supporto-e-contributi)
- [Test sul mio PC](#test-sul-mio-pc)
---
## Cosa è Colibrì?
Colibrì è un'applicazione che ti permette di eseguire un modello di intelligenza artificiale (GLM 5.2) direttamente sul tuo computer, senza connettersi a server esterni. È possbile anche farlo girare in Docker, che isola l'applicazione dal resto del sistema.
> **Nota importante**: Il modello è molto grande. Attendi anche diversi minuti per una risposta a una domanda semplice, specialmente con poca RAM. Alla fine di questo readme vedrai il risultato sul mio PC (senza scheda grafica discreta) e arrivo a 0.01 token al secondo.
---
## Cosa serve
### Hardware
| Memoria RAM | Funziona? | Note |
|:---:|:---:|---|
| < 16 GB | ❌ No | Memoria insufficiente |
| 24 GB | ⚠️ Forse | Possibile, da testare |
| 32 GB | ✅ Sì | Il minimo (ma vedi sezione memoria su Windows) |
| 48+ GB | ✅ Sì | Meglio |
Inoltre: un **disco SSD veloce** è essenziale. Colibrì usa il disco come memoria aggiuntiva. Con una scheda grafica NVidia è ancora meglio.
### Software
- **Docker Desktop** (Windows, Mac, Linux) — [scarica qui](https://www.docker.com/products/docker-desktop/)
- **Python** (solo se vuoi scaricare il modello da casa tua)
- Windows: [python.org](https://www.python.org) oppure Microsoft Store
- Linux: `apt-get install python3 python3-pip`
- Mac: [python.org](https://www.python.org) oppure Homebrew
Non serve nessun ambiente di compilazione. Tutto avviene dentro il container Docker.
---
## Come iniziare
### Passo 1: Scarica il modello
Il modello GLM 5.2 è circa **360 GB**. Scegli uno di questi metodi:
#### Metodo A: Con Python (consigliato)
1. **Installa la libreria per Hugging Face:**
```bash
python -m pip install -U huggingface_hub[cli]
```
Su Linux, usa `python3` al posto di `python`.
2. **Scarica il modello** (apri il terminale nella cartella dove lo vuoi salvare):
```bash
hf_download mateogrgic/GLM-5.2-colibri-int4-with-int8-mtp --local-dir .
```
**Esempio**: se vuoi salvarlo in `C:\LLM\models\glm-5.2` (Windows):
- Apri PowerShell in quella cartella
- Copia e incolla il comando sopra
- Attendi (molto)
#### Metodo B: Senza Python (solo se necessario)
Se sei su Windows e non riesci con Python:
- Scarica manualmente da [Hugging Face](https://huggingface.co/mateogrgic/GLM-5.2-colibri-int4-with-int8-mtp)
- Decomprimi in una cartella (es. `C:\LLM\models\glm-5.2`)
---
### Passo 2: Scarica il Dockerfile di Colibrì
1. Vai a: https://github.com/JustVugg/colibri/blob/main/docker/Dockerfile
2. Clicca il pulsante **Download** (icona ⬇️) in alto a destra
3. Salva il file in una cartella (es. `C:\LLM\Colibrì`)
---
### Passo 3: Compila l'immagine Docker
Apri il terminale (PowerShell su Windows, Terminal su Mac/Linux) **nella cartella dove hai salvato il Dockerfile** e digita:
**Windows:**
```bash
docker build -t colibri-i .
```
**Linux/Mac:**
```bash
sudo docker build -t colibri-i .
```
Attendi che finisca (pochi minuti). Se tutto va bene, vedrai: `Successfully tagged colibri-i:latest`
> **Se vuoi recepire gli aggiornamenti del repository**: Cancella prima l'immagine vecchia con `docker rmi colibri-i` e ricompila.
---
### Passo 4: Avvia Colibrì
Apri il terminale e digita il comando sottostante (sostituisci `C:\LLM\models\glm-5.2` con il percorso reale del tuo PC):
**Windows** (PowerShell):
```bash
$MODEL_PATH="C:\LLM\models\glm-5.2"
docker run --rm -it --name colibri-c `
-v "$MODEL_PATH`:/app/glm-5.2" `
-e COLI_MODEL=/app/glm-5.2 `
colibri-i ./coli chat
```
**Mac/Linux** (Terminal/Bash):
```bash
MODEL_PATH="/path/to/glm-5.2"
docker run --rm -it --name colibri-c \
-v "$MODEL_PATH:/app/glm-5.2" \
-e COLI_MODEL=/app/glm-5.2 \
colibri-i ./coli chat
```
**Esempio per Linux:**
```bash
MODEL_PATH="/home/user/LLM/glm-5.2"
docker run --rm -it --name colibri-c \
-v "$MODEL_PATH:/app/glm-5.2" \
-e COLI_MODEL=/app/glm-5.2 \
colibri-i ./coli chat
```
---
### Cosa significa quel comando?
| Parte | Spiegazione |
|-------|-------------|
| `docker run` | Avvia un container |
| `--rm` | Cancella il container quando chiudi |
| `-it` | Modalità interattiva (puoi scrivere e leggere) |
| `-v "PERCORSO:/app/glm-5.2"` | Collega il tuo modello dentro il container |
| `-e COLI_MODEL=/app/glm-5.2` | Dice a Colibrì dove trovare il modello |
| `colibri-i` | Nome dell'immagine Docker |
| `./coli chat` | Avvia Colibrì in modalità chat |
---
### Usare Colibrì
Una volta avviato, vedrai un prompt come questo:
```
──────────────────────────────────────────────────────────
type and press Enter · Ctrl-C stops the answer · :more continues · :reset clears memory · :q exits
```
**Comandi utili:**
- `Scrivi una domanda + Invio` → Ricevi la risposta
- `Ctrl + C` → Interrompi la risposta
- `:reset` → Cancella la memoria della conversazione
- `:q` → Esci
**Esempio di uso:**
```
Quanti abitanti ha la Cina?
La Cina è attualmente il paese più popoloso al mondo.
La popolazione è di circa 1,41 miliardi di abitanti.
```
Il modello capisce **italiano, inglese, cinese e altre lingue**, anche se è ottimizzato per inglese e cinese.
---
## Entrare in una console Linux dentro il container
Se vuoi esplorare il container come fosse una macchina Linux normale:
```bash
docker run --rm -it --name colibri-c \
-v "PERCORSO_MODELLO:/app/glm-5.2" \
-e COLI_MODEL=/app/glm-5.2 \
colibri-i /bin/bash
```
Ora sei dentro Linux. Digita `exit` per uscire.
---
## Risoluzione dei problemi
### ❌ "Docker non trovato"
**Causa**: Docker non è installato o il terminale non lo riconosce.
**Soluzione**:
1. Reinstalla [Docker Desktop](https://www.docker.com/products/docker-desktop/)
2. Riavvia il computer
3. Apri un nuovo terminale e riprova
---
### ❌ "Out of memory" (memoria insufficiente) o container che si chiude subito
**Causa**: Il tuo computer non ha abbastanza RAM, oppure su Windows, WSL usa meno memoria di quella disponibile.
**Soluzione per Windows (WSL):**
1. Apri PowerShell e controlla la memoria disponibile a WSL:
```bash
wsl
cat /proc/meminfo | grep MemTotal
exit
```
Dividi il numero per 1.073.741.824 (è 1024³) per averlo in GB.
2. Se WSL usa meno di quello che hai, crea un file di configurazione:
- Apri un editor di testo (Notepad va bene)
- Copia questo:
```ini
[wsl2]
memory=24GB
processors=12
swap=16GB
```
- Salva il file con il nome: `.wslconfig` (con il punto)
- Posizionalo in: `C:\Users\TuoNomeUtente\`
3. Riavvia WSL da PowerShell:
```bash
wsl --shutdown
wsl
```
4. Controlla di nuovo:
```bash
# cat /proc/meminfo | grep MemTotal
# exit
```
**Soluzione per Mac/Linux**: Aumenta la RAM disponibile a Docker dalle impostazioni di Docker Desktop, oppure aggiungi più RAM al computer.
---
### ❌ La risposta è molto lenta
**Cause possibili**:
1. Il disco è lento
2. Hai poca RAM
3. Colibrì sta usando il disco come memoria aggiuntiva (normale)
**Come controllare la velocità del disco:**
**Windows** (PowerShell da amministratore):
```bash
winsat disk -drive C
```
Cambia `C` con la lettera del tuo disco.
**Linux/Mac** (Terminal):
```bash
sudo hdparm -Tt /dev/sda
```
Cambia `/dev/sda` con il tuo disco (vedi con `lsblk` per Linux).
Un **SSD NVMe moderno** arriva a 15 GB/sec. Se il tuo è sotto 2-3 GB/sec, è lento.
---
### ❌ "Permission denied" su Linux
**Causa**: Docker richiede permessi da amministratore.
**Soluzione - Opzione 1** (rapida):
```bash
sudo docker build -t colibri-i .
sudo docker run ... (come sopra, con sudo davanti)
```
**Soluzione - Opzione 2** (permanente):
```bash
sudo usermod -aG docker $USER
# Riavvia il computer
docker run ... (senza sudo)
```
---
### ❌ "Image not found" o errore durante il build
**Causa**: Il Dockerfile è corrotto o non nella cartella giusta.
**Soluzione**:
1. Verifica che il Dockerfile sia nella cartella dove apri il terminale:
```bash
ls Dockerfile # Mac/Linux
dir Dockerfile # Windows
```
2. Riscarica il Dockerfile dal repository GitHub
3. Elimina l'immagine vecchia: `docker rmi colibri-i`
4. Riprova il build
---
### ❌ "hf_download: command not found"
**Causa**: La libreria Hugging Face non è installata correttamente.
**Soluzione**:
```bash
pip install -U huggingface_hub[cli]
# oppure su Linux/Mac:
pip3 install -U huggingface_hub[cli]
```
Poi riprova il comando `hf_download`.
---
### ❌ Il modello non si scarica (timeout o errori di rete)
**Cause**: Connessione lenta o instabile, Hugging Face temporaneamente non disponibile.
**Soluzione**:
1. Attendi e riprova il comando `hf_download`
2. Se continua, scarica manualmente da [qui](https://huggingface.co/mateogrgic/GLM-5.2-colibri-int4-with-int8-mtp)
3. Decomprimi il file ZIP nella cartella desiderata
---
## Note tecniche
### Perché il disco è importante?
Colibrì usa il disco come "RAM aggiuntiva" virtuale (paging). Un disco **veloce** è cruciale per prestazioni decenti.
- **SSD NVMe** (consigliato): 1-15 GB/sec
- **SSD SATA**: 0.5-1 GB/sec
- **Hard disk meccanico**: 0.05-0.1 GB/sec ❌ (troppo lento)
Se il tuo disco è lento, le risposte saranno molto lente anche con molta RAM.
---
### Configurazione di default consigliata per WLS su Windows
Se hai **esattamente 32 GB di RAM** e usi Windows, è molto probabile che WLS di default sia settato per non consumare più di 16 GB di RAM. Bisogna aumentare questo limite [Risoluzione dei problemi](#risoluzione-dei-problemi) . Nel mio caso ho adottato questa configurazione:
```ini
[wsl2]
memory=24GB
processors=12
swap=16GB
```
Ovvero, nel mio caso, ho lasciato 8 GB di RAM e 4 CPU a Windows e dato 24 GB e 12 processori a WSL + Linux.
---
## Domande frequenti
**D: E se ho meno di 32 GB di RAM?**
R: Probabile che non funzioni bene. Puoi provare se hai 24 GB, ma non è garantito.
**D: Posso aumentare la velocità di risposta?**
R: Sì, in parte:
- Usa un SSD NVMe veloce
- Aumenta la RAM
- Riduci la complessità delle domande
- Usa `:reset` per cancellare la memoria e alleggerire il carico
**D: Posso usare Colibrì senza Docker?**
R: Colibrì è nato così, ma questa guida assume Docker. Per compilare da sorgente, vedi il repository GitHub.
**D: Quanta connessione internet mi serve dopo aver scaricato il modello?**
R: Zero. Colibrì funziona completamente offline.
---
## Supporto e contributi
Se trovi errori o hai suggerimenti per migliorare questa guida, aprici una issue o una pull request sul repository GitHub di Colibrì.
Buon divertimento! 🐦
---
## Test sul mio PC
Nel primo caso ho fatto una domanda in italiano, nel secondo in giapponese, e nel terzo ho rifatto la domanda in giapponese ma ho richiesto una risposta in italiano.
```
PS C:\quack\llm\colibri\docker> docker run --rm -it --name colibri-c -v "C:\quack\llm\models\glm-5.2:/app/glm-5.2" -e COLI_MODEL=/app/glm-5.2 colibri-i ./coli chat
▄▀▀▀▄ ▄ colibrì v1.0
▄▄▄▄▀▀▀▀▄▀▀ tiny engine, immense model
▀▀▀▀▀▀▀ GLM-5.2 · 744B MoE · int4 · streaming CPU
▀▀▀▀ chat · glm-5.2 · ram -GB · topp off
──────────────────────────────────────────────────────────
type and press Enter · Ctrl-C stops the answer · :more continues · :reset clears memory · :q exits
╭────────────────────────────────────────────────────────────────────────────────────────────────╮
Quanti abitanti ha la Cina? │
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
◆ colibrì
La Cina è attualmente il paese più popoloso al mondo (sebbene, secondo alcune stime recenti, sia stata ormai superata dall'India).
La popolazione totale della Repubblica Popolare Cinese è di circa 1,41 miliardi di abitanti (dati del 2020-2022 circa).
└─ 76 tok · 0.04 tok/s · hit 3% · RSS 15.9 GB · 2012s
╭────────────────────────────────────────────────────────────────────────────────────────────────╮
│ › 漫画「ワンピース」の主人公の名前を教えてください。名前だけで、それ以上のコメントはありません。 │
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
◆ colibrì
ルフィ
└─ 2 tok · 0.01 tok/s · hit 1% · RSS 16.7 GB · 260s
╭────────────────────────────────────────────────────────────────────────────────────────────────╮
│ › 漫画「ワンピース」の主人公の名前を教えてください。名前だけで、それ以上のコメントはありません。イタリア語で返信 │
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
◆ colibrì
Il nome del protagonista di One Piece è Monkey D. Luffy.
└─ 14 tok · 0.02 tok/s · hit 2% · RSS 17.3 GB · 593s
╭────────────────────────────────────────────────────────────────────────────────────────────────╮
```
+488
View File
@@ -0,0 +1,488 @@
_Leggi il leggimi in [Italiano](README.IT.md)._
# Colibrì Guide - Local Inference Engine
A simple guide to running **Colibrì**, a local inference engine based on GLM 5.2, without needing programming knowledge. If you already have Docker installed, you are well on your way.
---
## 📋 Table of Contents
* [What is Colibrì?](https://www.google.com/search?q=#what-is-colibr%C3%AC)
* [Requirements](https://www.google.com/search?q=#requirements)
* [Hardware](https://www.google.com/search?q=#hardware)
* [Software](https://www.google.com/search?q=#software)
* [How to get started](https://www.google.com/search?q=#how-to-get-started)
* [Step 1: Download the model](https://www.google.com/search?q=#step-1-download-the-model)
* [Step 2: Download the Colibrì Dockerfile](https://www.google.com/search?q=#step-2-download-the-colibr%C3%AC-dockerfile)
* [Step 3: Build the Docker image](https://www.google.com/search?q=#step-3-build-the-docker-image)
* [Step 4: Start Colibrì](https://www.google.com/search?q=#step-4-start-colibr%C3%AC)
* [What does that command mean?](https://www.google.com/search?q=#what-does-that-command-mean)
* [Using Colibrì](https://www.google.com/search?q=#using-colibr%C3%AC)
* [Entering a Linux console inside the container](https://www.google.com/search?q=#entering-a-linux-console-inside-the-container)
* [Troubleshooting](https://www.google.com/search?q=#troubleshooting)
* [Technical notes](https://www.google.com/search?q=#technical-notes)
* [Frequently Asked Questions](https://www.google.com/search?q=#frequently-asked-questions)
* [Support and contributions](https://www.google.com/search?q=#support-and-contributions)
* [Tests on my PC](https://www.google.com/search?q=#tests-on-my-pc)
---
## What is Colibrì?
Colibrì is an application that allows you to run an artificial intelligence model (GLM 5.2) directly on your computer, without connecting to external servers. It is also possible to run it in Docker, which isolates the application from the rest of the system.
> **Important note**: The model is very large. Expect to wait several minutes for an answer to a simple question, especially with low RAM. At the end of this readme, you will see the result on my PC (without a discrete graphics card), and I reach 0.01 tokens per second.
---
## Requirements
### Hardware
| RAM Memory | Works? | Notes |
| --- | --- | --- |
| < 16 GB | ❌ No | Insufficient memory |
| 24 GB | ⚠️ Maybe | Possible, needs testing |
| 32 GB | ✅ Yes | The minimum (but see memory section for Windows) |
| 48+ GB | ✅ Yes | Better |
Additionally: a **fast SSD** is essential. Colibrì uses the disk as additional memory. Having an NVidia graphics card is even better.
### Software
* **Docker Desktop** (Windows, Mac, Linux) — [download here](https://www.google.com/search?q=https://www.docker.com/products/docker-desktop/)
* **Python** (only if you want to download the model yourself)
* Windows: [python.org](https://www.google.com/search?q=https://www.python.org) or Microsoft Store
* Linux: `apt-get install python3 python3-pip`
* Mac: [python.org](https://www.google.com/search?q=https://www.python.org) or Homebrew
No build environment is needed. Everything happens inside the Docker container.
---
## How to get started
### Step 1: Download the model
The GLM 5.2 model is approximately **360 GB**. Choose one of these methods:
#### Method A: Using Python (recommended)
1. **Install the library for Hugging Face:**
```bash
python -m pip install -U huggingface_hub[cli]
```
On Linux, use `python3` instead of `python`.
2. **Download the model** (open the terminal in the folder where you want to save it):
```bash
hf_download mateogrgic/GLM-5.2-colibri-int4-with-int8-mtp --local-dir .
```
**Example**: if you want to save it in `C:\LLM\models\glm-5.2` (Windows):
* Open PowerShell in that folder
* Copy and paste the command above
* Wait (a long time)
#### Method B: Without Python (only if necessary)
If you are on Windows and cannot get it to work with Python:
* Download manually from [Hugging Face](https://www.google.com/search?q=https://huggingface.co/mateogrgic/GLM-5.2-colibri-int4-with-int8-mtp)
* Unzip into a folder (e.g., `C:\LLM\models\glm-5.2`)
---
### Step 2: Download the Colibrì Dockerfile
1. Go to: [https://github.com/JustVugg/colibri/blob/main/docker/Dockerfile](https://www.google.com/search?q=https://github.com/JustVugg/colibri/blob/main/docker/Dockerfile)
2. Click the **Download** button (⬇️ icon) in the top right
3. Save the file in a folder (e.g., `C:\LLM\Colibrì`)
---
### Step 3: Build the Docker image
Open the terminal (PowerShell on Windows, Terminal on Mac/Linux) **in the folder where you saved the Dockerfile** and type:
**Windows:**
```bash
docker build -t colibri-i .
```
**Linux/Mac:**
```bash
sudo docker build -t colibri-i .
```
Wait for it to finish (a few minutes). If everything goes well, you will see: `Successfully tagged colibri-i:latest`
> **If you want to receive repository updates**: First delete the old image with `docker rmi colibri-i` and rebuild.
---
### Step 4: Start Colibrì
Open the terminal and type the command below (replace `C:\LLM\models\glm-5.2` with the actual path on your PC):
**Windows** (PowerShell):
```bash
$MODEL_PATH="C:\LLM\models\glm-5.2"
docker run --rm -it --name colibri-c `
-v "$MODEL_PATH`:/app/glm-5.2" `
-e COLI_MODEL=/app/glm-5.2 `
colibri-i ./coli chat
```
**Mac/Linux** (Terminal/Bash):
```bash
MODEL_PATH="/path/to/glm-5.2"
docker run --rm -it --name colibri-c \
-v "$MODEL_PATH:/app/glm-5.2" \
-e COLI_MODEL=/app/glm-5.2 \
colibri-i ./coli chat
```
**Example for Linux:**
```bash
MODEL_PATH="/home/user/LLM/glm-5.2"
docker run --rm -it --name colibri-c \
-v "$MODEL_PATH:/app/glm-5.2" \
-e COLI_MODEL=/app/glm-5.2 \
colibri-i ./coli chat
```
---
### What does that command mean?
| Part | Explanation |
| --- | --- |
| `docker run` | Starts a container |
| `--rm` | Deletes the container when you close it |
| `-it` | Interactive mode (you can write and read) |
| `-v "PATH:/app/glm-5.2"` | Mounts your model inside the container |
| `-e COLI_MODEL=/app/glm-5.2` | Tells Colibrì where to find the model |
| `colibri-i` | Name of the Docker image |
| `./coli chat` | Starts Colibrì in chat mode |
---
### Using Colibrì
Once started, you will see a prompt like this:
```
──────────────────────────────────────────────────────────
type and press Enter · Ctrl-C stops the answer · :more continues · :reset clears memory · :q exits
```
**Useful commands:**
* `Write a question + Enter` → Receive the answer
* `Ctrl + C` → Stop the answer
* `:reset` → Clear conversation memory
* `:q` → Exit
**Usage example:**
```
How many inhabitants does China have?
China is currently the most populous country in the world.
The population is approximately 1.41 billion people.
```
The model understands **Italian, English, Chinese, and other languages**, although it is optimized for English and Chinese.
---
## Entering a Linux console inside the container
If you want to explore the container as if it were a normal Linux machine:
```bash
docker run --rm -it --name colibri-c \
-v "MODEL_PATH:/app/glm-5.2" \
-e COLI_MODEL=/app/glm-5.2 \
colibri-i /bin/bash
```
Now you are inside Linux. Type `exit` to leave.
---
## Troubleshooting
### ❌ "Docker not found"
**Cause**: Docker is not installed or the terminal does not recognize it.
**Solution**:
1. Reinstall [Docker Desktop](https://www.google.com/search?q=https://www.docker.com/products/docker-desktop/)
2. Restart your computer
3. Open a new terminal and try again
---
### ❌ "Out of memory" or container closes immediately
**Cause**: Your computer does not have enough RAM, or on Windows, WSL is using less memory than available.
**Solution for Windows (WSL):**
1. Open PowerShell and check the memory available to WSL:
```bash
wsl
cat /proc/meminfo | grep MemTotal
exit
```
Divide the number by 1,073,741,824 (which is 1024³) to get it in GB.
2. If WSL uses less than what you have, create a configuration file:
* Open a text editor (Notepad is fine)
* Copy this:
```ini
[wsl2]
memory=24GB
processors=12
swap=16GB
```
* Save the file with the name: `.wslconfig` (with the dot)
* Place it in: `C:\Users\YourUsername\`
3. Restart WSL from PowerShell:
```bash
wsl --shutdown
wsl
```
4. Check again:
```bash
# cat /proc/meminfo | grep MemTotal
# exit
```
**Solution for Mac/Linux**: Increase the RAM available to Docker from the Docker Desktop settings, or add more RAM to the computer.
---
### ❌ The answer is very slow
**Possible causes**:
1. The disk is slow
2. You have low RAM
3. Colibrì is using the disk as additional memory (normal)
**How to check disk speed:**
**Windows** (PowerShell as administrator):
```bash
winsat disk -drive C
```
Change `C` with your disk letter.
**Linux/Mac** (Terminal):
```bash
sudo hdparm -Tt /dev/sda
```
Change `/dev/sda` with your disk (check with `lsblk` on Linux).
A **modern NVMe SSD** reaches 15 GB/sec. If yours is under 2-3 GB/sec, it is slow.
---
### ❌ "Permission denied" on Linux
**Cause**: Docker requires administrator permissions.
**Solution - Option 1** (quick):
```bash
sudo docker build -t colibri-i .
sudo docker run ... (as above, with sudo in front)
```
**Solution - Option 2** (permanent):
```bash
sudo usermod -aG docker $USER
# Restart the computer
docker run ... (without sudo)
```
---
### ❌ "Image not found" or error during build
**Cause**: The Dockerfile is corrupted or not in the right folder.
**Solution**:
1. Verify that the Dockerfile is in the folder where you open the terminal:
```bash
ls Dockerfile # Mac/Linux
dir Dockerfile # Windows
```
2. Redownload the Dockerfile from the GitHub repository
3. Delete the old image: `docker rmi colibri-i`
4. Retry the build
---
### ❌ "hf_download: command not found"
**Cause**: The Hugging Face library is not installed correctly.
**Solution**:
```bash
pip install -U huggingface_hub[cli]
# or on Linux/Mac:
pip3 install -U huggingface_hub[cli]
```
Then retry the `hf_download` command.
---
### ❌ The model does not download (timeout or network errors)
**Causes**: Slow or unstable connection, Hugging Face temporarily unavailable.
**Solution**:
1. Wait and retry the `hf_download` command
2. If it continues, download manually from [here](https://www.google.com/search?q=https://huggingface.co/mateogrgic/GLM-5.2-colibri-int4-with-int8-mtp)
3. Unzip the ZIP file into the desired folder
---
## Technical notes
### Why is the disk important?
Colibrì uses the disk as "additional virtual RAM" (paging). A **fast** disk is crucial for decent performance.
* **NVMe SSD** (recommended): 1-15 GB/sec
* **SATA SSD**: 0.5-1 GB/sec
* **Rotational hard disk**: 0.05-0.1 GB/sec ❌ (too slow)
If your disk is slow, the answers will be very slow even with a lot of RAM.
---
### Recommended default configuration for WLS on Windows
If you have **exactly 32 GB of RAM** and are using Windows, it is very likely that WLS by default is set to consume no more than 16 GB of RAM. We need to increase this limit [Troubleshooting](https://www.google.com/search?q=#troubleshooting) . In my case I adopted this configuration:
```ini
[wsl2]
memory=24GB
processors=12
swap=16GB
```
That is, in my case, I left 8 GB of RAM and 4 CPUs to Windows and gave 24 GB and 12 processors to WSL + Linux.
---
## Frequently Asked Questions
**Q: What if I have less than 32 GB of RAM?**
A: It is likely that it will not work well. You can try if you have 24 GB, but it is not guaranteed.
**Q: Can I increase the response speed?**
A: Yes, partly:
* Use a fast NVMe SSD
* Increase RAM
* Reduce the complexity of questions
* Use `:reset` to clear memory and lighten the load
**Q: Can I use Colibrì without Docker?**
A: Colibrì was born that way, but this guide assumes Docker. To build from source, see the GitHub repository.
**Q: How much internet connection do I need after downloading the model?**
A: Zero. Colibrì works completely offline.
---
## Support and contributions
If you find errors or have suggestions for improving this guide, open an issue or a pull request on the Colibrì GitHub repository.
Have fun! 🐦
---
## Tests on my PC
In the first case, I asked a question in Italian; in the second, in Japanese; and in the third, I repeated the question in Japanese but requested an answer in Italian.
```
PS C:\quack\llm\colibri\docker> docker run --rm -it --name colibri-c -v "C:\quack\llm\models\glm-5.2:/app/glm-5.2" -e COLI_MODEL=/app/glm-5.2 colibri-i ./coli chat
▄▀▀▀▄ ▄ colibrì v1.0
▄▄▄▄▀▀▀▀▄▀▀ tiny engine, immense model
▀▀▀▀▀▀▀ GLM-5.2 · 744B MoE · int4 · streaming CPU
▀▀▀▀ chat · glm-5.2 · ram -GB · topp off
──────────────────────────────────────────────────────────
type and press Enter · Ctrl-C stops the answer · :more continues · :reset clears memory · :q exits
╭────────────────────────────────────────────────────────────────────────────────────────────────╮
Quanti abitanti ha la Cina? │
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
◆ colibrì
La Cina è attualmente il paese più popoloso al mondo (sebbene, secondo alcune stime recenti, sia stata ormai superata dall'India).
La popolazione totale della Repubblica Popolare Cinese è di circa 1,41 miliardi di abitanti (dati del 2020-2022 circa).
└─ 76 tok · 0.04 tok/s · hit 3% · RSS 15.9 GB · 2012s
╭────────────────────────────────────────────────────────────────────────────────────────────────╮
│ › 漫画「ワンピース」の主人公の名前を教えてください。名前だけで、それ以上のコメントはありません。 │
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
◆ colibrì
ルフィ
└─ 2 tok · 0.01 tok/s · hit 1% · RSS 16.7 GB · 260s
╭────────────────────────────────────────────────────────────────────────────────────────────────╮
│ › 漫画「ワンピース」の主人公の名前を教えてください。名前だけで、それ以上のコメントはありません。イタリア語で返信 │
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
◆ colibrì
Il nome del protagonista di One Piece è Monkey D. Luffy.
└─ 14 tok · 0.02 tok/s · hit 2% · RSS 17.3 GB · 593s
╭────────────────────────────────────────────────────────────────────────────────────────────────╮
```
[source: 1]
+25 -5
View File
@@ -42,11 +42,31 @@ engine needs.
You have two options.
**Option A — download a prebuilt binary (no compiler needed).**
Grab the latest `colibri-<version>-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.
Grab `colibri-<version>-windows-x86_64.zip` from the
[Releases page](https://github.com/JustVugg/colibri/releases) and unzip it.
Inside you'll find:
| File | What it is |
|---|---|
| `colibri-<version>-windows-x86_64.exe` | **the engine** — the C program that actually runs the model |
| `coli` | the command-line launcher (`chat`, `serve`, `convert`, `doctor`, …) |
| `openai_server.py`, `resource_plan.py`, `doctor.py` | Python support for the API server and placement planner |
Two setup steps:
1. **Rename the engine to `glm.exe`** so the launcher can find it (it looks for a
binary named `glm`):
```powershell
Rename-Item colibri-*-windows-x86_64.exe glm.exe
```
2. **Install Python 3** from [python.org](https://www.python.org/downloads/) — the
`coli` launcher and the API gateway are Python scripts (the engine itself is
pure C and needs nothing).
Then continue to [step 3](#3-get-the-model). Prefer to skip the launcher? You can
run the engine directly — `.\glm.exe` reads the model path from the `SNAP`
environment variable (see [docs/windows.md](windows.md)) — but `coli chat` is the
easy path.
**Option B — build from source with MSYS2.**
Install [MSYS2](https://www.msys2.org/), open the **UCRT64** shell, and run:
-53
View File
@@ -1,53 +0,0 @@
[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*"]
+50
View File
@@ -0,0 +1,50 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 168 168">
<rect width="168" height="168" rx="36" fill="#080b0d"/>
<g transform="translate(7 21)" shape-rendering="crispEdges">
<rect x="56" y="0" width="14" height="14" fill="#d75fd7"/>
<rect x="70" y="0" width="14" height="14" fill="#d75fd7"/>
<rect x="84" y="0" width="14" height="14" fill="#d75fd7"/>
<rect x="42" y="14" width="14" height="14" fill="#d75fd7"/>
<rect x="56" y="14" width="14" height="14" fill="#d75fd7"/>
<rect x="70" y="14" width="14" height="14" fill="#d75fd7"/>
<rect x="84" y="14" width="14" height="14" fill="#d75fd7"/>
<rect x="98" y="14" width="14" height="14" fill="#d75fd7"/>
<rect x="140" y="14" width="14" height="14" fill="#5fd7d7"/>
<rect x="56" y="28" width="14" height="14" fill="#d75fd7"/>
<rect x="70" y="28" width="14" height="14" fill="#d75fd7"/>
<rect x="84" y="28" width="14" height="14" fill="#d75fd7"/>
<rect x="98" y="28" width="14" height="14" fill="#d75fd7"/>
<rect x="126" y="28" width="14" height="14" fill="#5fd7d7"/>
<rect x="140" y="28" width="14" height="14" fill="#5fd7d7"/>
<rect x="0" y="42" width="14" height="14" fill="#ff8700"/>
<rect x="14" y="42" width="14" height="14" fill="#ff8700"/>
<rect x="28" y="42" width="14" height="14" fill="#ff8700"/>
<rect x="42" y="42" width="14" height="14" fill="#ff8700"/>
<rect x="56" y="42" width="14" height="14" fill="#00afaf"/>
<rect x="70" y="42" width="14" height="14" fill="#00afaf"/>
<rect x="84" y="42" width="14" height="14" fill="#fff"/>
<rect x="98" y="42" width="14" height="14" fill="#00afaf"/>
<rect x="112" y="42" width="14" height="14" fill="#5fd7d7"/>
<rect x="126" y="42" width="14" height="14" fill="#5fd7d7"/>
<rect x="56" y="56" width="14" height="14" fill="#00afaf"/>
<rect x="70" y="56" width="14" height="14" fill="#00afaf"/>
<rect x="84" y="56" width="14" height="14" fill="#00afaf"/>
<rect x="98" y="56" width="14" height="14" fill="#00afaf"/>
<rect x="112" y="56" width="14" height="14" fill="#00afaf"/>
<rect x="126" y="56" width="14" height="14" fill="#5fd7d7"/>
<rect x="140" y="56" width="14" height="14" fill="#5fd7d7"/>
<rect x="70" y="70" width="14" height="14" fill="#00afaf"/>
<rect x="84" y="70" width="14" height="14" fill="#00afaf"/>
<rect x="98" y="70" width="14" height="14" fill="#00afaf"/>
<rect x="112" y="70" width="14" height="14" fill="#00afaf"/>
<rect x="126" y="70" width="14" height="14" fill="#5fd7d7"/>
<rect x="140" y="70" width="14" height="14" fill="#5fd7d7"/>
<rect x="84" y="84" width="14" height="14" fill="#00afaf"/>
<rect x="98" y="84" width="14" height="14" fill="#00afaf"/>
<rect x="112" y="84" width="14" height="14" fill="#5fd7d7"/>
<rect x="126" y="84" width="14" height="14" fill="#5fd7d7"/>
<rect x="98" y="98" width="14" height="14" fill="#00afaf"/>
<rect x="112" y="98" width="14" height="14" fill="#5fd7d7"/>
<rect x="112" y="112" width="14" height="14" fill="#5fd7d7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+55
View File
@@ -0,0 +1,55 @@
<svg xmlns="http://www.w3.org/2000/svg" width="620" height="140" viewBox="0 0 620 140">
<g shape-rendering="crispEdges">
<rect x="56" y="0" width="14" height="14" fill="#d75fd7"/>
<rect x="70" y="0" width="14" height="14" fill="#d75fd7"/>
<rect x="84" y="0" width="14" height="14" fill="#d75fd7"/>
<rect x="42" y="14" width="14" height="14" fill="#d75fd7"/>
<rect x="56" y="14" width="14" height="14" fill="#d75fd7"/>
<rect x="70" y="14" width="14" height="14" fill="#d75fd7"/>
<rect x="84" y="14" width="14" height="14" fill="#d75fd7"/>
<rect x="98" y="14" width="14" height="14" fill="#d75fd7"/>
<rect x="140" y="14" width="14" height="14" fill="#5fd7d7"/>
<rect x="56" y="28" width="14" height="14" fill="#d75fd7"/>
<rect x="70" y="28" width="14" height="14" fill="#d75fd7"/>
<rect x="84" y="28" width="14" height="14" fill="#d75fd7"/>
<rect x="98" y="28" width="14" height="14" fill="#d75fd7"/>
<rect x="126" y="28" width="14" height="14" fill="#5fd7d7"/>
<rect x="140" y="28" width="14" height="14" fill="#5fd7d7"/>
<rect x="0" y="42" width="14" height="14" fill="#ff8700"/>
<rect x="14" y="42" width="14" height="14" fill="#ff8700"/>
<rect x="28" y="42" width="14" height="14" fill="#ff8700"/>
<rect x="42" y="42" width="14" height="14" fill="#ff8700"/>
<rect x="56" y="42" width="14" height="14" fill="#00afaf"/>
<rect x="70" y="42" width="14" height="14" fill="#00afaf"/>
<rect x="84" y="42" width="14" height="14" fill="#ffffff"/>
<rect x="98" y="42" width="14" height="14" fill="#00afaf"/>
<rect x="112" y="42" width="14" height="14" fill="#5fd7d7"/>
<rect x="126" y="42" width="14" height="14" fill="#5fd7d7"/>
<rect x="56" y="56" width="14" height="14" fill="#00afaf"/>
<rect x="70" y="56" width="14" height="14" fill="#00afaf"/>
<rect x="84" y="56" width="14" height="14" fill="#00afaf"/>
<rect x="98" y="56" width="14" height="14" fill="#00afaf"/>
<rect x="112" y="56" width="14" height="14" fill="#00afaf"/>
<rect x="126" y="56" width="14" height="14" fill="#5fd7d7"/>
<rect x="140" y="56" width="14" height="14" fill="#5fd7d7"/>
<rect x="70" y="70" width="14" height="14" fill="#00afaf"/>
<rect x="84" y="70" width="14" height="14" fill="#00afaf"/>
<rect x="98" y="70" width="14" height="14" fill="#00afaf"/>
<rect x="112" y="70" width="14" height="14" fill="#00afaf"/>
<rect x="126" y="70" width="14" height="14" fill="#5fd7d7"/>
<rect x="140" y="70" width="14" height="14" fill="#5fd7d7"/>
<rect x="84" y="84" width="14" height="14" fill="#00afaf"/>
<rect x="98" y="84" width="14" height="14" fill="#00afaf"/>
<rect x="112" y="84" width="14" height="14" fill="#5fd7d7"/>
<rect x="126" y="84" width="14" height="14" fill="#5fd7d7"/>
<rect x="98" y="98" width="14" height="14" fill="#00afaf"/>
<rect x="112" y="98" width="14" height="14" fill="#5fd7d7"/>
<rect x="112" y="112" width="14" height="14" fill="#5fd7d7"/>
</g>
<text x="252" y="62" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
font-size="52" font-weight="bold" fill="#00afaf">colibr&#236;</text>
<text x="252" y="94" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
font-size="19" fill="#808080" font-style="italic">tiny engine, immense model</text>
<text x="252" y="122" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
font-size="15" fill="#9a9a9a">GLM-5.2 &#183; 744B MoE &#183; int4 &#183; streaming CPU</text>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

+698
View File
File diff suppressed because one or more lines are too long