@clutor/cli 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +14 -2
- package/c/Makefile +53 -0
- package/c/src/cache.c +232 -0
- package/c/src/cache.h +48 -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 +145 -0
- package/c/src/route.c +52 -0
- package/c/src/route.h +27 -0
- package/c/src/stream.c +396 -0
- package/c/src/stream.h +66 -0
- package/dist/bin.js +53 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-RBSFKJ6L.js +178 -0
- package/dist/chunk-RBSFKJ6L.js.map +1 -0
- package/dist/chunk-U5GHVJJO.js +135 -0
- package/dist/chunk-U5GHVJJO.js.map +1 -0
- package/dist/chunk-YL63VENN.js +2167 -0
- package/dist/chunk-YL63VENN.js.map +1 -0
- package/dist/index.d.ts +615 -0
- package/dist/index.js +123 -0
- package/dist/index.js.map +1 -0
- package/dist/repl-OVODHQI5.js +10 -0
- package/dist/repl-OVODHQI5.js.map +1 -0
- package/package.json +16 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sagar Pinninti
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @clutor/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The `clutor` command and local server.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g @clutor/cli
|
|
7
|
+
clutor plan deepseek-v3 --ram 16 # will it fit, and how fast could it possibly go
|
|
8
|
+
clutor plan ./my-model.gguf # exact figures from a real file
|
|
9
|
+
clutor serve # OpenAI- and Ollama-compatible API on 127.0.0.1:4320, agent, MCP
|
|
10
|
+
clutor code "fix the failing test" --verify "npm test"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Run `clutor --help`. Documentation: <https://github.com/isagarpinninti/clutor/blob/main/docs/GUIDE.md>.
|
|
14
|
+
|
|
15
|
+
It does not run models itself; it supervises [llama.cpp](https://github.com/ggml-org/llama.cpp)'s `llama-server` or talks to any OpenAI-compatible server.
|
package/c/Makefile
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Builds the Clutor engine. Needs only a C11 compiler and pthreads.
|
|
2
|
+
CC ?= cc
|
|
3
|
+
CFLAGS ?= -O2 -g -std=c11 -Wall -Wextra -Wpedantic -Wno-unused-parameter
|
|
4
|
+
ifeq ($(shell uname -m),arm64)
|
|
5
|
+
CFLAGS += -mcpu=native
|
|
6
|
+
endif
|
|
7
|
+
ifeq ($(shell uname -m),x86_64)
|
|
8
|
+
CFLAGS += -mavx2
|
|
9
|
+
endif
|
|
10
|
+
LDLIBS = -lm -lpthread
|
|
11
|
+
# BUILD can point somewhere writable when the source sits in a read-only installed package: make BUILD=$HOME/.clutor/bin
|
|
12
|
+
BUILD ?= build
|
|
13
|
+
SRC = src/gguf.c src/cache.c src/route.c src/kernel.c src/stream.c
|
|
14
|
+
BIN = $(BUILD)/clutor-engine
|
|
15
|
+
|
|
16
|
+
all: $(BIN)
|
|
17
|
+
build:
|
|
18
|
+
mkdir -p build
|
|
19
|
+
$(BIN): $(SRC) src/main.c $(wildcard src/*.h)
|
|
20
|
+
mkdir -p $(BUILD)
|
|
21
|
+
$(CC) $(CFLAGS) $(SRC) src/main.c -o $@ $(LDLIBS)
|
|
22
|
+
build/test_engine: build $(SRC) test/test_engine.c $(wildcard src/*.h)
|
|
23
|
+
$(CC) $(CFLAGS) -Isrc $(SRC) test/test_engine.c -o $@ $(LDLIBS)
|
|
24
|
+
FIXTURE = ../packages/core/test/fixtures/stories260K.gguf
|
|
25
|
+
test: build/test_engine
|
|
26
|
+
./build/test_engine $(FIXTURE)
|
|
27
|
+
|
|
28
|
+
# The kernel has one source and several compile-time paths. `make test` runs the one this machine picks; this builds and runs the others:
|
|
29
|
+
# portable C (everywhere), NEON without the dot-product instruction (arm64), and AVX2 cross-built for x86_64 and run through Rosetta if it can.
|
|
30
|
+
TEST_SRC = $(SRC) test/test_engine.c
|
|
31
|
+
PATH_FLAGS = -O2 -g -std=c11 -Wall -Wextra -Wpedantic -Wno-unused-parameter -Isrc
|
|
32
|
+
test-paths: build
|
|
33
|
+
$(CC) $(PATH_FLAGS) -DCL_FORCE_SCALAR $(TEST_SRC) -o build/test-scalar $(LDLIBS)
|
|
34
|
+
./build/test-scalar $(FIXTURE)
|
|
35
|
+
ifeq ($(shell uname -m),arm64)
|
|
36
|
+
$(CC) $(PATH_FLAGS) -mcpu=native -DCL_NO_DOTPROD $(TEST_SRC) -o build/test-neon $(LDLIBS)
|
|
37
|
+
./build/test-neon $(FIXTURE)
|
|
38
|
+
endif
|
|
39
|
+
test-avx2: build
|
|
40
|
+
$(CC) -arch x86_64 $(PATH_FLAGS) -mavx2 $(TEST_SRC) -o build/test-avx2 $(LDLIBS)
|
|
41
|
+
arch -x86_64 ./build/test-avx2 $(FIXTURE)
|
|
42
|
+
|
|
43
|
+
# Address + undefined-behaviour sanitizers on the unit tests and a threaded streaming run, then the thread sanitizer on the same run.
|
|
44
|
+
SAN_RUN = stream $(SMALL) --tokens 60 --cache-mb 1 --stage 1 --io-threads 3 --smart 1,8,1 --policy adaptive
|
|
45
|
+
sanitize: build
|
|
46
|
+
$(CC) -O1 -g -std=c11 -fsanitize=address,undefined -fno-omit-frame-pointer -Isrc $(SRC) test/test_engine.c -o build/test-asan $(LDLIBS)
|
|
47
|
+
./build/test-asan $(FIXTURE)
|
|
48
|
+
$(CC) -O1 -g -std=c11 -fsanitize=address,undefined -fno-omit-frame-pointer $(SRC) src/main.c -o build/engine-asan $(LDLIBS)
|
|
49
|
+
$(CC) -O1 -g -std=c11 -fsanitize=thread $(SRC) src/main.c -o build/engine-tsan $(LDLIBS)
|
|
50
|
+
@if [ -n "$(SMALL)" ]; then ./build/engine-asan $(SAN_RUN) >/dev/null && ./build/engine-tsan $(SAN_RUN) >/dev/null && echo "sanitizers clean on $(SMALL)"; else echo "(set SMALL=<moe.gguf> to also run the streaming engine under the sanitizers)"; fi
|
|
51
|
+
clean:
|
|
52
|
+
rm -rf build
|
|
53
|
+
.PHONY: all test test-paths test-avx2 sanitize clean
|
package/c/src/cache.c
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#include "cache.h"
|
|
2
|
+
#include <stdlib.h>
|
|
3
|
+
#include <string.h>
|
|
4
|
+
|
|
5
|
+
#ifndef CL_DEFAULT_WINDOW_PCT
|
|
6
|
+
#define CL_DEFAULT_WINDOW_PCT 1
|
|
7
|
+
#endif
|
|
8
|
+
typedef struct { int prev, next; uint32_t key; int list; int pin; } slot_t;
|
|
9
|
+
typedef struct { int head, tail; uint32_t n; } dlist_t;
|
|
10
|
+
|
|
11
|
+
struct cl_cache {
|
|
12
|
+
cl_policy policy;
|
|
13
|
+
uint32_t cache_cap, stage_cap, total, universe, win_cap, main_cap, prot_cap;
|
|
14
|
+
slot_t *s;
|
|
15
|
+
int *where;
|
|
16
|
+
dlist_t lists[5];
|
|
17
|
+
int free_head;
|
|
18
|
+
uint32_t free_n;
|
|
19
|
+
/* 4-bit count-min sketch: 4 rows, packed two counters per byte. */
|
|
20
|
+
uint8_t *cms;
|
|
21
|
+
uint32_t cms_mask;
|
|
22
|
+
uint64_t additions, sample;
|
|
23
|
+
/* adaptive window: compare hit rate across periods and keep moving the window the way that helped */
|
|
24
|
+
uint64_t hc_period, hc_acc, hc_hit;
|
|
25
|
+
double hc_prev;
|
|
26
|
+
int hc_dir;
|
|
27
|
+
cl_cache_stats st;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
static uint64_t mix(uint64_t x) { x += 0x9e3779b97f4a7c15ull; x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ull; x = (x ^ (x >> 27)) * 0x94d049bb133111ebull; return x ^ (x >> 31); }
|
|
31
|
+
static uint32_t cms_idx(const cl_cache *c, uint32_t key, int row) { return (uint32_t)(mix(((uint64_t)key << 3) ^ (uint64_t)(row + 1) * 0x2545f4914f6cdd1dull)) & c->cms_mask; }
|
|
32
|
+
static uint32_t cms_get(const cl_cache *c, uint32_t i, int row) { uint8_t b = c->cms[(size_t)row * (c->cms_mask + 1) / 2 + i / 2]; return (i & 1) ? b >> 4 : b & 15; }
|
|
33
|
+
static void cms_set(cl_cache *c, uint32_t i, int row, uint32_t v) {
|
|
34
|
+
uint8_t *p = &c->cms[(size_t)row * (c->cms_mask + 1) / 2 + i / 2];
|
|
35
|
+
*p = (i & 1) ? (uint8_t)((*p & 0x0f) | (v << 4)) : (uint8_t)((*p & 0xf0) | v);
|
|
36
|
+
}
|
|
37
|
+
uint32_t cl_cache_freq(const cl_cache *c, uint32_t key) {
|
|
38
|
+
uint32_t m = 15;
|
|
39
|
+
for (int r = 0; r < 4; r++) { uint32_t v = cms_get(c, cms_idx(c, key, r), r); if (v < m) m = v; }
|
|
40
|
+
return m;
|
|
41
|
+
}
|
|
42
|
+
static void cms_add(cl_cache *c, uint32_t key) {
|
|
43
|
+
uint32_t idx[4], cur[4], m = 15;
|
|
44
|
+
for (int r = 0; r < 4; r++) { idx[r] = cms_idx(c, key, r); cur[r] = cms_get(c, idx[r], r); if (cur[r] < m) m = cur[r]; }
|
|
45
|
+
if (m < 15) for (int r = 0; r < 4; r++) if (cur[r] == m) cms_set(c, idx[r], r, m + 1); /* conservative update */
|
|
46
|
+
if (++c->additions >= c->sample) { /* aging: halve every counter so old popularity fades */
|
|
47
|
+
size_t bytes = (size_t)4 * (c->cms_mask + 1) / 2;
|
|
48
|
+
for (size_t i = 0; i < bytes; i++) c->cms[i] = (uint8_t)((c->cms[i] >> 1) & 0x77);
|
|
49
|
+
c->additions = 0;
|
|
50
|
+
c->st.aging_resets++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static void unlink_slot(cl_cache *c, int i) {
|
|
55
|
+
slot_t *s = &c->s[i];
|
|
56
|
+
dlist_t *l = &c->lists[s->list];
|
|
57
|
+
if (s->prev >= 0) c->s[s->prev].next = s->next; else l->head = s->next;
|
|
58
|
+
if (s->next >= 0) c->s[s->next].prev = s->prev; else l->tail = s->prev;
|
|
59
|
+
l->n--;
|
|
60
|
+
s->prev = s->next = -1;
|
|
61
|
+
}
|
|
62
|
+
static void push_front(cl_cache *c, int i, int list) {
|
|
63
|
+
slot_t *s = &c->s[i];
|
|
64
|
+
dlist_t *l = &c->lists[list];
|
|
65
|
+
s->list = list; s->prev = -1; s->next = l->head;
|
|
66
|
+
if (l->head >= 0) c->s[l->head].prev = i; else l->tail = i;
|
|
67
|
+
l->head = i;
|
|
68
|
+
l->n++;
|
|
69
|
+
}
|
|
70
|
+
static int tail_unpinned(const cl_cache *c, int list, int skip) {
|
|
71
|
+
for (int i = c->lists[list].tail; i >= 0; i = c->s[i].prev) if (!c->s[i].pin && i != skip) return i;
|
|
72
|
+
return -1;
|
|
73
|
+
}
|
|
74
|
+
static void release(cl_cache *c, int i) {
|
|
75
|
+
slot_t *s = &c->s[i];
|
|
76
|
+
if (s->list != CL_FREE) unlink_slot(c, i);
|
|
77
|
+
if (s->key != UINT32_MAX) c->where[s->key] = -1;
|
|
78
|
+
s->key = UINT32_MAX; s->pin = 0;
|
|
79
|
+
push_front(c, i, CL_FREE);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
cl_cache *cl_cache_new(uint32_t cache_slots, uint32_t stage_slots, uint32_t universe, cl_policy policy, uint32_t window_pct) {
|
|
83
|
+
if (cache_slots == 0 || universe == 0) return NULL;
|
|
84
|
+
cl_cache *c = calloc(1, sizeof *c);
|
|
85
|
+
if (!c) return NULL;
|
|
86
|
+
c->policy = policy; c->cache_cap = cache_slots; c->stage_cap = stage_slots; c->universe = universe;
|
|
87
|
+
c->total = cache_slots + stage_slots + 1; /* one spare so an insert can precede the eviction it causes */
|
|
88
|
+
if (policy == CL_ADAPTIVE) { c->hc_period = (uint64_t)cache_slots * 8 > 500 ? (uint64_t)cache_slots * 8 : 500; c->hc_dir = 1; c->hc_prev = -1; if (!window_pct) window_pct = 10; }
|
|
89
|
+
if (policy == CL_LRU) { c->win_cap = cache_slots; c->main_cap = 0; }
|
|
90
|
+
else { if (!window_pct || window_pct > 90) window_pct = CL_DEFAULT_WINDOW_PCT;
|
|
91
|
+
c->win_cap = cache_slots * window_pct / 100 ? cache_slots * window_pct / 100 : 1; if (c->win_cap >= cache_slots) c->win_cap = cache_slots > 1 ? cache_slots - 1 : 1; c->main_cap = cache_slots - c->win_cap; }
|
|
92
|
+
c->prot_cap = c->main_cap * 4 / 5;
|
|
93
|
+
c->s = calloc(c->total, sizeof(slot_t));
|
|
94
|
+
c->where = malloc((size_t)universe * sizeof(int));
|
|
95
|
+
uint32_t w = 256;
|
|
96
|
+
while (w < universe * 4) w <<= 1;
|
|
97
|
+
c->cms_mask = w - 1;
|
|
98
|
+
c->cms = calloc((size_t)4 * w / 2, 1);
|
|
99
|
+
c->sample = (uint64_t)cache_slots * 10 > 1000 ? (uint64_t)cache_slots * 10 : 1000;
|
|
100
|
+
if (!c->s || !c->where || !c->cms) { cl_cache_free(c); return NULL; }
|
|
101
|
+
for (uint32_t k = 0; k < universe; k++) c->where[k] = -1;
|
|
102
|
+
for (int l = 0; l < 5; l++) c->lists[l].head = c->lists[l].tail = -1;
|
|
103
|
+
for (uint32_t i = 0; i < c->total; i++) { c->s[i].key = UINT32_MAX; c->s[i].prev = c->s[i].next = -1; push_front(c, (int)i, CL_FREE); }
|
|
104
|
+
return c;
|
|
105
|
+
}
|
|
106
|
+
void cl_cache_free(cl_cache *c) { if (!c) return; free(c->s); free(c->where); free(c->cms); free(c); }
|
|
107
|
+
uint32_t cl_cache_total_slots(const cl_cache *c) { return c->total; }
|
|
108
|
+
uint64_t cl_cache_meta_bytes(const cl_cache *c) { return sizeof *c + (uint64_t)c->total * sizeof(slot_t) + (uint64_t)c->universe * sizeof(int) + (uint64_t)4 * (c->cms_mask + 1) / 2; }
|
|
109
|
+
int cl_cache_find(const cl_cache *c, uint32_t key) { return key < c->universe ? c->where[key] : -1; }
|
|
110
|
+
cl_list cl_cache_list(const cl_cache *c, int slot) { return (cl_list)c->s[slot].list; }
|
|
111
|
+
uint32_t cl_cache_slot_key(const cl_cache *c, int slot) { return c->s[slot].key; }
|
|
112
|
+
cl_cache_stats cl_cache_get_stats(const cl_cache *c) { cl_cache_stats s = c->st; s.window_slots = c->win_cap; return s; }
|
|
113
|
+
uint32_t cl_cache_count(const cl_cache *c) { return c->lists[CL_WINDOW].n + c->lists[CL_PROBATION].n + c->lists[CL_PROTECTED].n; }
|
|
114
|
+
void cl_cache_pin(cl_cache *c, int slot) { c->s[slot].pin++; }
|
|
115
|
+
void cl_cache_unpin(cl_cache *c, int slot) { if (c->s[slot].pin > 0) c->s[slot].pin--; }
|
|
116
|
+
void cl_cache_abandon(cl_cache *c, int slot) { release(c, slot); }
|
|
117
|
+
|
|
118
|
+
/* Brings the cache back to capacity after an insert. Returns 0 if everything evictable is pinned. */
|
|
119
|
+
static int make_room(cl_cache *c, int keep) {
|
|
120
|
+
while (cl_cache_count(c) > c->cache_cap) {
|
|
121
|
+
if (c->policy == CL_LRU) {
|
|
122
|
+
int v = tail_unpinned(c, CL_WINDOW, keep);
|
|
123
|
+
if (v < 0) return 0;
|
|
124
|
+
c->st.evictions++; release(c, v);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (c->lists[CL_WINDOW].n > c->win_cap) {
|
|
128
|
+
int cand = tail_unpinned(c, CL_WINDOW, keep);
|
|
129
|
+
if (cand >= 0) {
|
|
130
|
+
unlink_slot(c, cand); push_front(c, cand, CL_PROBATION);
|
|
131
|
+
if (c->lists[CL_PROBATION].n + c->lists[CL_PROTECTED].n > c->main_cap) {
|
|
132
|
+
int victim = tail_unpinned(c, CL_PROBATION, -1);
|
|
133
|
+
if (victim < 0) victim = cand;
|
|
134
|
+
int loser = (victim != cand && cl_cache_freq(c, c->s[cand].key) >= cl_cache_freq(c, c->s[victim].key)) ? victim : cand;
|
|
135
|
+
if (loser == cand) c->st.admit_rejects++;
|
|
136
|
+
c->st.evictions++; release(c, loser);
|
|
137
|
+
}
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
int v = tail_unpinned(c, CL_PROBATION, keep);
|
|
142
|
+
if (v < 0) v = tail_unpinned(c, CL_PROTECTED, keep);
|
|
143
|
+
if (v < 0) v = tail_unpinned(c, CL_WINDOW, keep);
|
|
144
|
+
if (v < 0) return 0;
|
|
145
|
+
c->st.evictions++; release(c, v);
|
|
146
|
+
}
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
static void touch(cl_cache *c, int i) {
|
|
151
|
+
switch (c->s[i].list) {
|
|
152
|
+
case CL_WINDOW: unlink_slot(c, i); push_front(c, i, CL_WINDOW); break;
|
|
153
|
+
case CL_PROTECTED: unlink_slot(c, i); push_front(c, i, CL_PROTECTED); break;
|
|
154
|
+
case CL_PROBATION:
|
|
155
|
+
if (c->policy == CL_LRU) break;
|
|
156
|
+
unlink_slot(c, i); push_front(c, i, CL_PROTECTED);
|
|
157
|
+
while (c->lists[CL_PROTECTED].n > c->prot_cap) {
|
|
158
|
+
int d = tail_unpinned(c, CL_PROTECTED, i);
|
|
159
|
+
if (d < 0) break;
|
|
160
|
+
unlink_slot(c, d); push_front(c, d, CL_PROBATION);
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
default: break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static void set_window(cl_cache *c, uint32_t w) {
|
|
168
|
+
uint32_t lo = 1, hi = c->cache_cap > 1 ? c->cache_cap - 1 : 1;
|
|
169
|
+
if (w < lo) w = lo;
|
|
170
|
+
if (w > hi) w = hi;
|
|
171
|
+
c->win_cap = w; c->main_cap = c->cache_cap - w; c->prot_cap = c->main_cap * 4 / 5;
|
|
172
|
+
}
|
|
173
|
+
static void hill_climb(cl_cache *c, int hit) {
|
|
174
|
+
c->hc_acc++; c->hc_hit += (uint64_t)hit;
|
|
175
|
+
if (c->hc_acc < c->hc_period) return;
|
|
176
|
+
double rate = (double)c->hc_hit / (double)c->hc_acc;
|
|
177
|
+
if (c->hc_prev >= 0) {
|
|
178
|
+
if (rate < c->hc_prev - 0.002) c->hc_dir = -c->hc_dir; /* it got worse: turn around */
|
|
179
|
+
uint32_t step = c->cache_cap / 20 ? c->cache_cap / 20 : 1;
|
|
180
|
+
set_window(c, c->hc_dir > 0 ? c->win_cap + step : (c->win_cap > step ? c->win_cap - step : 1));
|
|
181
|
+
}
|
|
182
|
+
c->hc_prev = rate; c->hc_acc = c->hc_hit = 0;
|
|
183
|
+
}
|
|
184
|
+
static int take_free(cl_cache *c) {
|
|
185
|
+
int i = c->lists[CL_FREE].head;
|
|
186
|
+
if (i >= 0) unlink_slot(c, i);
|
|
187
|
+
return i;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
int cl_cache_access(cl_cache *c, uint32_t key, int *slot) {
|
|
191
|
+
*slot = -1;
|
|
192
|
+
if (key >= c->universe) return -1;
|
|
193
|
+
cms_add(c, key);
|
|
194
|
+
int i = c->where[key];
|
|
195
|
+
if (i >= 0 && c->s[i].list == CL_STAGE) {
|
|
196
|
+
/* A prefetched expert was wanted: it joins the cache. */
|
|
197
|
+
c->st.stage_hits++;
|
|
198
|
+
if (c->policy == CL_ADAPTIVE) hill_climb(c, 1);
|
|
199
|
+
unlink_slot(c, i); push_front(c, i, CL_WINDOW);
|
|
200
|
+
c->s[i].pin++;
|
|
201
|
+
make_room(c, i);
|
|
202
|
+
*slot = i;
|
|
203
|
+
return 2;
|
|
204
|
+
}
|
|
205
|
+
if (i >= 0) { c->st.hits++; if (c->policy == CL_ADAPTIVE) hill_climb(c, 1); touch(c, i); c->s[i].pin++; *slot = i; return 1; }
|
|
206
|
+
c->st.misses++;
|
|
207
|
+
if (c->policy == CL_ADAPTIVE) hill_climb(c, 0);
|
|
208
|
+
int n = take_free(c);
|
|
209
|
+
if (n < 0) return -1;
|
|
210
|
+
c->s[n].key = key; c->s[n].pin = 1; c->where[key] = n;
|
|
211
|
+
push_front(c, n, CL_WINDOW);
|
|
212
|
+
c->st.inserts++;
|
|
213
|
+
if (!make_room(c, n)) { /* everything else is pinned: do not keep the over-capacity entry */ release(c, n); return -1; }
|
|
214
|
+
*slot = n;
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
int cl_cache_stage(cl_cache *c, uint32_t key) {
|
|
219
|
+
if (key >= c->universe || c->where[key] >= 0 || c->stage_cap == 0) return -1;
|
|
220
|
+
if (c->lists[CL_STAGE].n >= c->stage_cap) {
|
|
221
|
+
int old = tail_unpinned(c, CL_STAGE, -1);
|
|
222
|
+
if (old < 0) return -1;
|
|
223
|
+
c->st.stage_wasted++;
|
|
224
|
+
release(c, old);
|
|
225
|
+
}
|
|
226
|
+
int n = take_free(c);
|
|
227
|
+
if (n < 0) return -1;
|
|
228
|
+
c->s[n].key = key; c->s[n].pin = 1; c->where[key] = n;
|
|
229
|
+
push_front(c, n, CL_STAGE);
|
|
230
|
+
c->st.stage_inserts++;
|
|
231
|
+
return n;
|
|
232
|
+
}
|
package/c/src/cache.h
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Expert cache policy: decides WHICH expert occupies WHICH slot. It holds no data and takes no locks; the caller owns the buffers and
|
|
3
|
+
* serialises access. Two policies: plain LRU, and W-TinyLFU (a small admission window, then a segmented LRU whose admission is gated
|
|
4
|
+
* by a 4-bit count-min sketch of how often each expert has been wanted). A separate staging area (the "Buffer B" of the design) holds
|
|
5
|
+
* prefetched experts that are not yet wanted, so a prefetch can never evict something the policy chose to keep.
|
|
6
|
+
*/
|
|
7
|
+
#ifndef CL_CACHE_H
|
|
8
|
+
#define CL_CACHE_H
|
|
9
|
+
#include <stdint.h>
|
|
10
|
+
|
|
11
|
+
typedef enum { CL_LRU = 0, CL_WTINYLFU = 1, CL_ADAPTIVE = 2 } cl_policy;
|
|
12
|
+
typedef enum { CL_FREE = 0, CL_WINDOW, CL_PROBATION, CL_PROTECTED, CL_STAGE } cl_list;
|
|
13
|
+
|
|
14
|
+
typedef struct {
|
|
15
|
+
uint64_t hits, misses, stage_hits, inserts, evictions, admit_rejects, stage_inserts, stage_wasted, aging_resets;
|
|
16
|
+
uint32_t window_slots; /* current admission window size */
|
|
17
|
+
} cl_cache_stats;
|
|
18
|
+
|
|
19
|
+
typedef struct cl_cache cl_cache;
|
|
20
|
+
|
|
21
|
+
/* `universe` is the number of distinct keys (layers x experts); keys are 0..universe-1. */
|
|
22
|
+
/* `window_pct` is the share of the cache used as the admission window (0 = default). */
|
|
23
|
+
cl_cache *cl_cache_new(uint32_t cache_slots, uint32_t stage_slots, uint32_t universe, cl_policy policy, uint32_t window_pct);
|
|
24
|
+
void cl_cache_free(cl_cache *c);
|
|
25
|
+
uint32_t cl_cache_total_slots(const cl_cache *c);
|
|
26
|
+
/* Bytes this cache allocates for itself: the struct, one slot record per slot, the key-to-slot map and the Count-Min Sketch. */
|
|
27
|
+
uint64_t cl_cache_meta_bytes(const cl_cache *c); /* slots the caller must provide buffers for */
|
|
28
|
+
|
|
29
|
+
int cl_cache_find(const cl_cache *c, uint32_t key); /* slot holding the key (cached or staged), or -1 */
|
|
30
|
+
cl_list cl_cache_list(const cl_cache *c, int slot);
|
|
31
|
+
uint32_t cl_cache_slot_key(const cl_cache *c, int slot);
|
|
32
|
+
|
|
33
|
+
/* A demand access. Records the access in the frequency sketch. Result:
|
|
34
|
+
* 1 hit in the cache (slot returned, recency updated) 2 hit in staging (slot adopted into the cache)
|
|
35
|
+
* 0 miss: a slot was assigned and pinned; the caller must fill it -1 miss and nothing could be evicted: bypass the cache */
|
|
36
|
+
int cl_cache_access(cl_cache *c, uint32_t key, int *slot);
|
|
37
|
+
/* A prefetch: assign a staging slot for `key` (pinned until `cl_cache_unpin`). Returns the slot, or -1 if already present / no room. */
|
|
38
|
+
int cl_cache_stage(cl_cache *c, uint32_t key);
|
|
39
|
+
|
|
40
|
+
void cl_cache_pin(cl_cache *c, int slot);
|
|
41
|
+
void cl_cache_unpin(cl_cache *c, int slot);
|
|
42
|
+
/* Drops a slot that was assigned but whose fill failed. */
|
|
43
|
+
void cl_cache_abandon(cl_cache *c, int slot);
|
|
44
|
+
|
|
45
|
+
uint32_t cl_cache_freq(const cl_cache *c, uint32_t key); /* estimated access count (0..15) */
|
|
46
|
+
cl_cache_stats cl_cache_get_stats(const cl_cache *c);
|
|
47
|
+
uint32_t cl_cache_count(const cl_cache *c); /* experts in the cache proper (not staging) */
|
|
48
|
+
#endif
|
package/c/src/gguf.c
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#define _GNU_SOURCE
|
|
2
|
+
#include "gguf.h"
|
|
3
|
+
#include <errno.h>
|
|
4
|
+
#include <fcntl.h>
|
|
5
|
+
#include <stdio.h>
|
|
6
|
+
#include <stdlib.h>
|
|
7
|
+
#include <string.h>
|
|
8
|
+
#include <sys/stat.h>
|
|
9
|
+
#include <unistd.h>
|
|
10
|
+
|
|
11
|
+
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
|
12
|
+
#error "GGUF is little-endian; this reader assumes a little-endian host"
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
/* [elements per block, bytes per block, name], by ggml type id. Same table as the TypeScript reader. */
|
|
16
|
+
static const struct { int id; uint32_t block, bytes; const char *name; } TYPES[] = {
|
|
17
|
+
{0, 1, 4, "F32"}, {1, 1, 2, "F16"}, {2, 32, 18, "Q4_0"}, {3, 32, 20, "Q4_1"}, {6, 32, 22, "Q5_0"}, {7, 32, 24, "Q5_1"}, {8, 32, 34, "Q8_0"}, {9, 32, 36, "Q8_1"},
|
|
18
|
+
{10, 256, 84, "Q2_K"}, {11, 256, 110, "Q3_K"}, {12, 256, 144, "Q4_K"}, {13, 256, 176, "Q5_K"}, {14, 256, 210, "Q6_K"}, {15, 256, 292, "Q8_K"},
|
|
19
|
+
{16, 256, 66, "IQ2_XXS"}, {17, 256, 74, "IQ2_XS"}, {18, 256, 98, "IQ3_XXS"}, {19, 256, 50, "IQ1_S"}, {20, 32, 18, "IQ4_NL"}, {21, 256, 110, "IQ3_S"},
|
|
20
|
+
{22, 256, 82, "IQ2_S"}, {23, 256, 136, "IQ4_XS"}, {24, 1, 1, "I8"}, {25, 1, 2, "I16"}, {26, 1, 4, "I32"}, {27, 1, 8, "I64"}, {28, 1, 8, "F64"},
|
|
21
|
+
{29, 256, 56, "IQ1_M"}, {30, 1, 2, "BF16"}, {34, 256, 54, "TQ1_0"}, {35, 256, 66, "TQ2_0"}, {39, 32, 17, "MXFP4"},
|
|
22
|
+
};
|
|
23
|
+
#define NTYPES (sizeof TYPES / sizeof TYPES[0])
|
|
24
|
+
|
|
25
|
+
const char *cl_type_name(uint32_t type) {
|
|
26
|
+
for (size_t i = 0; i < NTYPES; i++) if ((uint32_t)TYPES[i].id == type) return TYPES[i].name;
|
|
27
|
+
return "unknown";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
typedef struct { const uint8_t *p; size_t n, pos; int over, bad; } cur;
|
|
31
|
+
|
|
32
|
+
static int need(cur *c, size_t k) {
|
|
33
|
+
if (c->over || c->n - c->pos < k) { c->over = 1; return 0; }
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
static uint32_t rd32(cur *c) { uint32_t v = 0; if (need(c, 4)) { memcpy(&v, c->p + c->pos, 4); c->pos += 4; } return v; }
|
|
37
|
+
static uint64_t rd64(cur *c) { uint64_t v = 0; if (need(c, 8)) { memcpy(&v, c->p + c->pos, 8); c->pos += 8; } return v; }
|
|
38
|
+
|
|
39
|
+
#define MAX_STR (1u << 24)
|
|
40
|
+
static void rdstr(cur *c, char *out, size_t cap) {
|
|
41
|
+
uint64_t len = rd64(c);
|
|
42
|
+
if (c->over) return;
|
|
43
|
+
if (len > MAX_STR) { c->bad = 1; return; }
|
|
44
|
+
if (!need(c, (size_t)len)) return;
|
|
45
|
+
if (out && cap) { size_t k = len < cap - 1 ? (size_t)len : cap - 1; memcpy(out, c->p + c->pos, k); out[k] = 0; }
|
|
46
|
+
c->pos += (size_t)len;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static size_t scalar_size(uint32_t t) {
|
|
50
|
+
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; }
|
|
51
|
+
}
|
|
52
|
+
static int is_int(uint32_t t) { return t <= 5 || t == 10 || t == 11; }
|
|
53
|
+
|
|
54
|
+
static uint64_t read_int(cur *c, uint32_t t) {
|
|
55
|
+
switch (t) {
|
|
56
|
+
case 0: case 1: { if (!need(c, 1)) return 0; uint8_t v = c->p[c->pos++]; return t == 1 ? (uint64_t)(int64_t)(int8_t)v : v; }
|
|
57
|
+
case 2: case 3: { if (!need(c, 2)) return 0; uint16_t v; memcpy(&v, c->p + c->pos, 2); c->pos += 2; return t == 3 ? (uint64_t)(int64_t)(int16_t)v : v; }
|
|
58
|
+
case 4: return rd32(c);
|
|
59
|
+
case 5: return (uint64_t)(int64_t)(int32_t)rd32(c);
|
|
60
|
+
default: return rd64(c);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static void skip_value(cur *c, uint32_t t, int depth) {
|
|
65
|
+
if (c->over || c->bad) return;
|
|
66
|
+
if (depth > 4) { c->bad = 1; return; }
|
|
67
|
+
size_t s = scalar_size(t);
|
|
68
|
+
if (s) { if (need(c, s)) c->pos += s; return; }
|
|
69
|
+
if (t == 8) { rdstr(c, NULL, 0); return; }
|
|
70
|
+
if (t == 9) {
|
|
71
|
+
uint32_t et = rd32(c);
|
|
72
|
+
uint64_t n = rd64(c);
|
|
73
|
+
if (c->over) return;
|
|
74
|
+
if (n > (1ull << 32)) { c->bad = 1; return; }
|
|
75
|
+
size_t es = scalar_size(et);
|
|
76
|
+
if (es) {
|
|
77
|
+
if (n > (c->n - c->pos) / es + 1) { c->over = 1; return; }
|
|
78
|
+
if (!need(c, (size_t)n * es)) return;
|
|
79
|
+
c->pos += (size_t)n * es;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
for (uint64_t i = 0; i < n && !c->over && !c->bad; i++) skip_value(c, et, depth + 1);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
c->bad = 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
typedef struct { char key[96]; uint64_t v; } kv_int;
|
|
89
|
+
#define MAX_KV_INT 24
|
|
90
|
+
|
|
91
|
+
static int tensor_nbytes(const cl_tensor *t, uint64_t *out) {
|
|
92
|
+
const uint32_t *bs = NULL;
|
|
93
|
+
uint32_t block = 0, bytes = 0;
|
|
94
|
+
(void)bs;
|
|
95
|
+
for (size_t i = 0; i < NTYPES; i++) if ((uint32_t)TYPES[i].id == t->type) { block = TYPES[i].block; bytes = TYPES[i].bytes; }
|
|
96
|
+
if (!block || t->ndims == 0) return 0;
|
|
97
|
+
if (t->dims[0] % block) return 0;
|
|
98
|
+
unsigned __int128 n = (t->dims[0] / block) * (unsigned __int128)bytes;
|
|
99
|
+
for (uint32_t i = 1; i < t->ndims; i++) { n *= t->dims[i]; if (n > ((unsigned __int128)1 << 62)) return 0; }
|
|
100
|
+
*out = (uint64_t)n;
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static int starts(const char *s, const char *p) { return strncmp(s, p, strlen(p)) == 0; }
|
|
105
|
+
static int ends(const char *s, const char *suf) { size_t a = strlen(s), b = strlen(suf); return a >= b && !strcmp(s + a - b, suf); }
|
|
106
|
+
|
|
107
|
+
static uint64_t find_int(const kv_int *kv, int n, const char *arch, const char *suffix) {
|
|
108
|
+
char want[160];
|
|
109
|
+
snprintf(want, sizeof want, "%s.%s", arch, suffix);
|
|
110
|
+
for (int i = 0; i < n; i++) if (!strcmp(kv[i].key, want)) return kv[i].v;
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* 0 ok, 1 need a larger buffer, -1 bad file (message in err). */
|
|
115
|
+
static int parse(cl_model *m, const uint8_t *buf, size_t n, char *err, size_t errlen) {
|
|
116
|
+
cur c = {buf, n, 0, 0, 0};
|
|
117
|
+
if (n >= 4 && memcmp(buf, "GGUF", 4)) { snprintf(err, errlen, "not a GGUF file (bad magic)"); return -1; }
|
|
118
|
+
c.pos = 4;
|
|
119
|
+
m->version = rd32(&c);
|
|
120
|
+
uint64_t nt = rd64(&c), nkv = rd64(&c);
|
|
121
|
+
if (c.over) return 1;
|
|
122
|
+
if (m->version != 2 && m->version != 3) { snprintf(err, errlen, "unsupported GGUF version %u", m->version); return -1; }
|
|
123
|
+
if (nt > 1000000 || nkv > 1000000) { snprintf(err, errlen, "implausible tensor or metadata count"); return -1; }
|
|
124
|
+
kv_int kv[MAX_KV_INT];
|
|
125
|
+
int nk = 0;
|
|
126
|
+
uint64_t alignment = 32;
|
|
127
|
+
m->arch[0] = m->name[0] = 0;
|
|
128
|
+
for (uint64_t i = 0; i < nkv; i++) {
|
|
129
|
+
char key[160];
|
|
130
|
+
rdstr(&c, key, sizeof key);
|
|
131
|
+
uint32_t t = rd32(&c);
|
|
132
|
+
if (c.over) return 1;
|
|
133
|
+
if (c.bad) { snprintf(err, errlen, "corrupt metadata"); return -1; }
|
|
134
|
+
if (!strcmp(key, "general.architecture") && t == 8) rdstr(&c, m->arch, sizeof m->arch);
|
|
135
|
+
else if (!strcmp(key, "general.name") && t == 8) rdstr(&c, m->name, sizeof m->name);
|
|
136
|
+
else if (is_int(t) && (ends(key, ".block_count") || ends(key, ".expert_count") || ends(key, ".expert_used_count") || !strcmp(key, "general.alignment"))) {
|
|
137
|
+
uint64_t v = read_int(&c, t);
|
|
138
|
+
if (!strcmp(key, "general.alignment")) { if (v > 0 && v <= (1u << 20)) alignment = v; }
|
|
139
|
+
else if (nk < MAX_KV_INT) { snprintf(kv[nk].key, sizeof kv[nk].key, "%s", key); kv[nk++].v = v; }
|
|
140
|
+
} else skip_value(&c, t, 0);
|
|
141
|
+
if (c.over) return 1;
|
|
142
|
+
if (c.bad) { snprintf(err, errlen, "corrupt metadata value in \"%s\"", key); return -1; }
|
|
143
|
+
}
|
|
144
|
+
m->n_tensors = nt;
|
|
145
|
+
m->tensors = calloc(nt ? nt : 1, sizeof(cl_tensor));
|
|
146
|
+
if (!m->tensors) { snprintf(err, errlen, "out of memory"); return -1; }
|
|
147
|
+
uint64_t rel[1]; (void)rel;
|
|
148
|
+
uint64_t *rels = calloc(nt ? nt : 1, sizeof(uint64_t));
|
|
149
|
+
if (!rels) { snprintf(err, errlen, "out of memory"); return -1; }
|
|
150
|
+
for (uint64_t i = 0; i < nt; i++) {
|
|
151
|
+
cl_tensor *t = &m->tensors[i];
|
|
152
|
+
rdstr(&c, t->name, sizeof t->name);
|
|
153
|
+
t->ndims = rd32(&c);
|
|
154
|
+
if (c.over) { free(rels); return 1; }
|
|
155
|
+
if (t->ndims == 0 || t->ndims > 4 || c.bad) { free(rels); snprintf(err, errlen, "tensor %llu has %u dimensions", (unsigned long long)i, t->ndims); return -1; }
|
|
156
|
+
for (uint32_t d = 0; d < t->ndims; d++) t->dims[d] = rd64(&c);
|
|
157
|
+
t->type = rd32(&c);
|
|
158
|
+
rels[i] = rd64(&c);
|
|
159
|
+
if (c.over) { free(rels); return 1; }
|
|
160
|
+
}
|
|
161
|
+
m->data_offset = ((c.pos + alignment - 1) / alignment) * alignment;
|
|
162
|
+
for (uint64_t i = 0; i < nt; i++) {
|
|
163
|
+
cl_tensor *t = &m->tensors[i];
|
|
164
|
+
t->offset = m->data_offset + rels[i];
|
|
165
|
+
if (!tensor_nbytes(t, &t->nbytes)) { t->nbytes = 0; m->unsized_tensors++; continue; }
|
|
166
|
+
if (t->offset > m->file_size || t->nbytes > m->file_size - t->offset) { free(rels); snprintf(err, errlen, "tensor \"%s\" points outside the file (truncated download?)", t->name); return -1; }
|
|
167
|
+
}
|
|
168
|
+
free(rels);
|
|
169
|
+
|
|
170
|
+
m->n_layers = (uint32_t)find_int(kv, nk, m->arch, "block_count");
|
|
171
|
+
m->n_experts = (uint32_t)find_int(kv, nk, m->arch, "expert_count");
|
|
172
|
+
m->n_used = (uint32_t)find_int(kv, nk, m->arch, "expert_used_count");
|
|
173
|
+
if (m->n_layers == 0 || m->n_layers > 4096) { snprintf(err, errlen, "the file does not say how many layers it has"); return -1; }
|
|
174
|
+
m->layers = calloc(m->n_layers, sizeof(cl_layer));
|
|
175
|
+
if (!m->layers) { snprintf(err, errlen, "out of memory"); return -1; }
|
|
176
|
+
for (uint64_t i = 0; i < nt; i++) {
|
|
177
|
+
const cl_tensor *t = &m->tensors[i];
|
|
178
|
+
if (!t->nbytes) continue;
|
|
179
|
+
m->total_bytes += t->nbytes;
|
|
180
|
+
unsigned layer = 0;
|
|
181
|
+
int off = 0;
|
|
182
|
+
if (!starts(t->name, "blk.") || sscanf(t->name + 4, "%u.%n", &layer, &off) != 1 || layer >= m->n_layers) { m->resident_bytes += t->nbytes; continue; }
|
|
183
|
+
const char *rest = t->name + 4 + off;
|
|
184
|
+
cl_layer *L = &m->layers[layer];
|
|
185
|
+
if (!strcmp(rest, "ffn_gate_inp.weight")) { L->router_bytes += t->nbytes; m->resident_bytes += t->nbytes; continue; }
|
|
186
|
+
if (starts(rest, "ffn_") && ends(rest, "_exps.weight") && L->n_exp < 3 && t->ndims >= 2) {
|
|
187
|
+
uint64_t ne = t->dims[t->ndims - 1];
|
|
188
|
+
if (ne > 0 && t->nbytes % ne == 0) {
|
|
189
|
+
if (m->n_experts < ne) m->n_experts = (uint32_t)ne;
|
|
190
|
+
L->exp_off[L->n_exp] = t->offset;
|
|
191
|
+
L->exp_slab[L->n_exp] = t->nbytes / ne;
|
|
192
|
+
L->expert_bytes += t->nbytes / ne;
|
|
193
|
+
L->n_exp++;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (starts(rest, "ffn_") && strstr(rest, "_exps")) { snprintf(err, errlen, "tensor \"%s\": this expert layout is not supported (only fused per-layer expert tensors)", t->name); return -1; }
|
|
198
|
+
L->dense = realloc(L->dense, (size_t)(L->n_dense + 1) * sizeof(cl_range));
|
|
199
|
+
if (!L->dense) { snprintf(err, errlen, "out of memory"); return -1; }
|
|
200
|
+
L->dense[L->n_dense].offset = t->offset;
|
|
201
|
+
L->dense[L->n_dense].length = t->nbytes;
|
|
202
|
+
L->n_dense++;
|
|
203
|
+
L->dense_bytes += t->nbytes;
|
|
204
|
+
}
|
|
205
|
+
for (uint32_t l = 0; l < m->n_layers; l++) {
|
|
206
|
+
if (m->layers[l].n_exp) m->moe_layers++;
|
|
207
|
+
if (m->layers[l].dense_bytes > m->max_dense_bytes) m->max_dense_bytes = m->layers[l].dense_bytes;
|
|
208
|
+
if (m->layers[l].expert_bytes > m->max_expert_bytes) m->max_expert_bytes = m->layers[l].expert_bytes;
|
|
209
|
+
}
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
void cl_model_close(cl_model *m) {
|
|
214
|
+
if (m->layers) for (uint32_t l = 0; l < m->n_layers; l++) free(m->layers[l].dense);
|
|
215
|
+
free(m->layers);
|
|
216
|
+
free(m->tensors);
|
|
217
|
+
memset(m, 0, sizeof *m);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
int cl_model_open(cl_model *m, const char *path, char *err, size_t errlen) {
|
|
221
|
+
memset(m, 0, sizeof *m);
|
|
222
|
+
int fd = open(path, O_RDONLY);
|
|
223
|
+
if (fd < 0) { snprintf(err, errlen, "cannot open %s: %s", path, strerror(errno)); return 1; }
|
|
224
|
+
struct stat st;
|
|
225
|
+
if (fstat(fd, &st) || st.st_size < 24) { close(fd); snprintf(err, errlen, "%s is too small to be a GGUF file", path); return 1; }
|
|
226
|
+
m->file_size = (uint64_t)st.st_size;
|
|
227
|
+
size_t want = m->file_size < (1u << 20) ? (size_t)m->file_size : (1u << 20);
|
|
228
|
+
for (;;) {
|
|
229
|
+
uint8_t *buf = malloc(want);
|
|
230
|
+
if (!buf) { close(fd); snprintf(err, errlen, "out of memory"); return 1; }
|
|
231
|
+
ssize_t got = pread(fd, buf, want, 0);
|
|
232
|
+
if (got < 0 || (size_t)got != want) { free(buf); close(fd); snprintf(err, errlen, "cannot read %s", path); return 1; }
|
|
233
|
+
cl_model_close(m);
|
|
234
|
+
m->file_size = (uint64_t)st.st_size;
|
|
235
|
+
int r = parse(m, buf, want, err, errlen);
|
|
236
|
+
free(buf);
|
|
237
|
+
if (r == 0) { close(fd); return 0; }
|
|
238
|
+
if (r < 0) { close(fd); cl_model_close(m); return 1; }
|
|
239
|
+
if (want >= m->file_size || want >= ((size_t)1 << 29)) { close(fd); cl_model_close(m); snprintf(err, errlen, "the header is truncated or larger than 512 MiB"); return 1; }
|
|
240
|
+
want = want * 4 > m->file_size ? (size_t)m->file_size : want * 4;
|
|
241
|
+
}
|
|
242
|
+
}
|
package/c/src/gguf.h
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* GGUF v2/v3 reader: header, metadata we need, tensor table, and the layout the streamer needs (where each routed expert lives). */
|
|
2
|
+
#ifndef CL_GGUF_H
|
|
3
|
+
#define CL_GGUF_H
|
|
4
|
+
#include <stddef.h>
|
|
5
|
+
#include <stdint.h>
|
|
6
|
+
|
|
7
|
+
typedef struct { uint64_t offset, length; } cl_range;
|
|
8
|
+
|
|
9
|
+
typedef struct {
|
|
10
|
+
char name[160];
|
|
11
|
+
uint32_t ndims;
|
|
12
|
+
uint64_t dims[4];
|
|
13
|
+
uint32_t type;
|
|
14
|
+
uint64_t offset; /* absolute file offset of the tensor data */
|
|
15
|
+
uint64_t nbytes; /* 0 when the type or shape is not understood */
|
|
16
|
+
} cl_tensor;
|
|
17
|
+
|
|
18
|
+
typedef struct {
|
|
19
|
+
cl_range *dense; /* attention, norms, shared experts, dense feed-forward: read for every token */
|
|
20
|
+
int n_dense;
|
|
21
|
+
uint64_t dense_bytes;
|
|
22
|
+
uint64_t router_bytes;
|
|
23
|
+
/* Routed experts are fused: one tensor per gate/up/down holds every expert, experts slowest-varying, so each is one slab. */
|
|
24
|
+
int n_exp; /* 0 for a dense layer, else 1..3 */
|
|
25
|
+
uint64_t exp_off[3]; /* file offset of expert 0 of each fused tensor */
|
|
26
|
+
uint64_t exp_slab[3]; /* bytes of ONE expert in each fused tensor */
|
|
27
|
+
uint64_t expert_bytes;/* sum of exp_slab */
|
|
28
|
+
} cl_layer;
|
|
29
|
+
|
|
30
|
+
typedef struct {
|
|
31
|
+
char arch[64];
|
|
32
|
+
char name[128];
|
|
33
|
+
uint32_t version;
|
|
34
|
+
uint64_t file_size, data_offset;
|
|
35
|
+
uint32_t n_layers, n_experts, n_used;
|
|
36
|
+
cl_tensor *tensors;
|
|
37
|
+
uint64_t n_tensors;
|
|
38
|
+
cl_layer *layers;
|
|
39
|
+
uint64_t resident_bytes, max_dense_bytes, max_expert_bytes, total_bytes;
|
|
40
|
+
uint32_t moe_layers;
|
|
41
|
+
uint32_t unsized_tensors;
|
|
42
|
+
} cl_model;
|
|
43
|
+
|
|
44
|
+
/* Returns 0 on success. On failure returns nonzero and writes a message to err. */
|
|
45
|
+
int cl_model_open(cl_model *m, const char *path, char *err, size_t errlen);
|
|
46
|
+
void cl_model_close(cl_model *m);
|
|
47
|
+
const char *cl_type_name(uint32_t type);
|
|
48
|
+
#endif
|