Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 845af6378d | |||
| 3ffe4bb75e | |||
| 72e36772f5 | |||
| fae4b2cc3e | |||
| 22509fccde | |||
| ca39e5333f | |||
| 741d46ba25 | |||
| d86a6b93ad | |||
| c769e04d13 | |||
| b6bae91b66 | |||
| 2d8d2951ee | |||
| 1ac2e7b487 | |||
| 6ade4093de | |||
| ca788833ab | |||
| 24058d3de8 | |||
| b3fdb145f8 | |||
| 4ae4f61d16 | |||
| c4624276f3 | |||
| ac1f7a8f38 |
@@ -0,0 +1,10 @@
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 120
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
BreakBeforeBraces: Attach
|
||||
PointerAlignment: Right
|
||||
SpaceAfterCStyleCast: false
|
||||
SortIncludes: false
|
||||
@@ -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
|
||||
@@ -1,36 +0,0 @@
|
||||
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
|
||||
@@ -68,3 +68,7 @@ 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/
|
||||
|
||||
@@ -3,12 +3,6 @@
|
||||
</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>
|
||||
|
||||
@@ -157,14 +151,9 @@ scale-granularity/rotation ablations live in
|
||||
|
||||
## Get started
|
||||
|
||||
> **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).
|
||||
> **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
|
||||
|
||||
@@ -198,6 +187,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 |
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -5,6 +5,15 @@
|
||||
* 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>
|
||||
@@ -12,11 +21,22 @@
|
||||
#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;
|
||||
@@ -33,22 +53,61 @@ 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;
|
||||
/* 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;
|
||||
|
||||
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;
|
||||
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__)
|
||||
@@ -210,51 +269,224 @@ 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)); }
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
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);
|
||||
}
|
||||
st_read_f32(&m->S, name, tmp, 1); /* pread + fadvise DONTNEED */
|
||||
quantize_rows(tmp, q, scale, O, I, m->quant_bits);
|
||||
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 */
|
||||
}
|
||||
|
||||
/* ---------- 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->slots[i].used = ++m->clock; *out = &lc->slots[i];
|
||||
pthread_mutex_unlock(&g_pilot_mx);
|
||||
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++];
|
||||
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;
|
||||
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;
|
||||
*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;
|
||||
@@ -325,6 +557,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 (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];
|
||||
@@ -337,6 +582,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;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);
|
||||
@@ -352,9 +602,20 @@ 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);
|
||||
@@ -363,12 +624,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);
|
||||
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);
|
||||
@@ -377,6 +652,192 @@ 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;
|
||||
@@ -442,22 +903,32 @@ 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; }
|
||||
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 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;}
|
||||
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; }
|
||||
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());
|
||||
|
||||
@@ -487,6 +958,26 @@ 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;
|
||||
|
||||
@@ -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
|
||||
]
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/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()
|
||||
@@ -0,0 +1,70 @@
|
||||
"""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}")
|
||||
@@ -0,0 +1,91 @@
|
||||
"""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)
|
||||
@@ -0,0 +1,5 @@
|
||||
"""colibrì — tiny engine, immense model."""
|
||||
|
||||
from colibri._version import __version__
|
||||
|
||||
__all__ = ["__version__"]
|
||||
@@ -0,0 +1,23 @@
|
||||
"""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"
|
||||
@@ -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()
|
||||
@@ -1,15 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,454 +0,0 @@
|
||||
_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
|
||||
|
||||
╭────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ›
|
||||
```
|
||||
@@ -1,488 +0,0 @@
|
||||
_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]
|
||||
+5
-25
@@ -42,31 +42,11 @@ engine needs.
|
||||
You have two options.
|
||||
|
||||
**Option A — download a prebuilt binary (no compiler needed).**
|
||||
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.
|
||||
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.
|
||||
|
||||
**Option B — build from source with MSYS2.**
|
||||
Install [MSYS2](https://www.msys2.org/), open the **UCRT64** shell, and run:
|
||||
|
||||
@@ -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*"]
|
||||
@@ -1,50 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,55 +0,0 @@
|
||||
<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ì</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 · 744B MoE · int4 · streaming CPU</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
-698
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user