@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/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
package/c/src/kernel.c ADDED
@@ -0,0 +1,125 @@
1
+ #include "kernel.h"
2
+ #include <math.h>
3
+ #include <string.h>
4
+
5
+ #if !defined(CL_FORCE_SCALAR) && defined(__ARM_NEON)
6
+ #include <arm_neon.h>
7
+ #define CL_PATH_NEON 1
8
+ #elif !defined(CL_FORCE_SCALAR) && defined(__AVX2__)
9
+ #include <immintrin.h>
10
+ #define CL_PATH_AVX2 1
11
+ #endif
12
+
13
+ const char *cl_kernel_name(void) {
14
+ #if defined(CL_PATH_NEON) && defined(__ARM_FEATURE_DOTPROD) && !defined(CL_NO_DOTPROD)
15
+ return "neon+dotprod";
16
+ #elif defined(CL_PATH_NEON)
17
+ return "neon";
18
+ #elif defined(CL_PATH_AVX2)
19
+ return "avx2";
20
+ #else
21
+ return "scalar";
22
+ #endif
23
+ }
24
+
25
+ float cl_f16_to_f32(uint16_t h) {
26
+ uint32_t sign = (uint32_t)(h & 0x8000u) << 16, exp = (h >> 10) & 0x1f, man = h & 0x3ffu, bits;
27
+ if (exp == 0) {
28
+ if (man == 0) bits = sign;
29
+ else { /* subnormal: normalise */
30
+ int e = -1;
31
+ do { e++; man <<= 1; } while (!(man & 0x400u));
32
+ bits = sign | ((uint32_t)(127 - 15 - e) << 23) | ((man & 0x3ffu) << 13);
33
+ }
34
+ } else if (exp == 31) bits = sign | 0x7f800000u | (man << 13);
35
+ else bits = sign | ((exp + 112) << 23) | (man << 13);
36
+ float f;
37
+ memcpy(&f, &bits, sizeof f);
38
+ return f;
39
+ }
40
+
41
+ void cl_quantize_q8_0(const float *x, cl_block_q8_0 *y, int n) {
42
+ for (int b = 0; b < n / CL_QK; b++) {
43
+ float amax = 0;
44
+ for (int j = 0; j < CL_QK; j++) { float a = fabsf(x[b * CL_QK + j]); if (a > amax) amax = a; }
45
+ float d = amax / 127.0f, id = d != 0.0f ? 1.0f / d : 0.0f;
46
+ y[b].d = d;
47
+ for (int j = 0; j < CL_QK; j++) y[b].qs[j] = (int8_t)roundf(x[b * CL_QK + j] * id);
48
+ }
49
+ }
50
+
51
+ float cl_dot_q4_0_q8_0_scalar(int nblocks, const cl_block_q4_0 *w, const cl_block_q8_0 *a) {
52
+ float sum = 0;
53
+ for (int b = 0; b < nblocks; b++) {
54
+ int s = 0;
55
+ for (int j = 0; j < CL_QK / 2; j++) {
56
+ int lo = (w[b].qs[j] & 0x0f) - 8, hi = (w[b].qs[j] >> 4) - 8;
57
+ s += lo * a[b].qs[j] + hi * a[b].qs[j + CL_QK / 2];
58
+ }
59
+ sum += (float)s * (cl_f16_to_f32(w[b].d) * a[b].d);
60
+ }
61
+ return sum;
62
+ }
63
+
64
+ float cl_dot_q4_0_q8_0(int nblocks, const cl_block_q4_0 *w, const cl_block_q8_0 *a) {
65
+ #if defined(CL_PATH_NEON)
66
+ const uint8x16_t m4 = vdupq_n_u8(0x0f);
67
+ const int8x16_t eight = vdupq_n_s8(8);
68
+ float sum = 0;
69
+ for (int b = 0; b < nblocks; b++) {
70
+ uint8x16_t raw = vld1q_u8(w[b].qs);
71
+ /* unpack: low nibbles are weights 0..15, high nibbles are weights 16..31; both shifted from 0..15 to -8..7 */
72
+ int8x16_t lo = vsubq_s8(vreinterpretq_s8_u8(vandq_u8(raw, m4)), eight);
73
+ int8x16_t hi = vsubq_s8(vreinterpretq_s8_u8(vshrq_n_u8(raw, 4)), eight);
74
+ int8x16_t a0 = vld1q_s8(a[b].qs), a1 = vld1q_s8(a[b].qs + 16);
75
+ #if defined(__ARM_FEATURE_DOTPROD) && !defined(CL_NO_DOTPROD)
76
+ int32x4_t p = vdotq_s32(vdotq_s32(vdupq_n_s32(0), lo, a0), hi, a1);
77
+ #else
78
+ /* |weight| <= 8 and |activation| <= 127, so two products added in 16 bits cannot overflow (at most 2032) */
79
+ int16x8_t p0 = vmlal_s8(vmull_s8(vget_low_s8(lo), vget_low_s8(a0)), vget_high_s8(lo), vget_high_s8(a0));
80
+ int16x8_t p1 = vmlal_s8(vmull_s8(vget_low_s8(hi), vget_low_s8(a1)), vget_high_s8(hi), vget_high_s8(a1));
81
+ int32x4_t p = vpadalq_s16(vpaddlq_s16(p0), p1);
82
+ #endif
83
+ sum += (float)vaddvq_s32(p) * (cl_f16_to_f32(w[b].d) * a[b].d);
84
+ }
85
+ return sum;
86
+ #elif defined(CL_PATH_AVX2)
87
+ const __m128i m4 = _mm_set1_epi8(0x0f);
88
+ const __m256i eight = _mm256_set1_epi8(8), ones = _mm256_set1_epi16(1);
89
+ float sum = 0;
90
+ for (int b = 0; b < nblocks; b++) {
91
+ __m128i raw = _mm_loadu_si128((const __m128i *)(const void *)w[b].qs);
92
+ __m128i lo = _mm_and_si128(raw, m4);
93
+ __m128i hi = _mm_and_si128(_mm_srli_epi16(raw, 4), m4);
94
+ __m256i q = _mm256_sub_epi8(_mm256_inserti128_si256(_mm256_castsi128_si256(lo), hi, 1), eight); /* weights 0..31 as -8..7 */
95
+ __m256i av = _mm256_loadu_si256((const __m256i *)(const void *)a[b].qs);
96
+ /* maddubs wants unsigned x signed: use |q| and move q's sign onto the activation */
97
+ __m256i p16 = _mm256_maddubs_epi16(_mm256_sign_epi8(q, q), _mm256_sign_epi8(av, q));
98
+ __m256i p32 = _mm256_madd_epi16(p16, ones);
99
+ __m128i s = _mm_add_epi32(_mm256_castsi256_si128(p32), _mm256_extracti128_si256(p32, 1));
100
+ s = _mm_add_epi32(s, _mm_shuffle_epi32(s, _MM_SHUFFLE(1, 0, 3, 2)));
101
+ s = _mm_add_epi32(s, _mm_shuffle_epi32(s, _MM_SHUFFLE(2, 3, 0, 1)));
102
+ sum += (float)_mm_cvtsi128_si32(s) * (cl_f16_to_f32(w[b].d) * a[b].d);
103
+ }
104
+ return sum;
105
+ #else
106
+ return cl_dot_q4_0_q8_0_scalar(nblocks, w, a);
107
+ #endif
108
+ }
109
+
110
+ void cl_gemv_q4_0(const cl_block_q4_0 *w, int rows, int blocks_per_row, const cl_block_q8_0 *a, float *out) {
111
+ for (int r = 0; r < rows; r++) out[r] = cl_dot_q4_0_q8_0(blocks_per_row, w + (size_t)r * blocks_per_row, a);
112
+ }
113
+
114
+ uint64_t cl_q4_0_pass(const void *bytes, uint64_t n, const cl_block_q8_0 *act, int blocks_per_row) {
115
+ uint64_t nblocks = n / sizeof(cl_block_q4_0), rows = nblocks / (uint64_t)blocks_per_row, digest = 0;
116
+ const cl_block_q4_0 *w = (const cl_block_q4_0 *)bytes;
117
+ for (uint64_t r = 0; r < rows; r++) {
118
+ float v = cl_dot_q4_0_q8_0(blocks_per_row, w + r * (uint64_t)blocks_per_row, act);
119
+ if (!isfinite(v)) v = 0.0f; /* arbitrary bytes can hold an Inf or NaN scale */
120
+ uint32_t bits;
121
+ memcpy(&bits, &v, sizeof bits);
122
+ digest = digest * 1099511628211ull + bits;
123
+ }
124
+ return digest;
125
+ }
package/c/src/kernel.h ADDED
@@ -0,0 +1,45 @@
1
+ #ifndef CL_KERNEL_H
2
+ #define CL_KERNEL_H
3
+ #include <stdint.h>
4
+
5
+ /*
6
+ * Q4_0 weights times Q8_0 activations: the inner loop of a quantized matrix-vector product.
7
+ *
8
+ * A Q4_0 block holds 32 weights as 4-bit numbers plus one half-precision scale (18 bytes). Weight j (0..15) is the low nibble of byte j and
9
+ * weight j+16 is the high nibble of the same byte; the value is (nibble - 8) * scale. This is ggml's layout, so a real Q4_0 tensor can be read
10
+ * as it is. The activations are quantized to int8 blocks of 32 with a float scale (our own format, 36 bytes), and the dot product is exact
11
+ * integer arithmetic inside a block, so the vector paths agree with the scalar path up to the order in which block sums are added.
12
+ *
13
+ * Compile-time paths: ARM NEON (with the dot-product instruction when the CPU target has it), AVX2, and portable C11. -DCL_FORCE_SCALAR picks
14
+ * the portable one on any machine; the tests use that to compare it with the vector path.
15
+ */
16
+ #define CL_QK 32
17
+
18
+ typedef struct { uint16_t d; uint8_t qs[16]; } cl_block_q4_0; /* 18 bytes */
19
+ typedef struct { float d; int8_t qs[CL_QK]; } cl_block_q8_0; /* 36 bytes */
20
+ _Static_assert(sizeof(cl_block_q4_0) == 18, "a Q4_0 block is 18 bytes");
21
+ _Static_assert(sizeof(cl_block_q8_0) == 36, "a Q8_0 activation block is 36 bytes");
22
+
23
+ /* The path this build uses: "neon+dotprod", "neon", "avx2" or "scalar". */
24
+ const char *cl_kernel_name(void);
25
+
26
+ float cl_f16_to_f32(uint16_t h);
27
+
28
+ /* Quantizes n floats (a multiple of 32) to n/32 blocks. */
29
+ void cl_quantize_q8_0(const float *x, cl_block_q8_0 *y, int n);
30
+
31
+ /* The dot product of nblocks weight blocks with nblocks activation blocks. */
32
+ float cl_dot_q4_0_q8_0(int nblocks, const cl_block_q4_0 *w, const cl_block_q8_0 *a);
33
+ float cl_dot_q4_0_q8_0_scalar(int nblocks, const cl_block_q4_0 *w, const cl_block_q8_0 *a);
34
+
35
+ /* rows x blocks_per_row weights times one activation vector of blocks_per_row blocks. */
36
+ void cl_gemv_q4_0(const cl_block_q4_0 *w, int rows, int blocks_per_row, const cl_block_q8_0 *a, float *out);
37
+
38
+ /*
39
+ * Runs the product over a buffer of streamed bytes as if it were Q4_0 rows of blocks_per_row blocks (a trailing partial row is ignored) and
40
+ * folds the finite results into a digest. Used by the streaming engine: the bytes of a Q4_K or IQ2 model are not Q4_0, so what is
41
+ * computed is not that model's maths; the point is that the loaded bytes go through a real quantized kernel.
42
+ */
43
+ uint64_t cl_q4_0_pass(const void *bytes, uint64_t n, const cl_block_q8_0 *act, int blocks_per_row);
44
+
45
+ #endif