@clutor/cli 0.1.0 → 0.2.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/Makefile +85 -0
- package/c/src/cache.c +232 -0
- package/c/src/cache.h +48 -0
- package/c/src/forward.c +354 -0
- package/c/src/forward.h +69 -0
- package/c/src/gguf.c +242 -0
- package/c/src/gguf.h +48 -0
- package/c/src/kernel.c +125 -0
- package/c/src/kernel.h +45 -0
- package/c/src/main.c +476 -0
- package/c/src/meta.c +118 -0
- package/c/src/meta.h +22 -0
- package/c/src/quant.c +360 -0
- package/c/src/quant.h +43 -0
- package/c/src/route.c +52 -0
- package/c/src/route.h +27 -0
- package/c/src/stream.c +399 -0
- package/c/src/stream.h +68 -0
- package/c/src/tokenizer.c +287 -0
- package/c/src/tokenizer.h +25 -0
- package/c/src/xs.c +215 -0
- package/c/src/xs.h +39 -0
- package/dist/bin.js +2 -1
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-6WAEQCCP.js → chunk-2FBPYNHP.js} +249 -40
- package/dist/chunk-2FBPYNHP.js.map +1 -0
- package/dist/{chunk-RBSFKJ6L.js → chunk-BZZUO7XX.js} +35 -6
- package/dist/chunk-BZZUO7XX.js.map +1 -0
- package/dist/index.d.ts +27 -8
- package/dist/index.js +8 -2
- package/dist/{repl-OVODHQI5.js → repl-DOWMDXFR.js} +2 -2
- package/package.json +4 -4
- package/dist/chunk-6WAEQCCP.js.map +0 -1
- package/dist/chunk-RBSFKJ6L.js.map +0 -1
- /package/dist/{repl-OVODHQI5.js.map → repl-DOWMDXFR.js.map} +0 -0
package/c/src/stream.c
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
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
|
+
if (o->trace) memcpy(logits, o->trace + ((size_t)(tok % o->trace_tokens) * L + (size_t)l) * E, (size_t)E * sizeof(float));
|
|
303
|
+
else router_logits(&rt, tok, l, 0, rmode, logits);
|
|
304
|
+
resctx rc = {e, l, E};
|
|
305
|
+
cl_route_result rr;
|
|
306
|
+
cl_route(logits, E, K, &o->smart, resident_cb, &rc, &rr);
|
|
307
|
+
int slots[CL_MAX_K], pinned[CL_MAX_K];
|
|
308
|
+
for (int i = 0; i < K; i++) {
|
|
309
|
+
uint32_t key = (uint32_t)(l * E + rr.selected[i]);
|
|
310
|
+
int slot, r = cl_cache_access(e->cache, key, &slot);
|
|
311
|
+
pinned[i] = r >= 0;
|
|
312
|
+
e->t.accesses++; res->layers[l].accesses++;
|
|
313
|
+
if (r == 1) { e->t.hits++; res->layers[l].hits++; }
|
|
314
|
+
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++; }
|
|
315
|
+
else {
|
|
316
|
+
e->t.misses++; res->layers[l].misses++;
|
|
317
|
+
if (r == -1) { slot = (int)(total + (uint32_t)i); e->t.bypass++; }
|
|
318
|
+
e->state[slot] = S_LOADING;
|
|
319
|
+
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);
|
|
320
|
+
}
|
|
321
|
+
slots[i] = slot;
|
|
322
|
+
}
|
|
323
|
+
e->t.substituted += (uint64_t)rr.substituted; res->layers[l].substituted += (uint64_t)rr.substituted;
|
|
324
|
+
e->t.dropped_mass += rr.dropped_mass;
|
|
325
|
+
if (rr.max_delta > e->t.max_delta) e->t.max_delta = rr.max_delta;
|
|
326
|
+
/* while this layer's bytes arrive and are used, start reading the next layer's predicted experts */
|
|
327
|
+
/* With a recorded trace the predictor is causal: it guesses layer l+1 from what that layer chose for the PREVIOUS token (nothing is known for the first). */
|
|
328
|
+
if (o->prefetch && l + 1 < L && m->layers[l + 1].n_exp && stage_slots && (!o->trace || tok > 0)) {
|
|
329
|
+
if (o->trace) memcpy(logits, o->trace + ((size_t)((tok - 1) % o->trace_tokens) * L + (size_t)(l + 1)) * E, (size_t)E * sizeof(float));
|
|
330
|
+
else router_logits(&rt, tok, l + 1, 1, rmode, logits);
|
|
331
|
+
cl_route_result pr;
|
|
332
|
+
cl_route(logits, E, K, NULL, NULL, NULL, &pr);
|
|
333
|
+
for (int i = 0; i < K; i++) {
|
|
334
|
+
uint32_t key = (uint32_t)((l + 1) * E + pr.selected[i]);
|
|
335
|
+
int slot = cl_cache_stage(e->cache, key);
|
|
336
|
+
if (slot < 0) continue;
|
|
337
|
+
e->state[slot] = S_LOADING;
|
|
338
|
+
e->t.prefetch_issued++;
|
|
339
|
+
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);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
for (int i = 0; i < K; i++) wait_slot(e, slots[i], &lw);
|
|
343
|
+
pthread_mutex_unlock(&e->mu);
|
|
344
|
+
uint64_t c0 = now_ns();
|
|
345
|
+
if (dbuf) sink += compute_bytes(&cc, dbuf, ly->dense_bytes);
|
|
346
|
+
for (int i = 0; i < K; i++) sink += compute_bytes(&cc, e->buf[slots[i]], ly->expert_bytes);
|
|
347
|
+
if (o->compute_us > 0) { uint64_t until = now_ns() + (uint64_t)o->compute_us * 1000; while (now_ns() < until) { } }
|
|
348
|
+
uint64_t cdt = now_ns() - c0;
|
|
349
|
+
pthread_mutex_lock(&e->mu);
|
|
350
|
+
e->t.compute_ns += cdt;
|
|
351
|
+
for (int i = 0; i < K; i++) { if (pinned[i]) cl_cache_unpin(e->cache, slots[i]); }
|
|
352
|
+
} else {
|
|
353
|
+
pthread_mutex_unlock(&e->mu);
|
|
354
|
+
uint64_t c0 = now_ns();
|
|
355
|
+
if (dbuf) sink += compute_bytes(&cc, dbuf, ly->dense_bytes);
|
|
356
|
+
uint64_t cdt = now_ns() - c0;
|
|
357
|
+
pthread_mutex_lock(&e->mu);
|
|
358
|
+
e->t.compute_ns += cdt;
|
|
359
|
+
}
|
|
360
|
+
e->t.io_wait_ns += lw;
|
|
361
|
+
res->layers[l].io_wait_ns += lw;
|
|
362
|
+
(void)w0;
|
|
363
|
+
if (win > 0 && counting && l == L - 1 && (tok - o->warmup + 1) % win == 0 && res->n_windows < max_win) {
|
|
364
|
+
cl_window *w = &res->windows[res->n_windows++];
|
|
365
|
+
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();
|
|
366
|
+
win_acc = e->t.accesses; win_hit = e->t.hits;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
uint64_t t_end = now_ns();
|
|
370
|
+
e->shutdown = 1;
|
|
371
|
+
pthread_cond_broadcast(&e->cv_work);
|
|
372
|
+
pthread_mutex_unlock(&e->mu);
|
|
373
|
+
for (int i = 0; i < nth; i++) pthread_join(th[i], NULL);
|
|
374
|
+
|
|
375
|
+
cl_cache_stats cs = cl_cache_get_stats(e->cache);
|
|
376
|
+
res->t = e->t;
|
|
377
|
+
res->rss_peak = peak_rss();
|
|
378
|
+
{
|
|
379
|
+
uint64_t pg = (uint64_t)sysconf(_SC_PAGESIZE); if (pg < 4096) pg = 4096;
|
|
380
|
+
uint64_t p = res->rss_start + (((uint64_t)nbuf * stride + pg - 1) / pg * pg) + cl_cache_meta_bytes(e->cache);
|
|
381
|
+
p += 2 * (((m->max_dense_bytes + 4095) / 4096 * 4096) + 4096);
|
|
382
|
+
if (resident) for (uint32_t l = 0; l < m->n_layers; l++) p += (m->layers[l].dense_bytes + 4095) / 4096 * 4096;
|
|
383
|
+
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);
|
|
384
|
+
res->rss_predicted = p;
|
|
385
|
+
}
|
|
386
|
+
res->t.wall_ns = t_end - t_start;
|
|
387
|
+
res->t.admit_rejects = cs.admit_rejects - base_cs.admit_rejects;
|
|
388
|
+
res->t.evictions = cs.evictions - base_cs.evictions;
|
|
389
|
+
res->t.prefetch_wasted = cs.stage_wasted - base_cs.stage_wasted;
|
|
390
|
+
res->t.checksum = sink;
|
|
391
|
+
res->cache_slots = cache_slots; res->stage_slots = stage_slots; res->slot_bytes = slot_bytes;
|
|
392
|
+
res->ok = !e->failed;
|
|
393
|
+
if (e->failed) snprintf(res->error, sizeof res->error, "%s", e->error);
|
|
394
|
+
free(e->arena);
|
|
395
|
+
if (e->dense_res) { for (uint32_t l = 0; l < m->n_layers; l++) free(e->dense_res[l]); free(e->dense_res); }
|
|
396
|
+
free(e->buf); free(e->state); free(e->ring[0]); free(e->ring[1]); free(rt.base); free(rt.bonus);
|
|
397
|
+
cl_cache_free(e->cache); close(e->fd); free(e);
|
|
398
|
+
return res->ok ? 0 : 1;
|
|
399
|
+
}
|
package/c/src/stream.h
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
const float *trace; /* a recorded run of the REAL router (from `generate --trace`): trace_tokens x layers x experts raw scores; replayed cyclically. NULL = synthetic routing */
|
|
29
|
+
int trace_tokens;
|
|
30
|
+
int window_tokens; /* record hits and peak RSS every N measured tokens; 0 = off (10 when a scenario is set) */
|
|
31
|
+
} cl_stream_opts;
|
|
32
|
+
|
|
33
|
+
typedef struct {
|
|
34
|
+
uint64_t accesses, hits, stage_hits, misses, bypass, substituted;
|
|
35
|
+
uint64_t prefetch_issued, prefetch_late, prefetch_wasted;
|
|
36
|
+
uint64_t demand_bytes, prefetch_bytes, dense_bytes;
|
|
37
|
+
uint64_t read_ns_total; /* sum of time spent inside pread across all I/O threads */
|
|
38
|
+
uint64_t io_wait_ns; /* time the compute thread sat waiting for bytes */
|
|
39
|
+
uint64_t compute_ns; /* time spent in the compute step over the loaded bytes */
|
|
40
|
+
uint64_t wall_ns;
|
|
41
|
+
uint64_t admit_rejects, evictions;
|
|
42
|
+
double dropped_mass, max_delta;
|
|
43
|
+
uint64_t checksum;
|
|
44
|
+
} cl_stream_totals;
|
|
45
|
+
|
|
46
|
+
typedef struct {
|
|
47
|
+
uint64_t accesses, hits, misses, substituted, io_wait_ns;
|
|
48
|
+
} cl_layer_stats;
|
|
49
|
+
|
|
50
|
+
typedef struct { int tok_end; uint64_t accesses, hits, peak_rss; } cl_window;
|
|
51
|
+
|
|
52
|
+
typedef struct {
|
|
53
|
+
cl_stream_totals t;
|
|
54
|
+
cl_window *windows; int n_windows;
|
|
55
|
+
uint64_t rss_start, rss_peak; /* ru_maxrss in bytes: before the run allocates anything, and at the end */
|
|
56
|
+
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 */
|
|
57
|
+
cl_layer_stats *layers; /* model->n_layers entries */
|
|
58
|
+
uint32_t cache_slots, stage_slots;
|
|
59
|
+
int dense_resident; /* the mode actually used */
|
|
60
|
+
uint64_t dense_preload_bytes; /* read once before the timed run when resident */
|
|
61
|
+
uint64_t slot_bytes;
|
|
62
|
+
int ok;
|
|
63
|
+
char error[256];
|
|
64
|
+
} cl_stream_result;
|
|
65
|
+
|
|
66
|
+
int cl_stream_run(const cl_model *m, const char *path, const cl_stream_opts *o, cl_stream_result *r);
|
|
67
|
+
void cl_stream_result_free(cl_stream_result *r);
|
|
68
|
+
#endif
|