@clutor/cli 0.1.0 → 0.1.1

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/stream.c ADDED
@@ -0,0 +1,396 @@
1
+ #define _GNU_SOURCE
2
+ #include "stream.h"
3
+ #include "kernel.h"
4
+ #include <errno.h>
5
+ #include <fcntl.h>
6
+ #include <math.h>
7
+ #include <pthread.h>
8
+ #include <stdio.h>
9
+ #include <stdlib.h>
10
+ #include <string.h>
11
+ #include <sys/resource.h>
12
+ #include <time.h>
13
+ #include <unistd.h>
14
+
15
+ static uint64_t phys_ram_bytes(void) {
16
+ long pages = sysconf(_SC_PHYS_PAGES), sz = sysconf(_SC_PAGE_SIZE);
17
+ return pages > 0 && sz > 0 ? (uint64_t)pages * (uint64_t)sz : 0;
18
+ }
19
+
20
+ static uint64_t now_ns(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; }
21
+ static uint64_t mix64(uint64_t x) { x += 0x9e3779b97f4a7c15ull; x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ull; x = (x ^ (x >> 27)) * 0x94d049bb133111ebull; return x ^ (x >> 31); }
22
+ static double unit(uint64_t x) { return (double)(mix64(x) >> 11) * (1.0 / 9007199254740992.0); }
23
+ static double gauss(uint64_t x) { return (unit(x) + unit(x + 1) + unit(x + 2) + unit(x + 3) - 2.0) * 1.7320508; }
24
+
25
+ enum { S_EMPTY = 0, S_LOADING = 1, S_READY = 2 };
26
+ enum { J_EXPERT = 1, J_DENSE = 2 };
27
+ typedef struct job { int kind, slot, layer, expert, ring, prefetch; struct job *next; } job;
28
+
29
+ typedef struct {
30
+ const cl_model *m;
31
+ int fd;
32
+ int bypass_os_cache;
33
+ pthread_mutex_t mu;
34
+ pthread_cond_t cv_work, cv_done;
35
+ job *dq_head, *dq_tail, *pq_head, *pq_tail; /* demand and prefetch queues */
36
+ int shutdown, failed;
37
+ char error[256];
38
+ cl_cache *cache;
39
+ uint8_t **buf; /* one buffer per slot, plus bypass buffers after them; all inside `arena` */
40
+ uint8_t *arena;
41
+ int *state;
42
+ int slot_bytes;
43
+ uint8_t *ring[2];
44
+ uint8_t **dense_res; /* per layer, when the dense weights are resident */
45
+ int64_t ring_step[2];
46
+ cl_stream_totals t;
47
+ } eng;
48
+
49
+ static void push_job(eng *e, job *j, int demand) {
50
+ job **h = demand ? &e->dq_head : &e->pq_head, **t = demand ? &e->dq_tail : &e->pq_tail;
51
+ j->next = NULL;
52
+ if (*t) (*t)->next = j; else *h = j;
53
+ *t = j;
54
+ pthread_cond_signal(&e->cv_work);
55
+ }
56
+ static job *pop_job(eng *e) {
57
+ job **h = e->dq_head ? &e->dq_head : &e->pq_head, **t = e->dq_head ? &e->dq_tail : &e->pq_tail;
58
+ job *j = *h;
59
+ if (!j) return NULL;
60
+ *h = j->next;
61
+ if (!*h) *t = NULL;
62
+ return j;
63
+ }
64
+
65
+ static int read_full(eng *e, uint8_t *dst, uint64_t len, uint64_t off) {
66
+ uint64_t done = 0;
67
+ while (done < len) {
68
+ ssize_t n = pread(e->fd, dst + done, (size_t)(len - done), (off_t)(off + done));
69
+ if (n < 0 && errno == EINTR) continue;
70
+ if (n <= 0) return -1;
71
+ done += (uint64_t)n;
72
+ }
73
+ #if defined(__linux__)
74
+ if (e->bypass_os_cache) posix_fadvise(e->fd, (off_t)off, (off_t)len, POSIX_FADV_DONTNEED);
75
+ #endif
76
+ return 0;
77
+ }
78
+
79
+ static void *worker(void *arg) {
80
+ eng *e = arg;
81
+ pthread_mutex_lock(&e->mu);
82
+ for (;;) {
83
+ job *j = pop_job(e);
84
+ if (!j) { if (e->shutdown) break; pthread_cond_wait(&e->cv_work, &e->mu); continue; }
85
+ pthread_mutex_unlock(&e->mu);
86
+ uint64_t t0 = now_ns();
87
+ int rc = 0;
88
+ uint64_t bytes = 0;
89
+ if (j->kind == J_EXPERT) {
90
+ const cl_layer *L = &e->m->layers[j->layer];
91
+ uint8_t *dst = e->buf[j->slot];
92
+ uint64_t at = 0;
93
+ for (int i = 0; i < L->n_exp && !rc; i++) {
94
+ rc = read_full(e, dst + at, L->exp_slab[i], L->exp_off[i] + (uint64_t)j->expert * L->exp_slab[i]);
95
+ at += L->exp_slab[i];
96
+ }
97
+ bytes = L->expert_bytes;
98
+ } else {
99
+ const cl_layer *L = &e->m->layers[j->layer];
100
+ uint8_t *dst = e->ring[j->ring];
101
+ uint64_t at = 0;
102
+ for (int i = 0; i < L->n_dense && !rc; i++) { rc = read_full(e, dst + at, L->dense[i].length, L->dense[i].offset); at += L->dense[i].length; }
103
+ bytes = L->dense_bytes;
104
+ }
105
+ uint64_t dt = now_ns() - t0;
106
+ pthread_mutex_lock(&e->mu);
107
+ e->t.read_ns_total += dt;
108
+ if (rc) { e->failed = 1; snprintf(e->error, sizeof e->error, "read failed (%s) for layer %d", strerror(errno), j->layer); }
109
+ if (j->kind == J_EXPERT) {
110
+ e->state[j->slot] = rc ? S_EMPTY : S_READY;
111
+ if (j->prefetch) { e->t.prefetch_bytes += bytes; cl_cache_unpin(e->cache, j->slot); } else e->t.demand_bytes += bytes;
112
+ } else { e->t.dense_bytes += bytes; e->ring_step[j->ring] = j->prefetch; /* prefetch field carries the step for dense jobs */ }
113
+ pthread_cond_broadcast(&e->cv_done);
114
+ free(j);
115
+ }
116
+ pthread_mutex_unlock(&e->mu);
117
+ return NULL;
118
+ }
119
+
120
+ /* ---- synthetic router: deterministic, skewed, with topics that drift; labelled SYNTHETIC wherever it is reported ---- */
121
+ typedef struct { int L, E, topics; float *base, *bonus; double sigma; uint64_t seed; double rho; } router;
122
+ #define TOPIC_LEN 16
123
+ static void router_init(router *r, int L, int E, double locality, uint64_t seed, double rho) {
124
+ r->L = L; r->E = E; r->topics = 8; r->sigma = 1.0; r->seed = seed; r->rho = rho;
125
+ r->base = malloc((size_t)L * E * sizeof(float));
126
+ r->bonus = calloc((size_t)r->topics * L * E, sizeof(float));
127
+ int *perm = malloc((size_t)E * sizeof(int));
128
+ for (int l = 0; l < L; l++) {
129
+ for (int i = 0; i < E; i++) perm[i] = i;
130
+ for (int i = E - 1; i > 0; i--) { int j = (int)(mix64(seed ^ ((uint64_t)l << 20) ^ (uint64_t)i) % (uint64_t)(i + 1)); int t = perm[i]; perm[i] = perm[j]; perm[j] = t; }
131
+ for (int rank = 0; rank < E; rank++) r->base[(size_t)l * E + perm[rank]] = (float)(-3.0 * locality * log(rank + 1.0) / log(E));
132
+ for (int tp = 0; tp < r->topics; tp++)
133
+ for (int k = 0; k < 12 && k < E; k++) r->bonus[((size_t)tp * L + l) * E + (int)(mix64(seed ^ 0xabcdef ^ ((uint64_t)tp << 32) ^ ((uint64_t)l << 16) ^ (uint64_t)k) % (uint64_t)E)] += 1.5f;
134
+ }
135
+ free(perm);
136
+ }
137
+ /* mode 0: topics drift every TOPIC_LEN tokens; 1: one fixed topic (a tight cluster); 2: no structure at all (uniform noise) */
138
+ static void router_logits(const router *r, long t, int l, int predicted, int mode, float *out) {
139
+ int tp = mode == 1 ? 0 : (int)((t / TOPIC_LEN) % r->topics);
140
+ const float *b = r->base + (size_t)l * r->E, *bo = r->bonus + ((size_t)tp * r->L + l) * r->E;
141
+ for (int e = 0; e < r->E; e++) {
142
+ uint64_t k = r->seed ^ ((uint64_t)t << 24) ^ ((uint64_t)l << 12) ^ (uint64_t)e * 0x9e3779b1ull;
143
+ double n1 = gauss(k * 4 + 1);
144
+ double n = n1;
145
+ if (predicted) n = mode == 2 ? gauss(k * 4 + 7777) /* scan noise has no structure to predict: the predictor gets its own, independent draw */ : r->rho * n1 + sqrt(fmax(0.0, 1.0 - r->rho * r->rho)) * gauss(k * 4 + 7777);
146
+ out[e] = mode == 2 ? (float)(r->sigma * n) : (float)(b[e] + bo[e] + r->sigma * n);
147
+ }
148
+ }
149
+
150
+ #define KERNEL_COLS 1024 /* activation length: 32 Q8_0 blocks; each 576 bytes of streamed data is one row */
151
+ typedef struct { int kernel; cl_block_q8_0 act[KERNEL_COLS / CL_QK]; } compute_ctx;
152
+
153
+ static void compute_init(compute_ctx *cc, int mode, uint64_t seed) {
154
+ float x[KERNEL_COLS];
155
+ uint64_t s = seed * 6364136223846793005ull + 1442695040888963407ull;
156
+ for (int i = 0; i < KERNEL_COLS; i++) { s = s * 6364136223846793005ull + 1442695040888963407ull; x[i] = (float)((int64_t)(s >> 40) - (1 << 23)) / (float)(1 << 23); }
157
+ cl_quantize_q8_0(x, cc->act, KERNEL_COLS);
158
+ cc->kernel = mode == CL_COMPUTE_KERNEL;
159
+ }
160
+
161
+ static uint64_t touch_bytes(const uint8_t *p, uint64_t n) {
162
+ uint64_t s = 0;
163
+ const uint64_t *w = (const uint64_t *)(const void *)p;
164
+ uint64_t nw = n / 8;
165
+ for (uint64_t i = 0; i < nw; i++) s += w[i] * 0x9e3779b97f4a7c15ull;
166
+ for (uint64_t i = nw * 8; i < n; i++) s += p[i];
167
+ return s;
168
+ }
169
+
170
+ static uint64_t compute_bytes(const compute_ctx *cc, const uint8_t *p, uint64_t n) {
171
+ return cc->kernel ? cl_q4_0_pass(p, n, cc->act, KERNEL_COLS / CL_QK) : touch_bytes(p, n);
172
+ }
173
+
174
+ typedef struct { eng *e; int layer, E; } resctx;
175
+ static int resident_cb(void *ctx, int expert) {
176
+ resctx *c = ctx;
177
+ return cl_cache_find(c->e->cache, (uint32_t)(c->layer * c->E + expert)) >= 0;
178
+ }
179
+
180
+ static void wait_slot(eng *e, int slot, uint64_t *wait_ns) {
181
+ uint64_t t0 = now_ns();
182
+ int waited = 0;
183
+ while (e->state[slot] != S_READY && !e->failed) { waited = 1; pthread_cond_wait(&e->cv_done, &e->mu); }
184
+ if (waited) *wait_ns += now_ns() - t0;
185
+ }
186
+
187
+ /* Peak resident set size of this process so far, in bytes (macOS ru_maxrss is bytes; Linux uses VmHWM, falling back to ru_maxrss in kilobytes). */
188
+ static uint64_t peak_rss(void) {
189
+ #if defined(__linux__)
190
+ /* ru_maxrss survives fork+exec on Linux, so it can still show the parent's peak; VmHWM belongs to this process image only. */
191
+ FILE *f = fopen("/proc/self/status", "r");
192
+ if (f) {
193
+ char line[256]; unsigned long long kb = 0; int found = 0;
194
+ while (fgets(line, sizeof line, f)) if (sscanf(line, "VmHWM: %llu kB", &kb) == 1) { found = 1; break; }
195
+ fclose(f);
196
+ if (found) return (uint64_t)kb * 1024ull;
197
+ }
198
+ #endif
199
+ struct rusage u;
200
+ if (getrusage(RUSAGE_SELF, &u)) return 0;
201
+ #ifdef __APPLE__
202
+ return (uint64_t)u.ru_maxrss;
203
+ #else
204
+ return (uint64_t)u.ru_maxrss * 1024ull;
205
+ #endif
206
+ }
207
+
208
+ void cl_stream_result_free(cl_stream_result *r) { free(r->layers); r->layers = NULL; free(r->windows); r->windows = NULL; }
209
+
210
+ int cl_stream_run(const cl_model *m, const char *path, const cl_stream_opts *o, cl_stream_result *res) {
211
+ memset(res, 0, sizeof *res);
212
+ res->rss_start = peak_rss();
213
+ res->layers = calloc(m->n_layers, sizeof(cl_layer_stats));
214
+ if (m->moe_layers == 0 || m->n_experts == 0 || m->n_used == 0) { snprintf(res->error, sizeof res->error, "this model has no routed experts to stream"); return 1; }
215
+ if (m->n_used > CL_MAX_K || m->n_experts > CL_MAX_E) { snprintf(res->error, sizeof res->error, "unsupported expert counts (%u of %u)", m->n_used, m->n_experts); return 1; }
216
+ uint64_t slot_bytes = m->max_expert_bytes;
217
+ /* Slots sit back to back in one arena at a 4 KiB stride (aligned reads), so the budget is divided by the stride, not by the raw expert size. */
218
+ uint64_t stride = (slot_bytes + 4095) / 4096 * 4096;
219
+ uint32_t cache_slots = (uint32_t)(o->cache_bytes / stride);
220
+ if (cache_slots < 1) cache_slots = 1;
221
+ uint32_t stage_slots = o->stage_slots > 0 ? (uint32_t)o->stage_slots : (o->prefetch ? 2 * m->n_used : 0);
222
+ uint32_t universe = m->n_layers * m->n_experts;
223
+ eng *e = calloc(1, sizeof *e);
224
+ e->m = m; e->bypass_os_cache = o->os_cache_bypass;
225
+ e->fd = open(path, O_RDONLY);
226
+ if (e->fd < 0) { snprintf(res->error, sizeof res->error, "cannot open %s", path); free(e); return 1; }
227
+ #if defined(__APPLE__)
228
+ if (o->os_cache_bypass) fcntl(e->fd, F_NOCACHE, 1);
229
+ #endif
230
+ e->cache = cl_cache_new(cache_slots, stage_slots, universe, o->policy, (uint32_t)o->window_pct);
231
+ uint32_t total = cl_cache_total_slots(e->cache), nbuf = total + m->n_used;
232
+ e->buf = calloc(nbuf, sizeof(uint8_t *));
233
+ e->state = calloc(nbuf, sizeof(int));
234
+ { /* the arena is page aligned (16 KiB on Apple silicon) and a multiple of the page size; individual buffers are only 4 KiB aligned */
235
+ long pg = sysconf(_SC_PAGESIZE); if (pg < 4096) pg = 4096;
236
+ uint64_t bytes = ((uint64_t)nbuf * stride + (uint64_t)pg - 1) / (uint64_t)pg * (uint64_t)pg;
237
+ e->arena = aligned_alloc((size_t)pg, bytes);
238
+ if (!e->arena) { snprintf(res->error, sizeof res->error, "out of memory for the expert cache"); return 1; }
239
+ for (uint32_t i = 0; i < nbuf; i++) e->buf[i] = e->arena + (uint64_t)i * stride;
240
+ }
241
+ for (int i = 0; i < 2; i++) { e->ring[i] = aligned_alloc(4096, (m->max_dense_bytes + 4095) / 4096 * 4096 + 4096); e->ring_step[i] = -1; }
242
+ uint64_t dense_total = 0;
243
+ for (uint32_t l = 0; l < m->n_layers; l++) dense_total += m->layers[l].dense_bytes;
244
+ int resident = o->dense_mode == CL_DENSE_RESIDENT;
245
+ if (o->dense_mode == CL_DENSE_AUTO) { uint64_t ram = phys_ram_bytes(); resident = ram > 0 && dense_total <= ram / 4; }
246
+ res->dense_resident = resident;
247
+ if (resident) {
248
+ e->dense_res = calloc(m->n_layers, sizeof(uint8_t *));
249
+ for (uint32_t l = 0; l < m->n_layers; l++) {
250
+ const cl_layer *ly = &m->layers[l];
251
+ if (!ly->n_dense) continue;
252
+ e->dense_res[l] = aligned_alloc(4096, (ly->dense_bytes + 4095) / 4096 * 4096);
253
+ if (!e->dense_res[l]) { snprintf(res->error, sizeof res->error, "out of memory for resident dense weights (%.1f MiB)", dense_total / 1048576.0); return 1; }
254
+ uint64_t at = 0;
255
+ for (int i = 0; i < ly->n_dense; i++) { if (read_full(e, e->dense_res[l] + at, ly->dense[i].length, ly->dense[i].offset)) { snprintf(res->error, sizeof res->error, "read failed for layer %u", l); return 1; } at += ly->dense[i].length; }
256
+ }
257
+ res->dense_preload_bytes = dense_total;
258
+ }
259
+ pthread_mutex_init(&e->mu, NULL); pthread_cond_init(&e->cv_work, NULL); pthread_cond_init(&e->cv_done, NULL);
260
+ int nth = o->io_threads < 1 ? 1 : o->io_threads > 16 ? 16 : o->io_threads;
261
+ pthread_t th[16];
262
+ for (int i = 0; i < nth; i++) pthread_create(&th[i], NULL, worker, e);
263
+
264
+ router rt;
265
+ router_init(&rt, (int)m->n_layers, (int)m->n_experts, o->locality, o->seed, o->predict_corr);
266
+ float logits[CL_MAX_E];
267
+ const int K = (int)m->n_used, E = (int)m->n_experts, L = (int)m->n_layers;
268
+ long total_tokens = o->warmup + o->tokens;
269
+ int64_t steps = (int64_t)total_tokens * L, issued = 0;
270
+ cl_cache_stats base_cs = {0};
271
+ cl_stream_totals zero = {0};
272
+ uint64_t t_start = now_ns();
273
+ int counting = o->warmup == 0;
274
+ if (!counting) { /* stats restart after warm-up */ }
275
+ uint64_t sink = 0;
276
+ int win = o->window_tokens > 0 ? o->window_tokens : (o->shift_len > 0 ? 10 : 0);
277
+ int max_win = win > 0 ? (int)(o->tokens / win) + 1 : 0;
278
+ uint64_t win_acc = 0, win_hit = 0;
279
+ res->windows = max_win ? calloc((size_t)max_win, sizeof(cl_window)) : NULL; res->n_windows = 0;
280
+ compute_ctx cc;
281
+ compute_init(&cc, o->compute_mode, o->seed);
282
+
283
+ pthread_mutex_lock(&e->mu);
284
+ for (int64_t s = 0; s < steps && !e->failed; s++) {
285
+ long tok = (long)(s / L);
286
+ int l = (int)(s % L);
287
+ if (!counting && tok >= o->warmup) { counting = 1; e->t = zero; base_cs = cl_cache_get_stats(e->cache); t_start = now_ns(); for (int i = 0; i < L; i++) memset(&res->layers[i], 0, sizeof(cl_layer_stats)); sink = 0; }
288
+ const cl_layer *ly = &m->layers[l];
289
+ long mt = tok - o->warmup;
290
+ int rmode = o->shift_len > 0 ? ((mt >= o->shift_at && mt < o->shift_at + o->shift_len) ? 2 : 1) : 0;
291
+ /* dense weights: keep one layer ahead in the 2-slot ring */
292
+ while (!resident && issued <= s + 1 && issued < steps) {
293
+ const cl_layer *dl = &m->layers[issued % L];
294
+ if (dl->n_dense) { job *j = calloc(1, sizeof *j); j->kind = J_DENSE; j->layer = (int)(issued % L); j->ring = (int)(issued & 1); j->prefetch = (int)issued; push_job(e, j, issued == s); }
295
+ else e->ring_step[issued & 1] = issued;
296
+ issued++;
297
+ }
298
+ uint64_t w0 = e->t.io_wait_ns, lw = 0;
299
+ const uint8_t *dbuf = resident ? e->dense_res[l] : e->ring[s & 1];
300
+ if (!resident) { uint64_t t0 = now_ns(); int waited = 0; while (e->ring_step[s & 1] != s && !e->failed) { waited = 1; pthread_cond_wait(&e->cv_done, &e->mu); } if (waited) { lw += now_ns() - t0; } }
301
+ if (ly->n_exp) {
302
+ router_logits(&rt, tok, l, 0, rmode, logits);
303
+ resctx rc = {e, l, E};
304
+ cl_route_result rr;
305
+ cl_route(logits, E, K, &o->smart, resident_cb, &rc, &rr);
306
+ int slots[CL_MAX_K], pinned[CL_MAX_K];
307
+ for (int i = 0; i < K; i++) {
308
+ uint32_t key = (uint32_t)(l * E + rr.selected[i]);
309
+ int slot, r = cl_cache_access(e->cache, key, &slot);
310
+ pinned[i] = r >= 0;
311
+ e->t.accesses++; res->layers[l].accesses++;
312
+ if (r == 1) { e->t.hits++; res->layers[l].hits++; }
313
+ else if (r == 2) { e->t.stage_hits++; e->t.hits++; res->layers[l].hits++; if (e->state[slot] != S_READY) e->t.prefetch_late++; }
314
+ else {
315
+ e->t.misses++; res->layers[l].misses++;
316
+ if (r == -1) { slot = (int)(total + (uint32_t)i); e->t.bypass++; }
317
+ e->state[slot] = S_LOADING;
318
+ job *j = calloc(1, sizeof *j); j->kind = J_EXPERT; j->slot = slot; j->layer = l; j->expert = rr.selected[i]; push_job(e, j, 1);
319
+ }
320
+ slots[i] = slot;
321
+ }
322
+ e->t.substituted += (uint64_t)rr.substituted; res->layers[l].substituted += (uint64_t)rr.substituted;
323
+ e->t.dropped_mass += rr.dropped_mass;
324
+ if (rr.max_delta > e->t.max_delta) e->t.max_delta = rr.max_delta;
325
+ /* while this layer's bytes arrive and are used, start reading the next layer's predicted experts */
326
+ if (o->prefetch && l + 1 < L && m->layers[l + 1].n_exp && stage_slots) {
327
+ router_logits(&rt, tok, l + 1, 1, rmode, logits);
328
+ cl_route_result pr;
329
+ cl_route(logits, E, K, NULL, NULL, NULL, &pr);
330
+ for (int i = 0; i < K; i++) {
331
+ uint32_t key = (uint32_t)((l + 1) * E + pr.selected[i]);
332
+ int slot = cl_cache_stage(e->cache, key);
333
+ if (slot < 0) continue;
334
+ e->state[slot] = S_LOADING;
335
+ e->t.prefetch_issued++;
336
+ job *j = calloc(1, sizeof *j); j->kind = J_EXPERT; j->slot = slot; j->layer = l + 1; j->expert = pr.selected[i]; j->prefetch = 1; push_job(e, j, 0);
337
+ }
338
+ }
339
+ for (int i = 0; i < K; i++) wait_slot(e, slots[i], &lw);
340
+ pthread_mutex_unlock(&e->mu);
341
+ uint64_t c0 = now_ns();
342
+ if (dbuf) sink += compute_bytes(&cc, dbuf, ly->dense_bytes);
343
+ for (int i = 0; i < K; i++) sink += compute_bytes(&cc, e->buf[slots[i]], ly->expert_bytes);
344
+ if (o->compute_us > 0) { uint64_t until = now_ns() + (uint64_t)o->compute_us * 1000; while (now_ns() < until) { } }
345
+ uint64_t cdt = now_ns() - c0;
346
+ pthread_mutex_lock(&e->mu);
347
+ e->t.compute_ns += cdt;
348
+ for (int i = 0; i < K; i++) { if (pinned[i]) cl_cache_unpin(e->cache, slots[i]); }
349
+ } else {
350
+ pthread_mutex_unlock(&e->mu);
351
+ uint64_t c0 = now_ns();
352
+ if (dbuf) sink += compute_bytes(&cc, dbuf, ly->dense_bytes);
353
+ uint64_t cdt = now_ns() - c0;
354
+ pthread_mutex_lock(&e->mu);
355
+ e->t.compute_ns += cdt;
356
+ }
357
+ e->t.io_wait_ns += lw;
358
+ res->layers[l].io_wait_ns += lw;
359
+ (void)w0;
360
+ if (win > 0 && counting && l == L - 1 && (tok - o->warmup + 1) % win == 0 && res->n_windows < max_win) {
361
+ cl_window *w = &res->windows[res->n_windows++];
362
+ w->tok_end = (int)(tok - o->warmup + 1); w->accesses = e->t.accesses - win_acc; w->hits = e->t.hits - win_hit; w->peak_rss = peak_rss();
363
+ win_acc = e->t.accesses; win_hit = e->t.hits;
364
+ }
365
+ }
366
+ uint64_t t_end = now_ns();
367
+ e->shutdown = 1;
368
+ pthread_cond_broadcast(&e->cv_work);
369
+ pthread_mutex_unlock(&e->mu);
370
+ for (int i = 0; i < nth; i++) pthread_join(th[i], NULL);
371
+
372
+ cl_cache_stats cs = cl_cache_get_stats(e->cache);
373
+ res->t = e->t;
374
+ res->rss_peak = peak_rss();
375
+ {
376
+ uint64_t pg = (uint64_t)sysconf(_SC_PAGESIZE); if (pg < 4096) pg = 4096;
377
+ uint64_t p = res->rss_start + (((uint64_t)nbuf * stride + pg - 1) / pg * pg) + cl_cache_meta_bytes(e->cache);
378
+ p += 2 * (((m->max_dense_bytes + 4095) / 4096 * 4096) + 4096);
379
+ if (resident) for (uint32_t l = 0; l < m->n_layers; l++) p += (m->layers[l].dense_bytes + 4095) / 4096 * 4096;
380
+ p += (uint64_t)m->n_layers * m->n_experts * 4 * (1 + rt.topics) + (uint64_t)nbuf * (sizeof(uint8_t *) + sizeof(int)) + (uint64_t)m->n_layers * sizeof(cl_layer_stats) + (uint64_t)max_win * sizeof(cl_window);
381
+ res->rss_predicted = p;
382
+ }
383
+ res->t.wall_ns = t_end - t_start;
384
+ res->t.admit_rejects = cs.admit_rejects - base_cs.admit_rejects;
385
+ res->t.evictions = cs.evictions - base_cs.evictions;
386
+ res->t.prefetch_wasted = cs.stage_wasted - base_cs.stage_wasted;
387
+ res->t.checksum = sink;
388
+ res->cache_slots = cache_slots; res->stage_slots = stage_slots; res->slot_bytes = slot_bytes;
389
+ res->ok = !e->failed;
390
+ if (e->failed) snprintf(res->error, sizeof res->error, "%s", e->error);
391
+ free(e->arena);
392
+ if (e->dense_res) { for (uint32_t l = 0; l < m->n_layers; l++) free(e->dense_res[l]); free(e->dense_res); }
393
+ free(e->buf); free(e->state); free(e->ring[0]); free(e->ring[1]); free(rt.base); free(rt.bonus);
394
+ cl_cache_free(e->cache); close(e->fd); free(e);
395
+ return res->ok ? 0 : 1;
396
+ }
package/c/src/stream.h ADDED
@@ -0,0 +1,66 @@
1
+ #ifndef CL_STREAM_H
2
+ #define CL_STREAM_H
3
+ #include "cache.h"
4
+ #include "gguf.h"
5
+ #include "route.h"
6
+ #include <stdint.h>
7
+
8
+ enum { CL_COMPUTE_KERNEL = 0, CL_COMPUTE_CHECKSUM = 1 };
9
+ enum { CL_DENSE_AUTO = 0, CL_DENSE_RESIDENT = 1, CL_DENSE_RING = 2 };
10
+
11
+ typedef struct {
12
+ int tokens, warmup;
13
+ uint64_t cache_bytes; /* budget for the expert cache proper */
14
+ int stage_slots; /* prefetch staging ("Buffer B"), in experts; 0 = twice the experts used per layer */
15
+ cl_policy policy;
16
+ int window_pct; /* W-TinyLFU admission window as a share of the cache, 0 = default */
17
+ cl_smart smart;
18
+ int dense_mode; /* CL_DENSE_AUTO, CL_DENSE_RESIDENT (read once, kept in RAM) or CL_DENSE_RING (two layers at a time, re-read every token) */
19
+ int prefetch; /* 1: predict layer L+1's experts while layer L computes */
20
+ int io_threads;
21
+ int os_cache_bypass; /* ask the OS not to keep what we read (F_NOCACHE / fadvise), so reads hit the disk */
22
+ uint64_t seed;
23
+ double locality; /* skew of the synthetic router's expert popularity (Zipf exponent) */
24
+ double predict_corr; /* how well the synthetic L+1 predictor matches the real L+1 router (0..1) */
25
+ int compute_mode; /* CL_COMPUTE_KERNEL: a Q4_0 x Q8_0 dot-product kernel over every loaded byte; CL_COMPUTE_CHECKSUM: a cheap hash, so the run is I/O only */
26
+ int compute_us; /* extra per-layer busy time, added to either mode */
27
+ int shift_at, shift_len; /* scenario: measured tokens [shift_at, shift_at+shift_len) route uniformly at random (scan noise); every other measured token uses one fixed tight topic. shift_len 0 = off */
28
+ int window_tokens; /* record hits and peak RSS every N measured tokens; 0 = off (10 when a scenario is set) */
29
+ } cl_stream_opts;
30
+
31
+ typedef struct {
32
+ uint64_t accesses, hits, stage_hits, misses, bypass, substituted;
33
+ uint64_t prefetch_issued, prefetch_late, prefetch_wasted;
34
+ uint64_t demand_bytes, prefetch_bytes, dense_bytes;
35
+ uint64_t read_ns_total; /* sum of time spent inside pread across all I/O threads */
36
+ uint64_t io_wait_ns; /* time the compute thread sat waiting for bytes */
37
+ uint64_t compute_ns; /* time spent in the compute step over the loaded bytes */
38
+ uint64_t wall_ns;
39
+ uint64_t admit_rejects, evictions;
40
+ double dropped_mass, max_delta;
41
+ uint64_t checksum;
42
+ } cl_stream_totals;
43
+
44
+ typedef struct {
45
+ uint64_t accesses, hits, misses, substituted, io_wait_ns;
46
+ } cl_layer_stats;
47
+
48
+ typedef struct { int tok_end; uint64_t accesses, hits, peak_rss; } cl_window;
49
+
50
+ typedef struct {
51
+ cl_stream_totals t;
52
+ cl_window *windows; int n_windows;
53
+ uint64_t rss_start, rss_peak; /* ru_maxrss in bytes: before the run allocates anything, and at the end */
54
+ uint64_t rss_predicted; /* rss_start + every buffer and table the run allocates, with every page touched: what rss_peak should reach once the cache is full */
55
+ cl_layer_stats *layers; /* model->n_layers entries */
56
+ uint32_t cache_slots, stage_slots;
57
+ int dense_resident; /* the mode actually used */
58
+ uint64_t dense_preload_bytes; /* read once before the timed run when resident */
59
+ uint64_t slot_bytes;
60
+ int ok;
61
+ char error[256];
62
+ } cl_stream_result;
63
+
64
+ int cl_stream_run(const cl_model *m, const char *path, const cl_stream_opts *o, cl_stream_result *r);
65
+ void cl_stream_result_free(cl_stream_result *r);
66
+ #endif
package/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  main
4
- } from "./chunk-6WAEQCCP.js";
4
+ } from "./chunk-YL63VENN.js";
5
5
  import "./chunk-U5GHVJJO.js";
