@clutor/cli 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/c/src/main.c CHANGED
@@ -2,18 +2,28 @@
2
2
  #include "gguf.h"
3
3
  #include "stream.h"
4
4
  #include "kernel.h"
5
+ #include "forward.h"
6
+ #include "tokenizer.h"
7
+ #include "quant.h"
8
+ #include <math.h>
9
+ #include <time.h>
10
+ #include <sys/resource.h>
5
11
  #include <stdio.h>
6
12
  #include <stdlib.h>
7
13
  #include <string.h>
8
14
 
9
- #define VERSION "0.1.0"
15
+ #define VERSION "0.3.0"
10
16
 
11
17
  static void usage(void) {
12
18
  puts("clutor-engine " VERSION "\n"
13
19
  " info <model.gguf> [--json] Read the header and print where every layer's bytes and experts live\n"
14
- " stream <model.gguf> [options] Stream experts from disk through the cache for N tokens of SYNTHETIC routing and report what was measured\n"
20
+ " tokenize <model.gguf> TEXT [--no-special] Print the token ids of TEXT (byte-level BPE built from the model's own tables)\n"
21
+ " logits <model.gguf> PROMPT [--top N] [--ctx N] [--trace FILE] Run the REAL forward pass over PROMPT and print the next-token logprobs (olmoe models); --trace writes the router's choices\n"
22
+ " generate <model.gguf> PROMPT [--n N] [--trace FILE] [--quiet] [--cache-mb N --prefetch off|prev|ahead (off) --prefetch-n N --policy lru|wtinylfu|adaptive --io-threads N --no-os-cache] Greedy decoding with the real forward pass; --trace records the router's scores for every token; --cache-mb N streams the experts from disk through an N MiB cache instead of mapping the whole file\n"
23
+ " quality <model.gguf> TEXTFILE --cache-mb N [--smart J,M,TAU] [--max-tokens N] Teacher-forced perplexity, KL divergence and top-1 agreement of a streamed (optionally SMART-ROUTE) run against exact routing\n"
24
+ " stream <model.gguf> [options] Stream experts from disk through the cache for N tokens of routing (synthetic, or the real router with --trace FILE) and report what was measured\n"
15
25
  "\n"
16
- "stream options:\n"
26
+ "stream options:\n --trace FILE replay the REAL router recorded by `generate --trace` (cyclically) instead of the synthetic one; prefetch then guesses from the previous token's choices\n"
17
27
  " --tokens N (200) --warmup N (20) --cache-mb N (256) --stage N (twice the experts used)\n"
18
28
  " --policy lru|wtinylfu|adaptive (adaptive) --window-pct N --smart J,M,TAU | off (off) --prefetch on|off (on)\n"
19
29
  " --dense auto|resident|ring (auto: resident when the dense weights fit in a quarter of RAM)\n --io-threads N (2) --seed N (1) --locality X (1.0) --predict-corr X (0.8) --compute-us N (0)\n --compute kernel|checksum (kernel: a Q4_0 x Q8_0 dot-product kernel over the loaded bytes; checksum: I/O only)\n"
@@ -50,8 +60,9 @@ static void print_json(const cl_model *m, const cl_stream_opts *o, const cl_stre
50
60
  double wall = t->wall_ns / 1e9, readS = t->read_ns_total / 1e9, wait = t->io_wait_ns / 1e9;
51
61
  double overlap = readS > 0 ? 1.0 - wait / readS : 0;
52
62
  if (overlap < 0) overlap = 0;
53
- if (o->compute_mode == CL_COMPUTE_KERNEL) printf("{\"provenance\":{\"routing\":\"synthetic\",\"disk\":\"measured\",\"compute\":\"Q4_0 x Q8_0 dot-product kernel (%s) run over the loaded bytes, read as Q4_0 whatever they are: a real kernel, not this model's maths\"},", cl_kernel_name());
54
- else printf("{\"provenance\":{\"routing\":\"synthetic\",\"disk\":\"measured\",\"compute\":\"checksum over the loaded bytes, not matrix maths\"},");
63
+ const char *routing = o->trace ? "real" : "synthetic";
64
+ if (o->compute_mode == CL_COMPUTE_KERNEL) printf("{\"provenance\":{\"routing\":\"%s\",\"disk\":\"measured\",\"compute\":\"Q4_0 x Q8_0 dot-product kernel (%s) run over the loaded bytes, read as Q4_0 whatever they are: a real kernel, not this model's maths\"},", routing, cl_kernel_name());
65
+ else printf("{\"provenance\":{\"routing\":\"%s\",\"disk\":\"measured\",\"compute\":\"checksum over the loaded bytes, not matrix maths\"},", routing);
55
66
  printf("\"model\":{\"name\":\"%s\",\"arch\":\"%s\",\"layers\":%u,\"experts\":%u,\"expertsUsed\":%u,\"expertBytes\":%llu},", m->name, m->arch, m->n_layers, m->n_experts, m->n_used, (unsigned long long)r->slot_bytes);
56
67
  printf("\"config\":{\"policy\":\"%s\",\"cacheSlots\":%u,\"stageSlots\":%u,\"smartRoute\":%s,\"J\":%d,\"M\":%d,\"tau\":%g,\"denseMode\":\"%s\",\"densePreloadBytes\":%llu,\"prefetch\":%s,\"ioThreads\":%d,\"osCacheBypass\":%s,\"computeMode\":\"%s\",\"computeKernel\":\"%s\",\"tokens\":%d,\"warmup\":%d,\"seed\":%llu,\"locality\":%g,\"predictCorr\":%g},",
57
68
  o->policy == CL_LRU ? "lru" : o->policy == CL_WTINYLFU ? "wtinylfu" : "adaptive", r->cache_slots, r->stage_slots, o->smart.enabled ? "true" : "false", o->smart.J, o->smart.M, o->smart.tau, r->dense_resident ? "resident" : "ring", (unsigned long long)r->dense_preload_bytes, o->prefetch ? "true" : "false", o->io_threads, o->os_cache_bypass ? "true" : "false", o->compute_mode == CL_COMPUTE_KERNEL ? "kernel" : "checksum", o->compute_mode == CL_COMPUTE_KERNEL ? cl_kernel_name() : "none", o->tokens, o->warmup, (unsigned long long)o->seed, o->locality, o->predict_corr);
@@ -83,7 +94,39 @@ static void print_text(const cl_model *m, const cl_stream_opts *o, const cl_stre
83
94
  printf("MEASURED prefetch: %llu issued, %llu arrived late, %llu never used\n", (unsigned long long)t->prefetch_issued, (unsigned long long)t->prefetch_late, (unsigned long long)t->prefetch_wasted);
84
95
  if (o->smart.enabled) printf("MEASURED smart-route: %llu substitutions, largest probability moved %.4f (limit %g), total %.4f\n", (unsigned long long)t->substituted, t->max_delta, o->smart.tau, t->dropped_mass);
85
96
  if (o->compute_mode == CL_COMPUTE_KERNEL) printf("compute: Q4_0 x Q8_0 dot-product kernel (%s) over every loaded byte, %.2f s in total. The bytes are read as Q4_0 whatever their real type.\n", cl_kernel_name(), t->compute_ns / 1e9);
86
- printf("passes: %d tokens in %.2f s = %.2f passes/s. This is a streaming pass over SYNTHETIC routing; the compute is %s, NOT a transformer, so this is NOT model tokens per second.\n", o->tokens, wall, wall > 0 ? o->tokens / wall : 0.0, o->compute_mode == CL_COMPUTE_KERNEL ? "a real quantized kernel on bytes that are not this model's weights" : "a checksum");
97
+ printf("routing: %s\n", o->trace ? "REAL: the router scores recorded from this model's own forward pass" : "SYNTHETIC");
98
+ printf("passes: %d tokens in %.2f s = %.2f passes/s. This is a streaming pass over %s routing; the compute is %s, NOT this model's transformer, so this is NOT model tokens per second.\n", o->tokens, wall, wall > 0 ? o->tokens / wall : 0.0, o->trace ? "REAL (replayed)" : "SYNTHETIC", o->compute_mode == CL_COMPUTE_KERNEL ? "a real quantized kernel on bytes that are not this model's weights" : "a checksum");
99
+ }
100
+
101
+
102
+ /* Reads a v2 route trace: "token layer score0 ... scoreE-1" per line. Returns tokens x layers x experts floats, or NULL (message printed). */
103
+ static float *load_trace(const char *path, int L, int E, int *tokens) {
104
+ FILE *fp = fopen(path, "r");
105
+ if (!fp) { fprintf(stderr, "cannot open trace %s\n", path); return NULL; }
106
+ char *line = NULL; size_t cap = 0;
107
+ int hl = 0, he = 0, maxtok = -1;
108
+ float *buf = NULL; size_t used = 0, alloc = 0;
109
+ while (getline(&line, &cap, fp) > 0) {
110
+ if (line[0] == '#') {
111
+ if (!strstr(line, "route trace v2")) { fprintf(stderr, "%s is not a v2 route trace (make one with `generate --trace`)\n", path); free(line); fclose(fp); return NULL; }
112
+ char *p = strstr(line, "layers="); if (p) hl = atoi(p + 7);
113
+ p = strstr(line, "experts="); if (p) he = atoi(p + 8);
114
+ if (hl != L || he != E) { fprintf(stderr, "the trace is for %d layers x %d experts, this model has %d x %d\n", hl, he, L, E); free(line); fclose(fp); return NULL; }
115
+ continue;
116
+ }
117
+ if (!hl) { fprintf(stderr, "%s has no header line\n", path); free(line); fclose(fp); return NULL; }
118
+ char *p = line; int t = (int)strtol(p, &p, 10), l = (int)strtol(p, &p, 10);
119
+ if (t != maxtok && t != maxtok + 1) { fprintf(stderr, "trace lines out of order\n"); free(line); fclose(fp); free(buf); return NULL; }
120
+ if (t > maxtok) { maxtok = t; }
121
+ size_t need = ((size_t)t * L + (size_t)l + 1) * E;
122
+ if (need > alloc) { alloc = need * 2; buf = realloc(buf, alloc * sizeof(float)); if (!buf) { free(line); fclose(fp); return NULL; } }
123
+ for (int e = 0; e < E; e++) buf[((size_t)t * L + (size_t)l) * E + e] = strtof(p, &p);
124
+ if (need > used) used = need;
125
+ }
126
+ free(line); fclose(fp);
127
+ if (maxtok < 0 || used != (size_t)(maxtok + 1) * L * E) { fprintf(stderr, "the trace is empty or incomplete\n"); free(buf); return NULL; }
128
+ *tokens = maxtok + 1;
129
+ return buf;
87
130
  }
88
131
 
89
132
  static int cmd_stream(int argc, char **argv) {
@@ -126,6 +169,14 @@ static int cmd_stream(int argc, char **argv) {
126
169
  }
127
170
  o.os_cache_bypass = flag(argc, argv, "--no-os-cache");
128
171
  if (o.tokens < 1 || o.tokens > 1000000 || o.warmup < 0 || o.predict_corr < 0 || o.predict_corr > 1) { fprintf(stderr, "bad --tokens, --warmup or --predict-corr\n"); return 2; }
172
+ float *trace = NULL;
173
+ const char *tp = arg(argc, argv, "--trace");
174
+ if (tp) {
175
+ trace = load_trace(tp, (int)m.n_layers, (int)m.n_experts, &o.trace_tokens);
176
+ if (!trace) return 1;
177
+ if (o.shift_len > 0) { fprintf(stderr, "--shift builds synthetic scan noise and cannot be combined with --trace\n"); return 2; }
178
+ o.trace = trace;
179
+ }
129
180
  cl_stream_result r;
130
181
  int rc = cl_stream_run(&m, argv[0], &o, &r);
131
182
  if (rc) { fprintf(stderr, "%s\n", r.error); cl_stream_result_free(&r); cl_model_close(&m); return 1; }
@@ -135,10 +186,290 @@ static int cmd_stream(int argc, char **argv) {
135
186
  return 0;
136
187
  }
137
188
 
189
+
190
+ static double now_s(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec + t.tv_nsec / 1e9; }
191
+
192
+ /* A JSON string from bytes that may not be valid UTF-8 (a byte-level token can be half a character): bad bytes become U+FFFD, so the output always parses. */
193
+ static void json_str(const char *p, int n) {
194
+ putchar('"');
195
+ for (int i = 0; i < n;) {
196
+ unsigned char c = (unsigned char)p[i];
197
+ if (c == '"' || c == '\\') { printf("\\%c", c); i++; }
198
+ else if (c < 0x20) { printf("\\u%04x", c); i++; }
199
+ else if (c < 0x80) { putchar(c); i++; }
200
+ else {
201
+ int len = c >= 0xF0 && c < 0xF8 ? 4 : c >= 0xE0 ? 3 : c >= 0xC2 && c < 0xE0 ? 2 : 0, ok = len > 0 && i + len <= n && c < 0xF8;
202
+ for (int k = 1; ok && k < len; k++) if (((unsigned char)p[i + k] & 0xC0) != 0x80) ok = 0;
203
+ if (ok) { fwrite(p + i, 1, (size_t)len, stdout); i += len; }
204
+ else { fputs("\xef\xbf\xbd", stdout); i++; }
205
+ }
206
+ }
207
+ putchar('"');
208
+ }
209
+
210
+ static int load_tok(const char *path, cl_meta *meta, uint8_t **map, size_t *msize, cl_tok **tok) {
211
+ char err[256];
212
+ FILE *fp = fopen(path, "rb");
213
+ if (!fp) { fprintf(stderr, "cannot open %s\n", path); return 1; }
214
+ fseek(fp, 0, SEEK_END); long sz = ftell(fp); fclose(fp);
215
+ /* the metadata sits at the front of the file; read only the first part that holds it */
216
+ size_t want = (size_t)sz < ((size_t)64 << 20) ? (size_t)sz : ((size_t)64 << 20);
217
+ *map = malloc(want); *msize = want;
218
+ fp = fopen(path, "rb");
219
+ if (!*map || !fp || fread(*map, 1, want, fp) != want) { if (fp) fclose(fp); fprintf(stderr, "cannot read %s\n", path); return 1; }
220
+ fclose(fp);
221
+ if (cl_meta_parse(meta, *map, want, err, sizeof err)) { fprintf(stderr, "%s\n", err); return 1; }
222
+ *tok = cl_tok_load(meta, err, sizeof err);
223
+ if (!*tok) { fprintf(stderr, "%s\n", err); return 1; }
224
+ return 0;
225
+ }
226
+
227
+ static int cmd_tokenize(int argc, char **argv) {
228
+ if (argc < 2) { usage(); return 2; }
229
+ cl_meta meta; uint8_t *map; size_t ms; cl_tok *tok;
230
+ if (load_tok(argv[0], &meta, &map, &ms, &tok)) return 1;
231
+ size_t len = strlen(argv[1]);
232
+ int32_t *ids = malloc((len + 8) * sizeof *ids);
233
+ int n = cl_tok_encode(tok, argv[1], len, !flag(argc, argv, "--no-special"), ids, (int)len + 8);
234
+ if (n < 0) { fprintf(stderr, "could not tokenize that text\n"); return 1; }
235
+ for (int i = 0; i < n; i++) printf("%s%d", i ? " " : "", ids[i]);
236
+ putchar('\n');
237
+ return 0;
238
+ }
239
+
240
+ /* --cache-mb N turns on streaming: experts come from disk through an N MiB cache. --prefetch on|off, --policy, --io-threads, --no-os-cache tune it. */
241
+ static cl_fwd_opts fwd_opts(int argc, char **argv) {
242
+ cl_fwd_opts o = {0};
243
+ o.cache_bytes = (uint64_t)(num(argc, argv, "--cache-mb", 0) * 1048576.0);
244
+ const char *pf = arg(argc, argv, "--prefetch");
245
+ o.prefetch = pf ? (!strcmp(pf, "on") || !strcmp(pf, "prev") ? 1 : !strcmp(pf, "ahead") ? 2 : 0) : 0; /* off by default: previous-token prefetch lost on every disk-bound measurement (docs/NATIVE_ENGINE.md) */
246
+ const char *pol = arg(argc, argv, "--policy");
247
+ o.policy = !pol || !strcmp(pol, "adaptive") ? CL_ADAPTIVE : !strcmp(pol, "lru") ? CL_LRU : CL_WTINYLFU;
248
+ o.io_threads = (int)num(argc, argv, "--io-threads", 3);
249
+ o.stage_slots = (int)num(argc, argv, "--stage", 0);
250
+ o.prefetch_n = (int)num(argc, argv, "--prefetch-n", 0);
251
+ o.os_bypass = flag(argc, argv, "--no-os-cache");
252
+ const char *sm = arg(argc, argv, "--smart");
253
+ if (sm && strcmp(sm, "off")) {
254
+ int J, M; double tau;
255
+ if (sscanf(sm, "%d,%d,%lf", &J, &M, &tau) != 3 || J < 0 || M < 1 || tau < 0) { fprintf(stderr, "--smart wants J,M,TAU (for example 4,16,0.02) or off\n"); exit(2); }
256
+ o.smart.enabled = 1; o.smart.J = J; o.smart.M = M; o.smart.tau = tau;
257
+ }
258
+ return o;
259
+ }
260
+
261
+ static void report_xs(cl_fwd *f, double secs, int tokens) {
262
+ if (!f->xs) return;
263
+ cl_xs_stats s = cl_xs_get_stats(f->xs);
264
+ struct rusage ru;
265
+ getrusage(RUSAGE_SELF, &ru);
266
+ #if defined(__APPLE__)
267
+ double rss = (double)ru.ru_maxrss / 1048576.0;
268
+ #else
269
+ double rss = (double)ru.ru_maxrss / 1024.0;
270
+ #endif
271
+ if (f->smart.enabled) fprintf(stderr, "[smart-route %d,%d,%g: %llu of %llu expert slots (%.1f%%) used a resident expert instead of the router's own choice; probability moved: largest %.4f (limit %g), total %.3f]\n", f->smart.J, f->smart.M, f->smart.tau, (unsigned long long)f->subs, (unsigned long long)f->expert_slots, f->expert_slots ? 100.0 * f->subs / f->expert_slots : 0.0, f->max_delta, f->smart.tau, f->dropped_mass);
272
+ fprintf(stderr, "[streaming: cache %u experts of %llu bytes; %llu accesses, %.1f%% hits (%llu from prefetch), %llu misses, prefetch %llu issued / %llu never used (%.0f%% of predicted experts were chosen); read %.2f GB (%.2f GB/token) in %.2f s of read time; compute thread waited %.2f s of %.2f s; peak RSS %.0f MiB]\n",
273
+ cl_xs_cache_slots(f->xs), (unsigned long long)cl_xs_slot_bytes(f->xs), (unsigned long long)s.accesses, s.accesses ? 100.0 * s.hits / s.accesses : 0.0, (unsigned long long)s.stage_hits, (unsigned long long)s.misses,
274
+ (unsigned long long)s.prefetch_issued, (unsigned long long)s.prefetch_wasted, f->pred_total ? 100.0 * f->pred_hit / f->pred_total : 0.0, (s.demand_bytes + s.prefetch_bytes) / 1e9, tokens ? (s.demand_bytes + s.prefetch_bytes) / 1e9 / tokens : 0.0, s.read_ns / 1e9, s.wait_ns / 1e9, secs, rss);
275
+ }
276
+
277
+ /* One line per (token, layer): "token layer" then the router's raw score for every expert. */
278
+ static void write_trace(FILE *tf, const cl_fwd *f, const cl_fwd_route *rt, int tok) {
279
+ for (int l = 0; l < f->n_layer; l++) {
280
+ fprintf(tf, "%d %d", tok, l);
281
+ for (int e = 0; e < f->n_expert; e++) fprintf(tf, " %.6g", rt->logits[(size_t)l * f->n_expert + e]);
282
+ fputc('\n', tf);
283
+ }
284
+ }
285
+
286
+ static int cmd_logits(int argc, char **argv) {
287
+ if (argc < 2) { usage(); return 2; }
288
+ char err[256];
289
+ cl_meta meta; uint8_t *map; size_t ms; cl_tok *tok;
290
+ if (load_tok(argv[0], &meta, &map, &ms, &tok)) return 1;
291
+ size_t len = strlen(argv[1]);
292
+ int cap = (int)len + 8;
293
+ int32_t *ids = malloc((size_t)cap * sizeof *ids);
294
+ int n = 0;
295
+ if (cl_tok_add_bos(tok) && cl_tok_bos(tok) >= 0) ids[n++] = cl_tok_bos(tok);
296
+ int k = cl_tok_encode(tok, argv[1], len, 1, ids + n, cap - n);
297
+ if (k < 0) { fprintf(stderr, "could not tokenize the prompt\n"); return 1; }
298
+ n += k;
299
+ if (n == 0) { fprintf(stderr, "the prompt has no tokens\n"); return 1; }
300
+ int ctx = (int)num(argc, argv, "--ctx", n + 8), top = (int)num(argc, argv, "--top", 20);
301
+ cl_fwd f;
302
+ cl_fwd_opts fo = fwd_opts(argc, argv);
303
+ if (cl_fwd_open(&f, argv[0], ctx, &fo, err, sizeof err)) { fprintf(stderr, "%s\n", err); return 1; }
304
+ float *logits = malloc((size_t)f.n_vocab * sizeof *logits);
305
+ cl_fwd_route rt = {malloc((size_t)f.n_layer * f.n_used * sizeof(int)), malloc((size_t)f.n_layer * f.n_used * sizeof(float)), malloc((size_t)f.n_layer * f.n_expert * sizeof(float)), f.n_layer, f.n_used};
306
+ const char *tr = arg(argc, argv, "--trace");
307
+ FILE *tf = tr ? fopen(tr, "w") : NULL;
308
+ if (tf) fprintf(tf, "# clutor route trace v2 model=%s layers=%d experts=%d used=%d\n", f.model.name, f.n_layer, f.n_expert, f.n_used);
309
+ double t0 = now_s();
310
+ for (int i = 0; i < n; i++) {
311
+ if (cl_fwd_step(&f, ids[i], logits, &rt, err, sizeof err)) { fprintf(stderr, "%s\n", err); return 1; }
312
+ if (tf) write_trace(tf, &f, &rt, i);
313
+ }
314
+ double secs = now_s() - t0;
315
+ if (tf) fclose(tf);
316
+ double mx = logits[0];
317
+ for (int i = 1; i < f.n_vocab; i++) if (logits[i] > mx) mx = logits[i];
318
+ double sum = 0;
319
+ for (int i = 0; i < f.n_vocab; i++) sum += exp((double)logits[i] - mx);
320
+ double lse = mx + log(sum);
321
+ printf("{\"tokens\":[");
322
+ for (int i = 0; i < n; i++) printf("%s%d", i ? "," : "", ids[i]);
323
+ printf("],\"seconds\":%.3f,\"secondsPerToken\":%.3f,\"threads\":%d,\"top\":[", secs, secs / n, cl_get_threads());
324
+ char *used = calloc((size_t)f.n_vocab, 1);
325
+ for (int r = 0; r < top && r < f.n_vocab; r++) {
326
+ int b = -1;
327
+ for (int i = 0; i < f.n_vocab; i++) if (!used[i] && (b < 0 || logits[i] > logits[b])) b = i;
328
+ used[b] = 1;
329
+ char piece[256];
330
+ int pl = cl_tok_piece(tok, b, piece, sizeof piece);
331
+ printf("%s{\"id\":%d,\"logit\":%.4f,\"logprob\":%.4f,\"piece\":", r ? "," : "", b, logits[b], logits[b] - lse);
332
+ json_str(piece, pl);
333
+ putchar('}');
334
+ }
335
+ puts("]}");
336
+ cl_fwd_close(&f);
337
+ return 0;
338
+ }
339
+
340
+ /* Greedy decoding with the real forward pass: the text and the routing both come from the model itself. */
341
+ static int cmd_generate(int argc, char **argv) {
342
+ if (argc < 2) { usage(); return 2; }
343
+ char err[256];
344
+ cl_meta meta; uint8_t *map; size_t ms; cl_tok *tok;
345
+ if (load_tok(argv[0], &meta, &map, &ms, &tok)) return 1;
346
+ size_t len = strlen(argv[1]);
347
+ int want = (int)num(argc, argv, "--n", 32), cap = (int)len + 8;
348
+ int32_t *ids = malloc((size_t)cap * sizeof *ids);
349
+ int n = 0;
350
+ if (cl_tok_add_bos(tok) && cl_tok_bos(tok) >= 0) ids[n++] = cl_tok_bos(tok);
351
+ int k = cl_tok_encode(tok, argv[1], len, 1, ids + n, cap - n);
352
+ if (k < 0) { fprintf(stderr, "could not tokenize the prompt\n"); return 1; }
353
+ n += k;
354
+ if (n == 0 || want < 1) { fprintf(stderr, "need a prompt and --n >= 1\n"); return 1; }
355
+ cl_fwd f;
356
+ cl_fwd_opts fo = fwd_opts(argc, argv);
357
+ if (cl_fwd_open(&f, argv[0], n + want + 1, &fo, err, sizeof err)) { fprintf(stderr, "%s\n", err); return 1; }
358
+ float *logits = malloc((size_t)f.n_vocab * sizeof *logits);
359
+ cl_fwd_route rt = {malloc((size_t)f.n_layer * f.n_used * sizeof(int)), malloc((size_t)f.n_layer * f.n_used * sizeof(float)), malloc((size_t)f.n_layer * f.n_expert * sizeof(float)), f.n_layer, f.n_used};
360
+ const char *tr = arg(argc, argv, "--trace");
361
+ FILE *tf = tr ? fopen(tr, "w") : NULL;
362
+ if (tf) fprintf(tf, "# clutor route trace v2 model=%s layers=%d experts=%d used=%d\n", f.model.name, f.n_layer, f.n_expert, f.n_used);
363
+ int quiet = flag(argc, argv, "--quiet");
364
+ if (!quiet) { fputs(argv[1], stdout); fflush(stdout); }
365
+ double t0 = now_s();
366
+ int fed = 0, gen = 0, cur = ids[0];
367
+ int *outids = malloc((size_t)(want + 2) * sizeof(int));
368
+ int nout = 0;
369
+ for (;;) {
370
+ if (cl_fwd_step(&f, cur, logits, &rt, err, sizeof err)) { fprintf(stderr, "\n%s\n", err); return 1; }
371
+ if (tf) write_trace(tf, &f, &rt, fed);
372
+ fed++;
373
+ if (fed < n) { cur = ids[fed]; continue; } /* still reading the prompt */
374
+ int b = 0;
375
+ for (int i = 1; i < f.n_vocab; i++) if (logits[i] > logits[b]) b = i;
376
+ gen++;
377
+ if (b == cl_tok_eos(tok) || gen > want) break;
378
+ char piece[256];
379
+ int pl = cl_tok_piece(tok, b, piece, sizeof piece);
380
+ if (!quiet) { fwrite(piece, 1, (size_t)pl, stdout); fflush(stdout); }
381
+ cur = b;
382
+ outids[nout++] = b;
383
+ if (gen == want) break;
384
+ }
385
+ double secs = now_s() - t0;
386
+ if (tf) fclose(tf);
387
+ const char *idf = arg(argc, argv, "--ids"); /* the token ids in the format colibri's olmoe harness reads (ref.json): prompt and prompt + generated */
388
+ if (idf) {
389
+ FILE *jf = fopen(idf, "w");
390
+ if (jf) {
391
+ fprintf(jf, "{\n \"prompt_ids\": [");
392
+ for (int i = 0; i < n; i++) fprintf(jf, "%s%d", i ? ", " : "", ids[i]);
393
+ fprintf(jf, "],\n \"full_ids\": [");
394
+ for (int i = 0; i < n; i++) fprintf(jf, "%s%d", i ? ", " : "", ids[i]);
395
+ for (int i = 0; i < nout; i++) fprintf(jf, ", %d", outids[i]);
396
+ fprintf(jf, "]\n}\n");
397
+ fclose(jf);
398
+ }
399
+ }
400
+ fprintf(stderr, "\n[%d tokens fed (%d prompt + %d generated) in %.1f s = %.2f s/token on %d threads, %s]\n", fed, n, fed - n, secs, secs / fed, cl_get_threads(), tr ? "route trace written" : "no trace");
401
+ report_xs(&f, secs, fed);
402
+ cl_fwd_close(&f);
403
+ return 0;
404
+ }
405
+
406
+ static double logprob_of(const float *logits, int n, int tok, double *lse_out) {
407
+ double mx = logits[0];
408
+ for (int i = 1; i < n; i++) if (logits[i] > mx) mx = logits[i];
409
+ double s = 0;
410
+ for (int i = 0; i < n; i++) s += exp((double)logits[i] - mx);
411
+ double lse = mx + log(s);
412
+ if (lse_out) *lse_out = lse;
413
+ return (double)logits[tok] - lse;
414
+ }
415
+
416
+ /* Teacher-forced comparison of exact routing (the whole file mapped) with the configured run (streamed experts, optionally SMART-ROUTE) on a text file. */
417
+ static int cmd_quality(int argc, char **argv) {
418
+ if (argc < 2) { usage(); return 2; }
419
+ char err[256];
420
+ cl_meta meta; uint8_t *map; size_t ms; cl_tok *tok;
421
+ if (load_tok(argv[0], &meta, &map, &ms, &tok)) return 1;
422
+ FILE *tf = fopen(argv[1], "rb");
423
+ if (!tf) { fprintf(stderr, "cannot open %s\n", argv[1]); return 1; }
424
+ fseek(tf, 0, SEEK_END); long tsz = ftell(tf); fseek(tf, 0, SEEK_SET);
425
+ char *text = malloc((size_t)tsz + 1);
426
+ if (!text || fread(text, 1, (size_t)tsz, tf) != (size_t)tsz) { fprintf(stderr, "cannot read %s\n", argv[1]); return 1; }
427
+ fclose(tf);
428
+ int maxtok = (int)num(argc, argv, "--max-tokens", 512);
429
+ int32_t *ids = malloc(((size_t)tsz + 8) * sizeof *ids);
430
+ int n = cl_tok_encode(tok, text, (size_t)tsz, 0, ids, (int)tsz + 8);
431
+ if (n < 2) { fprintf(stderr, "the text has fewer than 2 tokens\n"); return 1; }
432
+ if (n > maxtok) n = maxtok;
433
+ cl_fwd a, b;
434
+ cl_fwd_opts none = {0}, fo = fwd_opts(argc, argv);
435
+ if (!fo.cache_bytes) { fprintf(stderr, "quality compares against streamed runs: give --cache-mb N\n"); return 2; }
436
+ if (cl_fwd_open(&a, argv[0], n + 1, &none, err, sizeof err) || cl_fwd_open(&b, argv[0], n + 1, &fo, err, sizeof err)) { fprintf(stderr, "%s\n", err); return 1; }
437
+ float *la = malloc((size_t)a.n_vocab * sizeof *la), *lb = malloc((size_t)a.n_vocab * sizeof *lb);
438
+ double nllA = 0, nllB = 0, kl = 0;
439
+ int agree = 0, used = 0;
440
+ double t0 = now_s();
441
+ for (int i = 0; i + 1 < n; i++) {
442
+ if (cl_fwd_step(&a, ids[i], la, NULL, err, sizeof err) || cl_fwd_step(&b, ids[i], lb, NULL, err, sizeof err)) { fprintf(stderr, "%s\n", err); return 1; }
443
+ double lseA, lseB;
444
+ double pa = logprob_of(la, a.n_vocab, ids[i + 1], &lseA), pb = logprob_of(lb, a.n_vocab, ids[i + 1], &lseB);
445
+ nllA -= pa; nllB -= pb;
446
+ double k = 0;
447
+ int ba = 0, bb = 0;
448
+ for (int v = 0; v < a.n_vocab; v++) {
449
+ double lpa = (double)la[v] - lseA, lpb = (double)lb[v] - lseB;
450
+ k += exp(lpa) * (lpa - lpb);
451
+ if (la[v] > la[ba]) ba = v;
452
+ if (lb[v] > lb[bb]) bb = v;
453
+ }
454
+ kl += k; agree += ba == bb; used++;
455
+ }
456
+ double secs = now_s() - t0;
457
+ cl_xs_stats s = cl_xs_get_stats(b.xs);
458
+ printf("{\"tokensScored\":%d,\"perplexityExact\":%.4f,\"perplexityRun\":%.4f,\"perplexityRatio\":%.4f,\"meanKL\":%.5f,\"top1Agreement\":%.4f,", used, exp(nllA / used), exp(nllB / used), exp((nllB - nllA) / used), kl / used, (double)agree / used);
459
+ printf("\"smart\":%s,\"substitutions\":%llu,\"expertSlots\":%llu,\"maxProbMoved\":%.5f,\"totalProbMoved\":%.4f,", b.smart.enabled ? "true" : "false", (unsigned long long)b.subs, (unsigned long long)b.expert_slots, b.max_delta, b.dropped_mass);
460
+ printf("\"cacheSlots\":%u,\"hitRate\":%.4f,\"gbPerToken\":%.4f,\"seconds\":%.1f,\"note\":\"the exact run maps the whole file, so the OS cache is warm: seconds here say nothing about disk speed\"}\n",
461
+ cl_xs_cache_slots(b.xs), s.accesses ? (double)s.hits / s.accesses : 0.0, (s.demand_bytes + s.prefetch_bytes) / 1e9 / used, secs);
462
+ return 0;
463
+ }
464
+
138
465
  int main(int argc, char **argv) {
139
466
  if (argc < 2) { usage(); return 2; }
140
467
  if (!strcmp(argv[1], "--version")) { puts(VERSION); return 0; }
141
468
  if (!strcmp(argv[1], "info")) return cmd_info(argc - 2, argv + 2);
469
+ if (!strcmp(argv[1], "tokenize")) return cmd_tokenize(argc - 2, argv + 2);
470
+ if (!strcmp(argv[1], "quality")) return cmd_quality(argc - 2, argv + 2);
471
+ if (!strcmp(argv[1], "generate")) return cmd_generate(argc - 2, argv + 2);
472
+ if (!strcmp(argv[1], "logits")) return cmd_logits(argc - 2, argv + 2);
142
473
  if (!strcmp(argv[1], "stream")) return cmd_stream(argc - 2, argv + 2);
143
474
  usage();
144
475
  return strcmp(argv[1], "--help") ? 2 : 0;
package/c/src/meta.c ADDED
@@ -0,0 +1,118 @@
1
+ #include "meta.h"
2
+ #include <stdio.h>
3
+ #include <stdlib.h>
4
+ #include <string.h>
5
+
6
+ typedef struct { const uint8_t *p; size_t n, pos; int bad; } cur;
7
+ static int need(cur *c, size_t k) { if (c->bad || c->n - c->pos < k) { c->bad = 1; return 0; } return 1; }
8
+ static uint32_t r32(cur *c) { uint32_t v = 0; if (need(c, 4)) { memcpy(&v, c->p + c->pos, 4); c->pos += 4; } return v; }
9
+ static uint64_t r64(cur *c) { uint64_t v = 0; if (need(c, 8)) { memcpy(&v, c->p + c->pos, 8); c->pos += 8; } return v; }
10
+
11
+ static size_t scalar_size(uint32_t t) {
12
+ switch (t) { case 0: case 1: case 7: return 1; case 2: case 3: return 2; case 4: case 5: case 6: return 4; case 10: case 11: case 12: return 8; default: return 0; }
13
+ }
14
+
15
+ static void skip_str(cur *c) { uint64_t len = r64(c); if (!c->bad && need(c, (size_t)len)) c->pos += (size_t)len; }
16
+
17
+ int cl_meta_parse(cl_meta *m, const uint8_t *base, size_t size, char *err, size_t errlen) {
18
+ memset(m, 0, sizeof *m);
19
+ m->base = base; m->size = size;
20
+ cur c = {base, size, 0, 0};
21
+ if (size < 24 || memcmp(base, "GGUF", 4)) { snprintf(err, errlen, "not a GGUF file"); return 1; }
22
+ c.pos = 4;
23
+ uint32_t ver = r32(&c);
24
+ (void)r64(&c);
25
+ uint64_t nkv = r64(&c);
26
+ if (ver != 2 && ver != 3) { snprintf(err, errlen, "unsupported GGUF version %u", ver); return 1; }
27
+ if (nkv > 100000) { snprintf(err, errlen, "implausible metadata count"); return 1; }
28
+ m->kv = calloc(nkv ? nkv : 1, sizeof(cl_kv));
29
+ if (!m->kv) { snprintf(err, errlen, "out of memory"); return 1; }
30
+ for (uint64_t i = 0; i < nkv; i++) {
31
+ cl_kv *k = &m->kv[m->n];
32
+ uint64_t kl = r64(&c);
33
+ if (c.bad || kl > 1u << 20 || !need(&c, (size_t)kl)) break;
34
+ size_t cp = kl < sizeof k->key - 1 ? (size_t)kl : sizeof k->key - 1;
35
+ memcpy(k->key, c.p + c.pos, cp); k->key[cp] = 0; c.pos += (size_t)kl;
36
+ k->type = r32(&c);
37
+ if (k->type == 9) { k->etype = r32(&c); k->count = r64(&c); }
38
+ k->off = c.pos;
39
+ if (c.bad) break;
40
+ if (k->type == 9) {
41
+ size_t es = scalar_size(k->etype);
42
+ if (es) { if (k->count > (c.n - c.pos) / es) { c.bad = 1; break; } c.pos += (size_t)k->count * es; }
43
+ else if (k->etype == 8) for (uint64_t j = 0; j < k->count && !c.bad; j++) skip_str(&c);
44
+ else { c.bad = 1; break; }
45
+ } else if (k->type == 8) skip_str(&c);
46
+ else { size_t s = scalar_size(k->type); if (!s || !need(&c, s)) { c.bad = 1; break; } c.pos += s; }
47
+ if (c.bad) break;
48
+ m->n++;
49
+ }
50
+ if (c.bad) { cl_meta_free(m); snprintf(err, errlen, "corrupt metadata"); return 1; }
51
+ return 0;
52
+ }
53
+
54
+ void cl_meta_free(cl_meta *m) { free(m->kv); memset(m, 0, sizeof *m); }
55
+
56
+ const cl_kv *cl_meta_get(const cl_meta *m, const char *key) {
57
+ for (int i = 0; i < m->n; i++) if (!strcmp(m->kv[i].key, key)) return &m->kv[i];
58
+ return NULL;
59
+ }
60
+
61
+ int cl_meta_f64(const cl_meta *m, const char *key, double *out) {
62
+ const cl_kv *k = cl_meta_get(m, key);
63
+ if (!k || k->type == 8 || k->type == 9) return 0;
64
+ const uint8_t *p = m->base + k->off;
65
+ switch (k->type) {
66
+ case 0: *out = p[0]; return 1;
67
+ case 1: *out = (int8_t)p[0]; return 1;
68
+ case 2: { uint16_t v; memcpy(&v, p, 2); *out = v; return 1; }
69
+ case 3: { int16_t v; memcpy(&v, p, 2); *out = v; return 1; }
70
+ case 4: { uint32_t v; memcpy(&v, p, 4); *out = v; return 1; }
71
+ case 5: { int32_t v; memcpy(&v, p, 4); *out = v; return 1; }
72
+ case 6: { float v; memcpy(&v, p, 4); *out = v; return 1; }
73
+ case 7: *out = p[0] != 0; return 1;
74
+ case 10: { uint64_t v; memcpy(&v, p, 8); *out = (double)v; return 1; }
75
+ case 11: { int64_t v; memcpy(&v, p, 8); *out = (double)v; return 1; }
76
+ case 12: { double v; memcpy(&v, p, 8); *out = v; return 1; }
77
+ default: return 0;
78
+ }
79
+ }
80
+ int cl_meta_u64(const cl_meta *m, const char *key, uint64_t *out) {
81
+ double d;
82
+ if (!cl_meta_f64(m, key, &d) || d < 0) return 0;
83
+ *out = (uint64_t)d;
84
+ return 1;
85
+ }
86
+
87
+ int cl_meta_str(const cl_meta *m, const char *key, cl_str *out) {
88
+ const cl_kv *k = cl_meta_get(m, key);
89
+ if (!k || k->type != 8) return 0;
90
+ uint64_t len; memcpy(&len, m->base + k->off, 8);
91
+ out->p = (const char *)m->base + k->off + 8; out->len = len;
92
+ return 1;
93
+ }
94
+
95
+ cl_str *cl_meta_str_array(const cl_meta *m, const char *key, uint64_t *n) {
96
+ const cl_kv *k = cl_meta_get(m, key);
97
+ if (!k || k->type != 9 || k->etype != 8) return NULL;
98
+ cl_str *a = calloc(k->count ? k->count : 1, sizeof *a);
99
+ if (!a) return NULL;
100
+ size_t pos = k->off;
101
+ for (uint64_t i = 0; i < k->count; i++) {
102
+ uint64_t len; memcpy(&len, m->base + pos, 8);
103
+ a[i].p = (const char *)m->base + pos + 8; a[i].len = len;
104
+ pos += 8 + (size_t)len;
105
+ }
106
+ *n = k->count;
107
+ return a;
108
+ }
109
+
110
+ int32_t *cl_meta_i32_array(const cl_meta *m, const char *key, uint64_t *n) {
111
+ const cl_kv *k = cl_meta_get(m, key);
112
+ if (!k || k->type != 9 || (k->etype != 5 && k->etype != 4)) return NULL;
113
+ int32_t *a = malloc((k->count ? k->count : 1) * sizeof *a);
114
+ if (!a) return NULL;
115
+ memcpy(a, m->base + k->off, (size_t)k->count * 4);
116
+ *n = k->count;
117
+ return a;
118
+ }
package/c/src/meta.h ADDED
@@ -0,0 +1,22 @@
1
+ /* Reads the key/value metadata of a memory-mapped GGUF file: the hyper-parameters and the tokenizer tables the forward pass needs. */
2
+ #ifndef CL_META_H
3
+ #define CL_META_H
4
+ #include <stddef.h>
5
+ #include <stdint.h>
6
+
7
+ typedef struct { const char *p; uint64_t len; } cl_str; /* a view into the mapped file, not NUL-terminated */
8
+ typedef struct { char key[128]; uint32_t type, etype; uint64_t count; size_t off; } cl_kv; /* off: the value, or the first element of an array */
9
+ typedef struct { const uint8_t *base; size_t size; cl_kv *kv; int n; } cl_meta;
10
+
11
+ int cl_meta_parse(cl_meta *m, const uint8_t *base, size_t size, char *err, size_t errlen);
12
+ void cl_meta_free(cl_meta *m);
13
+ const cl_kv *cl_meta_get(const cl_meta *m, const char *key);
14
+ /* Scalars: return 1 and set *out when the key exists and has a numeric (or bool) type. */
15
+ int cl_meta_u64(const cl_meta *m, const char *key, uint64_t *out);
16
+ int cl_meta_f64(const cl_meta *m, const char *key, double *out);
17
+ /* A string value: returns 1 and fills *out. */
18
+ int cl_meta_str(const cl_meta *m, const char *key, cl_str *out);
19
+ /* Arrays: malloc'd, the caller frees. NULL when absent or of another type. */
20
+ cl_str *cl_meta_str_array(const cl_meta *m, const char *key, uint64_t *n);
21
+ int32_t *cl_meta_i32_array(const cl_meta *m, const char *key, uint64_t *n);
22
+ #endif