@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 ADDED
@@ -0,0 +1,85 @@
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/quant.c src/meta.c src/xs.c src/forward.c src/tokenizer.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
+ # Golden test: a tiny generated OLMoE with random K-quant weights, checked against an independent pure-Python forward pass (needs only python3).
44
+ # Run on the default build and, to cover every kernel path, on the portable-C and NEON-without-dotprod builds.
45
+ GOLDEN_GGUF = build/tiny_olmoe.gguf
46
+ GOLDEN_Q3 = build/tiny_qwen3moe.gguf
47
+ golden-files: build
48
+ python3 test/tiny_olmoe.py write $(GOLDEN_GGUF)
49
+ python3 test/tiny_olmoe.py write $(GOLDEN_Q3) --arch qwen3moe
50
+ golden: $(BIN) golden-files
51
+ python3 test/tiny_olmoe.py check $(BIN) $(GOLDEN_GGUF)
52
+ python3 test/tiny_olmoe.py check $(BIN) $(GOLDEN_Q3) --arch qwen3moe
53
+ golden-paths: $(BIN) golden-files
54
+ python3 test/tiny_olmoe.py check $(BIN) $(GOLDEN_GGUF)
55
+ python3 test/tiny_olmoe.py check $(BIN) $(GOLDEN_Q3) --arch qwen3moe
56
+ $(CC) $(PATH_FLAGS) -DCL_FORCE_SCALAR $(SRC) src/main.c -o build/engine-scalar $(LDLIBS)
57
+ python3 test/tiny_olmoe.py check build/engine-scalar $(GOLDEN_GGUF)
58
+ python3 test/tiny_olmoe.py check build/engine-scalar $(GOLDEN_Q3) --arch qwen3moe
59
+ ifeq ($(shell uname -m),arm64)
60
+ $(CC) $(PATH_FLAGS) -mcpu=native -DCL_NO_DOTPROD $(SRC) src/main.c -o build/engine-neon $(LDLIBS)
61
+ python3 test/tiny_olmoe.py check build/engine-neon $(GOLDEN_GGUF)
62
+ python3 test/tiny_olmoe.py check build/engine-neon $(GOLDEN_Q3) --arch qwen3moe
63
+ endif
64
+
65
+ # Mutation fuzzing of the file readers under ASan + UBSan: corrupt a valid model many ways, expect clean errors and never a crash or hang.
66
+ fuzz: build
67
+ $(CC) -O1 -g -std=c11 -fsanitize=address,undefined -fno-omit-frame-pointer $(SRC) src/main.c -o build/engine-asan $(LDLIBS)
68
+ python3 test/tiny_olmoe.py write $(GOLDEN_GGUF)
69
+ python3 test/fuzz_mutate.py build/engine-asan $(GOLDEN_GGUF) --iterations $(or $(ITER),600)
70
+ python3 test/fuzz_mutate.py build/engine-asan $(FIXTURE) --iterations $(or $(ITER),600) --seed 7
71
+
72
+ # Address + undefined-behaviour sanitizers on the unit tests and a threaded streaming run, then the thread sanitizer on the same run.
73
+ SAN_RUN = stream $(SMALL) --tokens 60 --cache-mb 1 --stage 1 --io-threads 3 --smart 1,8,1 --policy adaptive
74
+ sanitize: build
75
+ $(CC) -O1 -g -std=c11 -fsanitize=address,undefined -fno-omit-frame-pointer -Isrc $(SRC) test/test_engine.c -o build/test-asan $(LDLIBS)
76
+ ./build/test-asan $(FIXTURE)
77
+ $(CC) -O1 -g -std=c11 -fsanitize=address,undefined -fno-omit-frame-pointer $(SRC) src/main.c -o build/engine-asan $(LDLIBS)
78
+ $(CC) -O1 -g -std=c11 -fsanitize=thread $(SRC) src/main.c -o build/engine-tsan $(LDLIBS)
79
+ @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
80
+ # Compares tokenization, logits and routing with llama.cpp (needs llama-server and llama-tokenize on PATH): make verify-llama MODEL=path/to/olmoe.gguf
81
+ verify-llama: $(BIN)
82
+ python3 test/verify_llama.py $(MODEL) --engine $(BIN)
83
+ clean:
84
+ rm -rf build
85
+ .PHONY: fuzz golden golden-files golden-paths verify-llama 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