6
6
 
7
7
  // src/bin.ts
@@ -1134,7 +1134,7 @@ function createClutorServer(o) {
1134
1134
 
1135
1135
  // src/engine.ts
1136
1136
  import { spawn as spawn2 } from "child_process";
1137
- import { accessSync as accessSync2, constants as constants2 } from "fs";
1137
+ import { accessSync as accessSync2, constants as constants2, existsSync } from "fs";
1138
1138
  import { homedir as homedir3 } from "os";
1139
1139
  import { delimiter as delimiter2, dirname, join as join5 } from "path";
1140
1140
  import { fileURLToPath } from "url";
@@ -1154,8 +1154,11 @@ var executable = (p) => {
1154
1154
  }
1155
1155
  };
1156
1156
  function engineSourceDir() {
1157
- return join5(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "c");
1157
+ const here = dirname(fileURLToPath(import.meta.url));
1158
+ const packaged = join5(here, "..", "c");
1159
+ return existsSync(join5(packaged, "Makefile")) ? packaged : join5(here, "..", "..", "..", "c");
1158
1160
  }
1161
+ var isPackaged = () => existsSync(join5(dirname(fileURLToPath(import.meta.url)), "..", "c", "Makefile"));
1159
1162
  function findEngine(env = process.env) {
1160
1163
  const candidates = [env["CLUTOR_ENGINE"], join5(engineSourceDir(), "build", EXE), join5(homedir3(), ".clutor", "bin", EXE), ...(env["PATH"] ?? "").split(delimiter2).map((d) => join5(d, EXE))];
1161
1164
  return candidates.find((c) => !!c && executable(c));
@@ -1186,12 +1189,13 @@ function run(bin, args, opts = {}) {
1186
1189
  }
1187
1190
  async function buildEngine() {
1188
1191
  const dir = engineSourceDir();
1189
- const r = await run("make", ["-C", dir], { timeoutMs: 3e5 }).catch((e) => {
1192
+ const out = isPackaged() ? join5(homedir3(), ".clutor", "bin") : join5(dir, "build");
1193
+ const r = await run("make", ["-C", dir, ...isPackaged() ? [`BUILD=${out}`] : []], { timeoutMs: 3e5 }).catch((e) => {
1190
1194
  throw new EngineError(`could not run make: ${e instanceof Error ? e.message : String(e)}`);
1191
1195
  });
1192
1196
  if (r.code !== 0) throw new EngineError(`the engine did not compile:
1193
1197
  ${(r.stderr || r.stdout).split("\n").slice(-15).join("\n")}`);
1194
- const bin = join5(dir, "build", EXE);
1198
+ const bin = join5(out, EXE);
1195
1199
  if (!executable(bin)) throw new EngineError(`make finished but ${bin} is missing`);
1196
1200
  return bin;
1197
1201
  }
@@ -1236,7 +1240,7 @@ async function engineStream(bin, file, o = {}, timeoutMs = 9e5) {
1236
1240
  }
1237
1241
 
1238
1242
  // src/cli.ts
1239
- import { existsSync, mkdirSync as mkdirSync2, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
1243
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
1240
1244
  import { tmpdir } from "os";
1241
1245
  import { dirname as dirname2, join as join6, resolve as resolve2 } from "path";
1242
1246
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -1375,7 +1379,7 @@ function init(io, dirArg) {
1375
1379
  const dir = resolve2(io.cwd, dirArg ?? ".");
1376
1380
  mkdirSync2(join6(dir, "models"), { recursive: true });
1377
1381
  const path = join6(dir, CONFIG_FILE);
1378
- if (existsSync(path)) {
1382
+ if (existsSync2(path)) {
1379
1383
  io.err(`${path} already exists; leaving it alone`);
1380
1384
  return 1;
1381
1385
  }
@@ -1637,7 +1641,7 @@ async function code(io, args, task, session) {
1637
1641
  if (!task.trim()) throw new UsageError('usage: clutor code "<what to do>" [--dir folder]');
1638
1642
  const { config, root } = cfgFor(args, io);
1639
1643
  const dir = resolve2(io.cwd, flagString(args, "dir") ?? ".");
1640
- if (!existsSync(dir)) throw new UsageError(`${dir} does not exist`);
1644
+ if (!existsSync2(dir)) throw new UsageError(`${dir} does not exist`);
1641
1645
  const backend = session ? session.backend ??= await startBackendIfConfigured(config, io) : await startBackendIfConfigured(config, io);
1642
1646
  try {
1643
1647
  const llm = llmFrom(config, backend);
@@ -1942,7 +1946,7 @@ async function pull(io, args, id) {
1942
1946
  const d = DOWNLOADS.find((x) => x.id === id);
1943
1947
  if (!d) throw new UsageError(`unknown download "${id}"; run clutor pull to see the list`);
1944
1948
  const dest = join6(dir, d.file);
1945
- if (existsSync(dest)) {
1949
+ if (existsSync2(dest)) {
1946
1950
  io.out(`${dest} is already there.`);
1947
1951
  return 0;
1948
1952
  }
@@ -2040,7 +2044,7 @@ async function streamCmd(io, args, file, json) {
2040
2044
  ...shiftFlag(args)
2041
2045
  };
2042
2046
  const path = resolve2(io.cwd, file);
2043
- if (!existsSync(path)) throw new UsageError(`${path} does not exist`);
2047
+ if (!existsSync2(path)) throw new UsageError(`${path} does not exist`);
2044
2048
  const r = await engineStream(bin, path, o);
2045
2049
  if (json) {
2046
2050
  io.out(JSON.stringify(r, null, 2));
@@ -2160,4 +2164,4 @@ export {
2160
2164
  main,
2161
2165
  startBackendIfConfigured
2162
2166
  };
2163
- //# sourceMappingURL=chunk-6WAEQCCP.js.map
2167
+ //# sourceMappingURL=chunk-YL63VENN.js.map