English
azomDev commited on
Commit
4dd1ca4
·
verified ·
1 Parent(s): 1a625ca

Uploaded runtime

Browse files
Files changed (3) hide show
  1. runtime/Makefile +20 -0
  2. runtime/main.c +704 -0
  3. runtime/ttm.h +418 -0
runtime/Makefile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # TinyTitle runtime
2
+ CC ?= cc
3
+ CFLAGS ?= -O2 -march=native -Wall
4
+
5
+ all: title-v1
6
+
7
+ title-v1: main.c ttm.h
8
+ $(CC) $(CFLAGS) -o $@ main.c -lm
9
+
10
+ # sanitizer build for the adversarial gate
11
+ asan: main.c ttm.h
12
+ $(CC) -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer -o title-v1-asan main.c -lm
13
+
14
+ musl: main.c ttm.h
15
+ musl-gcc -O2 -static -o title-v1-musl main.c -lm
16
+
17
+ clean:
18
+ rm -f title-v1 title-v1-asan title-v1-musl
19
+
20
+ .PHONY: all asan musl clean
runtime/main.c ADDED
@@ -0,0 +1,704 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // main.c — TinyTitle pointer-generator title CLI (.ttm1)
2
+ // Usage: ./title-v1 <model.ttm1> "user message" (or stdin)
3
+ #include "ttm.h"
4
+ #include <math.h>
5
+ #include <time.h>
6
+ #include <unistd.h>
7
+ #include <sys/resource.h>
8
+
9
+ // ---- tensor name constants (match python state_dict) ----
10
+ #define T_EMB "emb.weight"
11
+ #define T_ENC_F_W "enc.fwd.w.weight"
12
+ #define T_ENC_F_U "enc.fwd.uh.weight"
13
+ #define T_ENC_F_B "enc.fwd.b"
14
+ #define T_ENC_R_W "enc.bwd.w.weight"
15
+ #define T_ENC_R_U "enc.bwd.uh.weight"
16
+ #define T_ENC_R_B "enc.bwd.b"
17
+ #define T_DEC_W "dec.w.weight"
18
+ #define T_DEC_CTX "dec_ctx.weight"
19
+ #define T_DEC_U "dec.uh.weight"
20
+ #define T_DEC_B "dec.b"
21
+ #define T_WATT_V "w_att_v.weight"
22
+ #define T_WATT_H "w_att_h.weight"
23
+ #define T_V_ATT "v_att.weight"
24
+ #define T_COV "cov.weight"
25
+ #define T_WC "w_c.weight"
26
+ #define T_WO "w_o.weight"
27
+ #define T_GATE "gate.weight"
28
+ #define T_GATE_B "gate.bias"
29
+ #define T_DEC_INIT "dec_init.weight"
30
+ #define T_DEC_INIT_B "dec_init.bias"
31
+
32
+ #define D_EMB TTM_D_EMB
33
+ #define D_ENC_HALF TTM_D_ENC
34
+ #define D_ENC (2 * TTM_D_ENC)
35
+ #define D_DEC TTM_D_DEC
36
+ #define D_ATT TTM_D_ATT
37
+ #define V_MAX TTM_MAX_VOCAB
38
+
39
+
40
+ // ---- matvec: [rows x cols] int8 tensor * x -> y ----
41
+ static void matvec(const ttm_model *m, const char *name, int rows, int cols,
42
+ const float *x, float *y, float *rowbuf) {
43
+ (void)rowbuf;
44
+ if (ttm_matvec(m, name, rows, cols, x, y) != 0) {
45
+ fprintf(stderr, "ttm1: invalid matvec tensor %s\n", name);
46
+ memset(y, 0, (size_t)rows * sizeof(float));
47
+ }
48
+ }
49
+
50
+ // ---- GRU cell (matches python GRUCell) ----
51
+ // x: [in], h: [hid] -> h_out [hid]; scratch needs 6*hid floats
52
+ static void gru_cell(const ttm_model *m, const char *w_name, const char *u_name,
53
+ const char *b_name, int in, int hid, const float *x, const float *h,
54
+ float *h_out, float *scratch) {
55
+ float *gx = scratch; // 3*hid
56
+ float *gh = scratch + 3 * hid; // 3*hid
57
+ float *rowbuf = scratch + 6 * hid; // max(in, hid) floats
58
+ matvec(m, w_name, 3 * hid, in, x, gx, rowbuf);
59
+ matvec(m, u_name, 3 * hid, hid, h, gh, rowbuf);
60
+ const float *bias = ttm_vec(m, b_name, NULL);
61
+ for (int i = 0; i < 3 * hid; i++) gx[i] += bias[i];
62
+ for (int i = 0; i < hid; i++) {
63
+ float rr = 1.f / (1.f + expf(-(gx[i] + gh[i])));
64
+ float zz = 1.f / (1.f + expf(-(gx[hid + i] + gh[hid + i])));
65
+ float nn = tanhf(gx[2 * hid + i] + rr * gh[2 * hid + i]);
66
+ h_out[i] = (1.f - zz) * nn + zz * h[i];
67
+ }
68
+ }
69
+
70
+ // ---- encode: fills src_enc [n][2*d_enc] ----
71
+ static void encode(const ttm_model *m, const uint32_t *ids, int n,
72
+ float *src_enc, float *work) {
73
+ float *emb = work;
74
+ float *hf = work + (size_t)n * D_EMB;
75
+ float *hb = hf + D_ENC_HALF;
76
+ float *tmp = hb + D_ENC_HALF;
77
+ float *gscratch = tmp + D_ENC_HALF;
78
+ for (int i = 0; i < n; i++)
79
+ ttm_row(m, T_EMB, (int)ids[i], emb + (size_t)i * D_EMB);
80
+ memset(hf, 0, D_ENC_HALF * sizeof(float));
81
+ memset(hb, 0, D_ENC_HALF * sizeof(float));
82
+ for (int i = 0; i < n; i++) {
83
+ gru_cell(m, T_ENC_F_W, T_ENC_F_U, T_ENC_F_B,
84
+ D_EMB, D_ENC_HALF, emb + (size_t)i * D_EMB, hf, tmp, gscratch);
85
+ memcpy(hf, tmp, D_ENC_HALF * sizeof(float));
86
+ memcpy(src_enc + (size_t)i * D_ENC, hf, D_ENC_HALF * sizeof(float));
87
+ }
88
+ for (int i = n - 1; i >= 0; i--) {
89
+ gru_cell(m, T_ENC_R_W, T_ENC_R_U, T_ENC_R_B,
90
+ D_EMB, D_ENC_HALF, emb + (size_t)i * D_EMB, hb, tmp, gscratch);
91
+ memcpy(hb, tmp, D_ENC_HALF * sizeof(float));
92
+ memcpy(src_enc + (size_t)i * D_ENC + D_ENC_HALF,
93
+ hb, D_ENC_HALF * sizeof(float));
94
+ }
95
+ }
96
+
97
+ // ---- decode one step ----
98
+ // h: [192] decoder state, prev_emb: [96], cov: [n] coverage vector
99
+ // fills attn [n], logits [V], p_gen [1]
100
+ // scratch needs: n*128 (e) + 192 (ctx) + 96 (o) + 96 (logit_in) + max(rows) rowbuf
101
+ static void decode_step(const ttm_model *m, const float *h, const float *prev_emb,
102
+ const float *src_enc, int n,
103
+ const float *cov, float *attn, float *logits, float *p_gen,
104
+ float *ctx_out, float *scratch) {
105
+ float *e = scratch; // n*128
106
+ float *ctx = scratch + (size_t)n * D_ATT; // 192
107
+ float *o = ctx + D_ENC; // 96
108
+ float *li = o + D_EMB; // 96
109
+ float *rowbuf = li + D_EMB; // max(192,128,96,3*192)
110
+
111
+ float *wh = rowbuf;
112
+ matvec(m, T_WATT_H, D_ATT, D_DEC, h, wh, rowbuf + D_ATT);
113
+ float *vr = rowbuf + 2 * D_ATT;
114
+ // e[i] = w_att_v(src_enc[i]) + wh + cov[i]*w_cov
115
+ // w_att_v: [128 x 192]
116
+ for (int i = 0; i < n; i++) {
117
+ matvec(m, T_WATT_V, D_ATT, D_ENC, src_enc + (size_t)i * D_ENC, e + (size_t)i * D_ATT, vr);
118
+ for (int j = 0; j < D_ATT; j++) e[(size_t)i * D_ATT + j] += wh[j];
119
+ // cov term: cov is [1] per position; skip (python adds cov via cov.weight)
120
+ // cov.weight: [128 x 1] — c[j] = cov.weight[j] * cov[i]
121
+ for (int j = 0; j < D_ATT; j++) {
122
+ float cw = ttm_elem2(m, T_COV, j, 0);
123
+ e[(size_t)i * D_ATT + j] += cw * cov[i];
124
+ }
125
+ }
126
+ // scores = v_att(tanh(e)) -> [n]
127
+ float mx = -1e30f;
128
+ for (int i = 0; i < n; i++) {
129
+ float s = 0.f;
130
+ float *ei = e + (size_t)i * D_ATT;
131
+ for (int j = 0; j < D_ATT; j++) {
132
+ float vw = ttm_elem2(m, T_V_ATT, 0, j);
133
+ s += vw * tanhf(ei[j]);
134
+ }
135
+ attn[i] = s;
136
+ if (s > mx) mx = s;
137
+ }
138
+ // softmax over n
139
+ float sum = 0.f;
140
+ for (int i = 0; i < n; i++) { attn[i] = expf(attn[i] - mx); sum += attn[i]; }
141
+ for (int i = 0; i < n; i++) attn[i] /= sum;
142
+ // ctx = sum attn[i] * src_enc[i]
143
+ memset(ctx, 0, D_ENC * sizeof(float));
144
+ for (int i = 0; i < n; i++)
145
+ for (int j = 0; j < D_ENC; j++)
146
+ ctx[j] += attn[i] * src_enc[(size_t)i * D_ENC + j];
147
+ memcpy(ctx_out, ctx, D_ENC * sizeof(float));
148
+ const int context_in = D_DEC + D_ENC;
149
+ const int output_in = D_DEC + D_EMB;
150
+ const int gate_in = D_DEC + D_ENC + D_EMB;
151
+ float *hc = rowbuf;
152
+ memcpy(hc, h, D_DEC * sizeof(float));
153
+ memcpy(hc + D_DEC, ctx, D_ENC * sizeof(float));
154
+ float *rowbuf2 = hc + gate_in;
155
+ matvec(m, T_WC, D_EMB, context_in, hc, li, rowbuf2);
156
+ memcpy(hc, h, D_DEC * sizeof(float));
157
+ memcpy(hc + D_DEC, prev_emb, D_EMB * sizeof(float));
158
+ matvec(m, T_WO, D_EMB, output_in, hc, o, rowbuf2);
159
+ for (int i = 0; i < D_EMB; i++) li[i] += o[i];
160
+ // logits = head(tanh(li)) ; head tied to emb: logits[v] = emb[v] . tanh(li)
161
+ float *tanh_li = rowbuf2; // 96
162
+ for (int i = 0; i < D_EMB; i++) tanh_li[i] = tanhf(li[i]);
163
+ if (getenv("TTM_DEBUG")) {
164
+ fprintf(stderr, "[dbg] li[:4]=%.6f %.6f %.6f %.6f ctx[:3]=%.6f %.6f %.6f\n",
165
+ li[0], li[1], li[2], li[3], ctx[0], ctx[1], ctx[2]);
166
+ }
167
+ for (int v = 0; v < V_MAX; v++) logits[v] = 0.f;
168
+ matvec(m, T_EMB, (int)m->vocab_count, D_EMB, tanh_li, logits, NULL);
169
+ memcpy(hc, h, D_DEC * sizeof(float));
170
+ memcpy(hc + D_DEC, ctx, D_ENC * sizeof(float));
171
+ memcpy(hc + D_DEC + D_ENC, prev_emb, D_EMB * sizeof(float));
172
+ const float *gate_bias = ttm_vec(m, T_GATE_B, NULL);
173
+ float g = gate_bias[0];
174
+ for (int j = 0; j < gate_in; j++) g += ttm_elem2(m, T_GATE, 0, j) * hc[j];
175
+ *p_gen = 1.f / (1.f + expf(-g));
176
+
177
+ if (getenv("TTM_DEBUG")) {
178
+ // top 5
179
+ int top5[5]; float topv[5];
180
+ for (int k = 0; k < 5; k++) { top5[k] = -1; topv[k] = -1e30f; }
181
+ for (int v = 0; v < (int)m->vocab_count; v++) {
182
+ for (int k = 0; k < 5; k++) {
183
+ if (logits[v] > topv[k]) {
184
+ for (int kk = 4; kk > k; kk--) { top5[kk] = top5[kk-1]; topv[kk] = topv[kk-1]; }
185
+ top5[k] = v; topv[k] = logits[v];
186
+ break;
187
+ }
188
+ }
189
+ }
190
+ fprintf(stderr, "[dbg] p_gen=%f attn0=%f top:", *p_gen, attn[0]);
191
+ for (int k = 0; k < 5; k++) fprintf(stderr, " %d(%.5f)", top5[k], topv[k]);
192
+ fprintf(stderr, "\n");
193
+ }
194
+ if (getenv("TTM_LOGITS")) {
195
+ fprintf(stderr, "[logits]");
196
+ for (int v = 0; v < 64 && v < (int)m->vocab_count; v++) fprintf(stderr, " %.6f", logits[v]);
197
+ fprintf(stderr, "\n");
198
+ }
199
+ }
200
+
201
+ static void vocab_probs(float *probs, const float *logits, float p_gen, float temp, int V) {
202
+ float mx = -1e30f;
203
+ for (int v = 0; v < V; v++) if (logits[v] > mx) mx = logits[v];
204
+ float sum = 0.f;
205
+ for (int v = 0; v < V; v++) {
206
+ probs[v] = expf((logits[v] - mx) / temp);
207
+ sum += probs[v];
208
+ }
209
+ for (int v = 0; v < V; v++) probs[v] = p_gen * probs[v] / sum;
210
+ }
211
+
212
+ // render a token to output text; returns bytes written
213
+ // copied tokens render from the source span (original case), generated from vocab
214
+ static uint32_t utf8_codepoint(const uint8_t *p, int n) {
215
+ if (n == 1) return p[0];
216
+ if (n == 2) return ((uint32_t)(p[0] & 0x1f) << 6) | (p[1] & 0x3f);
217
+ if (n == 3) return ((uint32_t)(p[0] & 0x0f) << 12) |
218
+ ((uint32_t)(p[1] & 0x3f) << 6) | (p[2] & 0x3f);
219
+ return ((uint32_t)(p[0] & 0x07) << 18) |
220
+ ((uint32_t)(p[1] & 0x3f) << 12) |
221
+ ((uint32_t)(p[2] & 0x3f) << 6) | (p[3] & 0x3f);
222
+ }
223
+
224
+ static int lexical_codepoint(uint32_t cp) {
225
+ return (cp >= 'a' && cp <= 'z') || (cp >= 'A' && cp <= 'Z') ||
226
+ (cp >= '0' && cp <= '9') || cp == '_' || (cp >= 0x00c0 && cp <= 0x024f);
227
+ }
228
+
229
+ static int lexical_connector(uint32_t cp) {
230
+ return cp == '\'' || cp == '-' || cp == '.' || cp == 0x2019;
231
+ }
232
+
233
+ static int lexical_spans(const char *text, int *starts, int *ends, int max_words) {
234
+ const uint8_t *s = (const uint8_t *)text;
235
+ int len = (int)strlen(text), n = 0, i = 0;
236
+ while (i < len && n < max_words) {
237
+ int cp_len = ttm_utf8_len(s + i, len - i);
238
+ uint32_t cp = utf8_codepoint(s + i, cp_len);
239
+ int leading_dot = cp == '.';
240
+ if (leading_dot) {
241
+ int next = i + cp_len;
242
+ if (next >= len) { i += cp_len; continue; }
243
+ int next_len = ttm_utf8_len(s + next, len - next);
244
+ if (!lexical_codepoint(utf8_codepoint(s + next, next_len))) {
245
+ i += cp_len;
246
+ continue;
247
+ }
248
+ } else if (!lexical_codepoint(cp)) {
249
+ i += cp_len;
250
+ continue;
251
+ }
252
+ int start = i;
253
+ i += cp_len;
254
+ while (i < len) {
255
+ cp_len = ttm_utf8_len(s + i, len - i);
256
+ cp = utf8_codepoint(s + i, cp_len);
257
+ if (lexical_codepoint(cp)) { i += cp_len; continue; }
258
+ if (cp == '+' || cp == '#') { i += cp_len; continue; }
259
+ if (lexical_connector(cp)) {
260
+ int next = i + cp_len;
261
+ if (next < len) {
262
+ int next_len = ttm_utf8_len(s + next, len - next);
263
+ uint32_t next_cp = utf8_codepoint(s + next, next_len);
264
+ if (lexical_codepoint(next_cp)) { i = next; continue; }
265
+ }
266
+ }
267
+ break;
268
+ }
269
+ starts[n] = start;
270
+ ends[n] = i;
271
+ n++;
272
+ }
273
+ return n;
274
+ }
275
+
276
+ static int valid_input_bytes(const char *text) {
277
+ const uint8_t *p = (const uint8_t *)text;
278
+ int remaining = (int)strlen(text);
279
+ while (remaining > 0) {
280
+ if (*p < 0x20 && !ttm_is_ws(*p)) return 0;
281
+ int n = ttm_utf8_len(p, remaining);
282
+ if (n <= 0) return 0;
283
+ p += n;
284
+ remaining -= n;
285
+ }
286
+ return 1;
287
+ }
288
+
289
+ static int ascii_word_eq(const char *a, const char *b, int n) {
290
+ for (int i = 0; i < n; i++) {
291
+ uint8_t x = (uint8_t)a[i], y = (uint8_t)b[i];
292
+ if (x >= 'A' && x <= 'Z') x += 'a' - 'A';
293
+ if (y >= 'A' && y <= 'Z') y += 'a' - 'A';
294
+ if (x != y) return 0;
295
+ }
296
+ return 1;
297
+ }
298
+
299
+ static void collapse_duplicate_words(char *s) {
300
+ char out[512];
301
+ int oi = 0;
302
+ for (int i = 0; s[i] && oi < (int)sizeof(out) - 1;) {
303
+ while (s[i] == ' ') i++;
304
+ if (!s[i]) break;
305
+ int start = i;
306
+ while (s[i] && s[i] != ' ') i++;
307
+ int len = i - start, duplicate = 0;
308
+ for (int p = 0; p < oi;) {
309
+ while (p < oi && out[p] == ' ') p++;
310
+ int q = p;
311
+ while (q < oi && out[q] != ' ') q++;
312
+ if (q - p == len && ascii_word_eq(out + p, s + start, len)) {
313
+ duplicate = 1;
314
+ break;
315
+ }
316
+ p = q;
317
+ }
318
+ if (duplicate) continue;
319
+ if (oi > 0) out[oi++] = ' ';
320
+ memcpy(out + oi, s + start, (size_t)len);
321
+ oi += len;
322
+ }
323
+ out[oi] = 0;
324
+ memcpy(s, out, (size_t)oi + 1);
325
+ }
326
+
327
+ static int ascii_small_word(const char *s, int len) {
328
+ static const char *small[] = {
329
+ "a", "an", "and", "as", "at", "but", "by", "for", "from", "in",
330
+ "into", "nor", "of", "on", "or", "over", "per", "the", "to", "via",
331
+ "vs", "with"
332
+ };
333
+ for (size_t k = 0; k < sizeof(small) / sizeof(small[0]); k++) {
334
+ int n = (int)strlen(small[k]);
335
+ if (n != len) continue;
336
+ int same = 1;
337
+ for (int i = 0; i < len; i++) {
338
+ uint8_t c = (uint8_t)s[i];
339
+ if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
340
+ if (c != (uint8_t)small[k][i]) { same = 0; break; }
341
+ }
342
+ if (same) return 1;
343
+ }
344
+ return 0;
345
+ }
346
+
347
+ static void title_case_ascii(char *s) {
348
+ int word_index = 0;
349
+ for (int i = 0; s[i];) {
350
+ while (s[i] == ' ') i++;
351
+ if (!s[i]) break;
352
+ int start = i;
353
+ while (s[i] && s[i] != ' ') i++;
354
+ int end = i, internal_upper = 0, first_alpha = -1;
355
+ for (int j = start; j < end; j++) {
356
+ uint8_t c = (uint8_t)s[j];
357
+ if (first_alpha < 0 && ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')))
358
+ first_alpha = j;
359
+ if (j > start && c >= 'A' && c <= 'Z') internal_upper = 1;
360
+ }
361
+ int small = word_index > 0 && ascii_small_word(s + start, end - start);
362
+ if (small || !internal_upper) {
363
+ for (int j = start; j < end; j++)
364
+ if (s[j] >= 'A' && s[j] <= 'Z') s[j] += 'a' - 'A';
365
+ }
366
+ if (!small && !internal_upper && first_alpha >= 0 &&
367
+ s[first_alpha] >= 'a' && s[first_alpha] <= 'z')
368
+ s[first_alpha] -= 'a' - 'A';
369
+ word_index++;
370
+ }
371
+ }
372
+
373
+ static int render_token(const ttm_model *m, uint32_t id, char *out) {
374
+ int len;
375
+ const char *s = ttm_vocab_str(m, id, &len, NULL);
376
+ int written = 0;
377
+ for (int i = 0; i < len;) {
378
+ if (i + 2 < len && (uint8_t)s[i] == 0xe2 &&
379
+ (uint8_t)s[i + 1] == 0x96 && (uint8_t)s[i + 2] == 0x81) {
380
+ out[written++] = ' ';
381
+ i += 3;
382
+ } else {
383
+ out[written++] = s[i++];
384
+ }
385
+ }
386
+ return written;
387
+ }
388
+
389
+ #define TTM_BEAM 2
390
+
391
+ typedef struct {
392
+ float h[D_DEC];
393
+ float prev_emb[D_EMB];
394
+ float cov[TTM_MAX_SRC];
395
+ float score;
396
+ int tokens;
397
+ int done;
398
+ char title[512];
399
+ int title_len;
400
+ } beam_hyp;
401
+
402
+ static int rendered_word_count(const char *s) {
403
+ int count = 0, in_word = 0;
404
+ for (; *s; s++) {
405
+ int ws = *s == ' ' || *s == '\t' || *s == '\n' || *s == '\r';
406
+ if (!ws && !in_word) { count++; in_word = 1; }
407
+ if (ws) in_word = 0;
408
+ }
409
+ return count;
410
+ }
411
+
412
+ static float beam_rank(const beam_hyp *b) {
413
+ if (b->done && rendered_word_count(b->title) < 2) return -1e30f;
414
+ float length_penalty = powf((5.f + (float)(b->tokens > 0 ? b->tokens : 1)) / 6.f, 0.6f);
415
+ return b->score / length_penalty;
416
+ }
417
+
418
+ static void append_generated(beam_hyp *b, const ttm_model *m, uint32_t id) {
419
+ char rendered[256];
420
+ int len = render_token(m, id, rendered);
421
+ int skip = b->title_len == 0 && len > 0 && rendered[0] == ' ';
422
+ if (skip) { len--; memmove(rendered, rendered + 1, (size_t)len); }
423
+ if (len > (int)sizeof(b->title) - 1 - b->title_len)
424
+ len = (int)sizeof(b->title) - 1 - b->title_len;
425
+ memcpy(b->title + b->title_len, rendered, (size_t)len);
426
+ b->title_len += len;
427
+ b->title[b->title_len] = 0;
428
+ }
429
+
430
+ static void append_copied_word(beam_hyp *b, const char *text, int bs, int be) {
431
+ if (b->title_len > 0 && b->title[b->title_len - 1] != ' ' &&
432
+ b->title_len < (int)sizeof(b->title) - 1) b->title[b->title_len++] = ' ';
433
+ int len = be - bs;
434
+ if (len > (int)sizeof(b->title) - 1 - b->title_len)
435
+ len = (int)sizeof(b->title) - 1 - b->title_len;
436
+ memcpy(b->title + b->title_len, text + bs, (size_t)len);
437
+ b->title_len += len;
438
+ b->title[b->title_len] = 0;
439
+ }
440
+
441
+ int main(int argc, char **argv) {
442
+ if (argc < 2) { fprintf(stderr, "usage: %s <model.ttm1> [\"user message\"]\n", argv[0]); return 1; }
443
+ ttm_model m;
444
+ if (ttm_load(argv[1], &m) != 0) return 1;
445
+ int emb_dims = 0; int32_t emb_rows = 0, emb_cols = 0;
446
+ ttm_tensor(&m, T_EMB, &emb_dims, &emb_rows, &emb_cols);
447
+ if (ttm_find_tensor(&m, T_DEC_CTX) < 0 || emb_dims != 2 ||
448
+ emb_rows != (int32_t)m.vocab_count || emb_cols != D_EMB) {
449
+ fprintf(stderr, "incompatible ttm1: expected word-copy dimensions\n");
450
+ ttm_unload(&m);
451
+ return 1;
452
+ }
453
+ if (getenv("TTM_TOUCH")) ttm_touch_all(&m);
454
+
455
+ const char *text = (argc >= 3) ? argv[2] : NULL;
456
+ char buf[TTM_MAX_BYTES + 2];
457
+ if (!text) {
458
+ if (!fgets(buf, sizeof(buf), stdin)) return 1;
459
+ buf[strcspn(buf, "\n")] = 0;
460
+ text = buf;
461
+ }
462
+ if (strlen(text) > TTM_MAX_BYTES) { fprintf(stderr, "input too long\n"); return 1; }
463
+ if (!valid_input_bytes(text)) { fprintf(stderr, "input contains control bytes\n"); return 1; }
464
+
465
+ // tokenize
466
+ uint32_t ids[TTM_MAX_SRC * 4];
467
+ int bstart[TTM_MAX_SRC * 4], bend[TTM_MAX_SRC * 4];
468
+ int n = ttm_tokenize(&m, (const uint8_t *)text, (int)strlen(text), ids, bstart, bend, TTM_MAX_SRC * 4);
469
+ if (n <= 0) { fprintf(stderr, "empty input\n"); ttm_unload(&m); return 1; }
470
+
471
+ if (argc >= 4 && strcmp(argv[3], "--dump-tokens") == 0) {
472
+ for (int i = 0; i < n; i++) printf("%u%c", ids[i], i + 1 < n ? ' ' : '\n');
473
+ ttm_unload(&m);
474
+ return 0;
475
+ }
476
+
477
+ // select (first 192 + salience top-64 of the tail), matching python
478
+ int offs[TTM_MAX_SRC];
479
+ if (n <= TTM_MAX_SRC) {
480
+ for (int i = 0; i < n; i++) offs[i] = i;
481
+ } else {
482
+ int keep_first = 192;
483
+ struct { float s; int i; } scored[TTM_MAX_SRC * 4];
484
+ int nscored = 0;
485
+ for (int i = keep_first; i < n; i++) {
486
+ int l = bend[i] - bstart[i];
487
+ const char *w = text + bstart[i];
488
+ float s = 0.f;
489
+ int has_digit = 0, cap = 0;
490
+ if (l >= 1 && w[0] >= 'A' && w[0] <= 'Z') cap = 1;
491
+ for (int c = 0; c < l; c++) if (w[c] >= '0' && w[c] <= '9') { has_digit = 1; break; }
492
+ if (cap) s += 0.8f;
493
+ if (has_digit) s += 0.5f;
494
+ if (ids[i] == TTM_UNK_ID) s += 1.5f;
495
+ s += 0.3f * (l < 8 ? l : 8);
496
+ s += 0.02f * (i - keep_first);
497
+ scored[nscored].s = s; scored[nscored].i = i;
498
+ nscored++;
499
+ }
500
+ int m64 = nscored < 64 ? nscored : 64;
501
+ for (int a = 0; a < m64; a++) {
502
+ int best = a;
503
+ for (int b = a + 1; b < nscored; b++)
504
+ if (scored[b].s > scored[best].s) best = b;
505
+ float ts = scored[a].s; int ti = scored[a].i;
506
+ scored[a].s = scored[best].s; scored[a].i = scored[best].i;
507
+ scored[best].s = ts; scored[best].i = ti;
508
+ }
509
+ int picked[64];
510
+ for (int a = 0; a < m64; a++) picked[a] = scored[a].i;
511
+ for (int a = 0; a < m64; a++)
512
+ for (int b = a + 1; b < m64; b++)
513
+ if (picked[b] < picked[a]) { int t = picked[a]; picked[a] = picked[b]; picked[b] = t; }
514
+ for (int i = 0; i < keep_first; i++) offs[i] = i;
515
+ for (int a = 0; a < m64; a++) offs[keep_first + a] = picked[a];
516
+ n = keep_first + m64;
517
+ }
518
+ uint32_t sel_ids[TTM_MAX_SRC];
519
+ int sel_bs[TTM_MAX_SRC], sel_be[TTM_MAX_SRC];
520
+ for (int i = 0; i < n; i++) {
521
+ sel_ids[i] = ids[offs[i]];
522
+ sel_bs[i] = bstart[offs[i]];
523
+ sel_be[i] = bend[offs[i]];
524
+ }
525
+
526
+ // Group selected source tokens into lexical words. Punctuation surrounding
527
+ // a word is deliberately outside its copy span.
528
+ int all_word_bs[TTM_MAX_SRC * 4], all_word_be[TTM_MAX_SRC * 4];
529
+ int all_word_count = lexical_spans(text, all_word_bs, all_word_be, TTM_MAX_SRC * 4);
530
+ int represented[TTM_MAX_SRC * 4];
531
+ for (int i = 0; i < all_word_count; i++) represented[i] = -1;
532
+ int token_word[TTM_MAX_SRC], word_bs[TTM_MAX_SRC], word_be[TTM_MAX_SRC];
533
+ for (int i = 0; i < n; i++) token_word[i] = -1;
534
+ int word_count = 0;
535
+ for (int i = 0; i < n; i++) {
536
+ for (int w = 0; w < all_word_count; w++) {
537
+ if (sel_be[i] > all_word_bs[w] && sel_bs[i] < all_word_be[w]) {
538
+ if (represented[w] < 0 && word_count < TTM_MAX_SRC) {
539
+ represented[w] = word_count;
540
+ word_bs[word_count] = all_word_bs[w];
541
+ word_be[word_count] = all_word_be[w];
542
+ word_count++;
543
+ }
544
+ token_word[i] = represented[w];
545
+ break;
546
+ }
547
+ }
548
+ }
549
+
550
+ // workspace (static, bounded)
551
+ size_t work_size = (size_t)n * D_EMB + 3 * D_ENC_HALF + 7 * D_ENC_HALF;
552
+ float *work = malloc(work_size * sizeof(float));
553
+ float *src_enc = malloc((size_t)n * D_ENC * sizeof(float));
554
+ float *attn = malloc((size_t)n * sizeof(float));
555
+ float *cov = calloc((size_t)n, sizeof(float));
556
+ size_t gate_in = D_DEC + D_ENC + D_EMB;
557
+ size_t dsize = (size_t)n * D_ATT + D_ENC + 2 * D_EMB +
558
+ gate_in + 2 * D_ATT + gate_in + D_ENC + 64;
559
+ float *dscratch = malloc(dsize * sizeof(float));
560
+ float *logits = malloc(V_MAX * sizeof(float));
561
+ float *probs = malloc(V_MAX * sizeof(float));
562
+ if (!work || !src_enc || !attn || !cov || !dscratch || !logits || !probs) { perror("alloc"); return 1; }
563
+
564
+ // encode the source
565
+ encode(&m, sel_ids, n, src_enc, work);
566
+
567
+ // decoder init from final forward + final backward encoder summaries.
568
+ float h[D_DEC];
569
+ {
570
+ float summary[D_ENC], row[D_ENC], diw[D_DEC];
571
+ memcpy(summary, src_enc + (size_t)(n - 1) * D_ENC,
572
+ D_ENC_HALF * sizeof(float));
573
+ memcpy(summary + D_ENC_HALF, src_enc + D_ENC_HALF,
574
+ D_ENC_HALF * sizeof(float));
575
+ const float *bias = ttm_vec(&m, T_DEC_INIT_B, NULL);
576
+ matvec(&m, T_DEC_INIT, D_DEC, D_ENC, summary, diw, row);
577
+ for (int i = 0; i < D_DEC; i++) h[i] = tanhf(diw[i] + bias[i]);
578
+ }
579
+
580
+ // bounded beam-2 decode. Each hypothesis owns only fixed recurrent state,
581
+ // coverage, and a small output buffer.
582
+ const int max_tokens = 16, eos_id = 1;
583
+ int beam_width = 1;
584
+ const char *beam_env = getenv("TTM_BEAM_WIDTH");
585
+ if (beam_env && atoi(beam_env) == 2) beam_width = TTM_BEAM;
586
+ beam_hyp beams[TTM_BEAM], candidates[TTM_BEAM * TTM_BEAM];
587
+ memset(beams, 0, sizeof(beams));
588
+ memcpy(beams[0].h, h, sizeof(h));
589
+ ttm_row(&m, T_EMB, 0, beams[0].prev_emb);
590
+
591
+ int beam_count = 1;
592
+
593
+ for (int t = 0; t < max_tokens; t++) {
594
+ int candidate_count = 0;
595
+ for (int b = 0; b < beam_count; b++) {
596
+ if (beams[b].done) {
597
+ candidates[candidate_count++] = beams[b];
598
+ continue;
599
+ }
600
+ float p_gen, step_ctx[D_ENC];
601
+ decode_step(&m, beams[b].h, beams[b].prev_emb, src_enc, n,
602
+ beams[b].cov, attn, logits, &p_gen, step_ctx, dscratch);
603
+ vocab_probs(probs, logits, p_gen, 1.0f, m.vocab_count);
604
+ if (t < 2 || rendered_word_count(beams[b].title) < 2) probs[eos_id] = 0.f;
605
+ probs[0] = 0.f;
606
+ float word_probs[TTM_MAX_SRC] = {0};
607
+ for (int i = 0; i < n; i++)
608
+ if (token_word[i] >= 0) word_probs[token_word[i]] += (1.f - p_gen) * attn[i];
609
+
610
+ int top[TTM_BEAM] = {0, 0};
611
+ float top_p[TTM_BEAM] = {-1.f, -1.f};
612
+ for (int action = 0; action < (int)m.vocab_count + word_count; action++) {
613
+ float probability = action < (int)m.vocab_count ? probs[action] :
614
+ word_probs[action - (int)m.vocab_count];
615
+ for (int k = 0; k < beam_width; k++) {
616
+ if (probability > top_p[k]) {
617
+ for (int q = beam_width - 1; q > k; q--) {
618
+ top_p[q] = top_p[q - 1]; top[q] = top[q - 1];
619
+ }
620
+ top_p[k] = probability; top[k] = action;
621
+ break;
622
+ }
623
+ }
624
+ }
625
+
626
+ for (int k = 0; k < beam_width; k++) {
627
+ if (top_p[k] <= 0.f || candidate_count >= TTM_BEAM * TTM_BEAM) continue;
628
+ beam_hyp child = beams[b];
629
+ child.score += logf(top_p[k] + 1e-30f);
630
+ child.tokens++;
631
+ int action = top[k];
632
+ if (action == eos_id) {
633
+ child.done = 1;
634
+ candidates[candidate_count++] = child;
635
+ continue;
636
+ }
637
+
638
+ if (action < (int)m.vocab_count) {
639
+ append_generated(&child, &m, (uint32_t)action);
640
+ ttm_row(&m, T_EMB, action, child.prev_emb);
641
+ } else {
642
+ int group = action - (int)m.vocab_count;
643
+ append_copied_word(&child, text, word_bs[group], word_be[group]);
644
+ memset(child.prev_emb, 0, sizeof(child.prev_emb));
645
+ int group_tokens = 0;
646
+ float token_emb[D_EMB];
647
+ for (int i = 0; i < n; i++) if (token_word[i] == group) {
648
+ ttm_row(&m, T_EMB, (int)sel_ids[i], token_emb);
649
+ for (int j = 0; j < D_EMB; j++) child.prev_emb[j] += token_emb[j];
650
+ group_tokens++;
651
+ }
652
+ if (group_tokens > 0)
653
+ for (int j = 0; j < D_EMB; j++) child.prev_emb[j] /= (float)group_tokens;
654
+ }
655
+ float ctx_proj[D_EMB], dec_input[D_EMB], rowbuf[D_ENC];
656
+ matvec(&m, T_DEC_CTX, D_EMB, D_ENC, step_ctx, ctx_proj, rowbuf);
657
+ for (int i = 0; i < D_EMB; i++)
658
+ dec_input[i] = child.prev_emb[i] + tanhf(ctx_proj[i]);
659
+ float h_new[D_DEC], gscratch[7 * D_DEC];
660
+ gru_cell(&m, T_DEC_W, T_DEC_U, T_DEC_B, D_EMB, D_DEC,
661
+ dec_input, beams[b].h, h_new, gscratch);
662
+ memcpy(child.h, h_new, sizeof(h_new));
663
+ for (int i = 0; i < n; i++) child.cov[i] = beams[b].cov[i] + attn[i];
664
+ candidates[candidate_count++] = child;
665
+ }
666
+ }
667
+ if (candidate_count == 0) break;
668
+ for (int i = 0; i < candidate_count; i++) {
669
+ int best = i;
670
+ for (int j = i + 1; j < candidate_count; j++)
671
+ if (beam_rank(&candidates[j]) > beam_rank(&candidates[best])) best = j;
672
+ if (best != i) { beam_hyp tmp = candidates[i]; candidates[i] = candidates[best]; candidates[best] = tmp; }
673
+ }
674
+ beam_count = candidate_count < beam_width ? candidate_count : beam_width;
675
+ for (int i = 0; i < beam_count; i++) beams[i] = candidates[i];
676
+ int all_done = 1;
677
+ for (int i = 0; i < beam_count; i++) if (!beams[i].done) all_done = 0;
678
+ if (all_done) break;
679
+ }
680
+ int winner = 0;
681
+ for (int i = 1; i < beam_count; i++)
682
+ if (beam_rank(&beams[i]) > beam_rank(&beams[winner])) winner = i;
683
+ beams[winner].title[beams[winner].title_len] = 0;
684
+ collapse_duplicate_words(beams[winner].title);
685
+ title_case_ascii(beams[winner].title);
686
+ printf("%s\n", beams[winner].title);
687
+ if (getenv("TTM_RSS")) {
688
+ // read VmHWM from /proc/self/status (the plan's protocol; ru_maxrss is
689
+ // unreliable on this system)
690
+ FILE *pf = fopen("/proc/self/status", "r");
691
+ char line[256];
692
+ while (pf && fgets(line, sizeof(line), pf)) {
693
+ if (strncmp(line, "VmHWM:", 6) == 0) {
694
+ fprintf(stderr, "[rss] peak=%s kb\n", line + 6);
695
+ break;
696
+ }
697
+ }
698
+ if (pf) fclose(pf);
699
+ }
700
+
701
+ free(work); free(src_enc); free(attn); free(cov); free(dscratch); free(logits); free(probs);
702
+ ttm_unload(&m);
703
+ return 0;
704
+ }
runtime/ttm.h ADDED
@@ -0,0 +1,418 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ttm.h — .ttm1 loader + tokenizer + forward for the TinyTitle pointer-generator
2
+ // Single-header, bounded, no dynamic allocation after init.
3
+ #ifndef TTM_H
4
+ #define TTM_H
5
+
6
+ #include <stdint.h>
7
+ #include <stddef.h>
8
+ #include <stdio.h>
9
+ #include <stdlib.h>
10
+ #include <string.h>
11
+ #include <sys/mman.h>
12
+ #include <sys/stat.h>
13
+ #include <fcntl.h>
14
+ #include <unistd.h>
15
+
16
+ #define TTM_MAGIC "TTM1"
17
+ #define TTM_TAG_VOCAB 1
18
+ #define TTM_TAG_WEIGHTS 2
19
+
20
+ #define TTM_MAX_VOCAB 8000
21
+ #define TTM_MAX_SRC 256
22
+ #define TTM_MAX_TGT 16
23
+ #define TTM_MAX_BYTES 6000
24
+ #define TTM_MAX_PIECES 2048
25
+ #define TTM_MAX_TENSORS 64
26
+ #define TTM_MAX_NAME 64
27
+ #define TTM_D_EMB 128
28
+ #define TTM_D_ENC 128
29
+ #define TTM_D_DEC 256
30
+ #define TTM_D_ATT 160
31
+ #define TTM_V 8000
32
+
33
+ // vocabulary entry layout (export.py): u8 blen, bytes, u16 zero, f32 score
34
+ typedef struct {
35
+ uint32_t offset;
36
+ uint32_t size;
37
+ uint8_t tag;
38
+ } ttm_section;
39
+
40
+ typedef struct {
41
+ // file mapping
42
+ const uint8_t *data;
43
+ size_t file_size;
44
+ ttm_section sections[TTM_MAX_TENSORS];
45
+ int n_sections;
46
+ // vocab
47
+ const uint8_t *vocab; // points at count field
48
+ uint32_t vocab_count;
49
+ // tensors (name -> offset)
50
+ struct {
51
+ char name[TTM_MAX_NAME];
52
+ int dims;
53
+ int32_t d0, d1;
54
+ uint64_t row_bytes; // 0 = raw f32 rows (2-D) or 1-D
55
+ const uint8_t *data;
56
+ } tensors[TTM_MAX_TENSORS];
57
+ int n_tensors;
58
+ } ttm_model;
59
+
60
+ // ---- safe unaligned reads (the file is packed; fields may not be aligned) ----
61
+ static inline uint32_t rd_u32(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v; }
62
+ static inline uint64_t rd_u64(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v; }
63
+ static inline int32_t rd_i32(const uint8_t *p) { int32_t v; memcpy(&v, p, 4); return v; }
64
+
65
+ // ---- loader ----
66
+ static int ttm_load(const char *path, ttm_model *m) {
67
+ memset(m, 0, sizeof(*m));
68
+ int fd = open(path, O_RDONLY);
69
+ if (fd < 0) { perror("open"); return -1; }
70
+ struct stat st;
71
+ if (fstat(fd, &st) != 0) { perror("fstat"); return -1; }
72
+ m->file_size = (size_t)st.st_size;
73
+ m->data = (const uint8_t *)mmap(NULL, m->file_size, PROT_READ, MAP_PRIVATE, fd, 0);
74
+ close(fd);
75
+ if (m->data == MAP_FAILED) { perror("mmap"); return -1; }
76
+
77
+ const uint8_t *p = m->data;
78
+ if (m->file_size < 4 + 4 + 4 + 8) { fprintf(stderr, "ttm1: file too small\n"); return -1; }
79
+ if (memcmp(p, TTM_MAGIC, 4) != 0) { fprintf(stderr, "ttm1: bad magic\n"); return -1; }
80
+ uint32_t n_vocab = rd_u32(p + 4);
81
+ uint32_t n_sections = rd_u32(p + 8);
82
+ uint64_t table_off = rd_u64(p + 12);
83
+ if (n_vocab > TTM_MAX_VOCAB || n_sections > TTM_MAX_TENSORS) {
84
+ fprintf(stderr, "ttm1: limits exceeded\n");
85
+ return -1;
86
+ }
87
+ if (table_off + (uint64_t)n_sections * 24 > m->file_size) {
88
+ fprintf(stderr, "ttm1: bad section table\n");
89
+ return -1;
90
+ }
91
+ const uint8_t *tbl = m->data + table_off;
92
+ m->n_sections = (int)n_sections;
93
+ for (uint32_t i = 0; i < n_sections; i++) {
94
+ uint32_t tag = rd_u32(tbl + i * 24);
95
+ uint64_t off = rd_u64(tbl + i * 24 + 8);
96
+ uint64_t sz = rd_u64(tbl + i * 24 + 16);
97
+ if (off + sz > m->file_size) { fprintf(stderr, "ttm1: section out of range\n"); return -1; }
98
+ m->sections[i].tag = (uint8_t)tag;
99
+ m->sections[i].offset = (uint32_t)off;
100
+ m->sections[i].size = (uint32_t)sz;
101
+ }
102
+ // vocab section
103
+ for (int i = 0; i < m->n_sections; i++) {
104
+ if (m->sections[i].tag == TTM_TAG_VOCAB) {
105
+ m->vocab = m->data + m->sections[i].offset;
106
+ m->vocab_count = rd_u32(m->vocab);
107
+ if (m->vocab_count > TTM_MAX_VOCAB) { fprintf(stderr, "ttm1: vocab too big\n"); return -1; }
108
+ }
109
+ }
110
+ // weights section: parse the tensor list
111
+ for (int i = 0; i < m->n_sections; i++) {
112
+ if (m->sections[i].tag != TTM_TAG_WEIGHTS) continue;
113
+ const uint8_t *w = m->data + m->sections[i].offset;
114
+ const uint8_t *wend = w + m->sections[i].size;
115
+ uint32_t count = rd_u32(w);
116
+ if (count > TTM_MAX_TENSORS) { fprintf(stderr, "ttm1: too many tensors\n"); return -1; }
117
+ w += 4;
118
+ for (uint32_t k = 0; k < count && w + 4 <= wend; k++) {
119
+ uint32_t nl = rd_u32(w); w += 4;
120
+ if (nl >= TTM_MAX_NAME || w + nl > wend) return -1;
121
+ if (m->n_tensors >= TTM_MAX_TENSORS) return -1;
122
+ ttm_model *mm = m;
123
+ memcpy(mm->tensors[m->n_tensors].name, w, nl);
124
+ mm->tensors[m->n_tensors].name[nl] = 0;
125
+ w += nl;
126
+ // names are padded to 4-byte alignment
127
+ w += (4 - ((w - m->data) & 3)) & 3;
128
+ uint32_t dims = rd_u32(w); w += 4;
129
+ int32_t a = rd_i32(w); w += 4;
130
+ int32_t b = rd_i32(w); w += 4;
131
+ mm->tensors[m->n_tensors].dims = (int)dims;
132
+ mm->tensors[m->n_tensors].d0 = a;
133
+ mm->tensors[m->n_tensors].d1 = b;
134
+ mm->tensors[m->n_tensors].row_bytes = 0;
135
+ m->n_tensors++;
136
+ if (getenv("TTM_DEBUG")) {
137
+ fprintf(stderr, "[load] %s dims=%u %dx%d file_off=%ld\n",
138
+ mm->tensors[m->n_tensors - 1].name, dims, a, b,
139
+ (long)(w - m->data));
140
+ }
141
+ if (dims == 2) {
142
+ uint64_t rows = rd_u64(w); w += 8;
143
+ uint64_t row_bytes = rd_u64(w); w += 8;
144
+ if (rows > 1u << 20 || row_bytes > 1u << 20) return -1;
145
+ mm->tensors[m->n_tensors - 1].row_bytes = row_bytes;
146
+ if (row_bytes == 0) {
147
+ // raw f32 rows: a*b floats
148
+ if ((size_t)a * b > (size_t)(wend - w) / 4) return -1;
149
+ mm->tensors[m->n_tensors - 1].data = w;
150
+ w += (size_t)a * b * 4;
151
+ } else {
152
+ if (w + 4 * rows + row_bytes > wend) return -1;
153
+ mm->tensors[m->n_tensors - 1].data = w;
154
+ w += 4 * rows + row_bytes;
155
+ }
156
+ } else {
157
+ if (a < 0 || (size_t)a * 4 > (size_t)(wend - w)) return -1;
158
+ mm->tensors[m->n_tensors - 1].data = w;
159
+ w += (size_t)a * 4;
160
+ }
161
+ }
162
+ }
163
+ return 0;
164
+ }
165
+
166
+ static void ttm_unload(ttm_model *m) {
167
+ if (m->data && m->data != MAP_FAILED) munmap((void *)m->data, m->file_size);
168
+ memset(m, 0, sizeof(*m));
169
+ }
170
+
171
+ // touch every page of the mapping (the plan requires all-pages-touched RSS)
172
+ static void ttm_touch_all(const ttm_model *m) {
173
+ volatile uint8_t acc = 0;
174
+ for (size_t off = 0; off < m->file_size; off += 4096) {
175
+ acc ^= m->data[off];
176
+ }
177
+ (void)acc;
178
+ }
179
+
180
+ // find tensor by name
181
+ static int ttm_find_tensor(const ttm_model *m, const char *name) {
182
+ for (int i = 0; i < m->n_tensors; i++)
183
+ if (strcmp(m->tensors[i].name, name) == 0) return i;
184
+ return -1;
185
+ }
186
+
187
+ static const uint8_t *ttm_tensor(const ttm_model *m, const char *name, int *dims,
188
+ int32_t *d0, int32_t *d1) {
189
+ int i = ttm_find_tensor(m, name);
190
+ if (i >= 0) {
191
+ if (dims) *dims = m->tensors[i].dims;
192
+ if (d0) *d0 = m->tensors[i].d0;
193
+ if (d1) *d1 = m->tensors[i].d1;
194
+ return m->tensors[i].data;
195
+ }
196
+ fprintf(stderr, "ttm1: tensor %s not found\n", name);
197
+ return NULL;
198
+ }
199
+
200
+ // dequantize row r of a 2-D tensor into out[cols] (f32)
201
+ // int8 layout: [f32 scale x d0] [int8 rows x row_bytes]; f32 layout: [f32 d0*d1]
202
+ static void ttm_row(const ttm_model *m, const char *name, int r, float *out) {
203
+ int dims; int32_t d0, d1;
204
+ const uint8_t *t = ttm_tensor(m, name, &dims, &d0, &d1);
205
+ if (!t || dims != 2) { fprintf(stderr, "ttm1: bad tensor %s\n", name); return; }
206
+ if (r < 0 || r >= d0) { fprintf(stderr, "ttm1: row %d out of range for %s (%dx%d)\n", r, name, d0, d1); return; }
207
+ uint64_t row_bytes = 0;
208
+ for (int i = 0; i < m->n_tensors; i++)
209
+ if (strcmp(m->tensors[i].name, name) == 0) { row_bytes = m->tensors[i].row_bytes; break; }
210
+ if (row_bytes == 0) {
211
+ memcpy(out, t + (size_t)r * d1 * 4, (size_t)d1 * 4);
212
+ return;
213
+ }
214
+ const float *scales = (const float *)t;
215
+ const int8_t *q = (const int8_t *)(t + 4 * (size_t)d0);
216
+ float s = scales[r];
217
+ size_t row_stride = row_bytes / (size_t)d0; // stored row_bytes = d0*d1 total
218
+ const int8_t *qr = q + (size_t)r * row_stride;
219
+ for (int i = 0; i < d1; i++) out[i] = s * (float)qr[i];
220
+ }
221
+
222
+ // direct matrix-vector multiply without materializing dequantized rows.
223
+ static int ttm_matvec(const ttm_model *m, const char *name, int rows, int cols,
224
+ const float *x, float *y) {
225
+ int ti = ttm_find_tensor(m, name);
226
+ if (ti < 0) return -1;
227
+ const int d0 = m->tensors[ti].d0, d1 = m->tensors[ti].d1;
228
+ const uint8_t *t = m->tensors[ti].data;
229
+ if (m->tensors[ti].dims != 2 || d0 != rows || d1 != cols) return -1;
230
+ uint64_t row_bytes = m->tensors[ti].row_bytes;
231
+ if (row_bytes == 0) {
232
+ const float *w = (const float *)t;
233
+ for (int r = 0; r < rows; r++) {
234
+ float acc = 0.f;
235
+ for (int c = 0; c < cols; c++) acc += w[(size_t)r * cols + c] * x[c];
236
+ y[r] = acc;
237
+ }
238
+ return 0;
239
+ }
240
+ const float *scales = (const float *)t;
241
+ const int8_t *q = (const int8_t *)(t + 4 * (size_t)d0);
242
+ size_t stride = row_bytes / (size_t)d0;
243
+ for (int r = 0; r < rows; r++) {
244
+ const int8_t *qr = q + (size_t)r * stride;
245
+ float acc = 0.f;
246
+ for (int c = 0; c < cols; c++) acc += (float)qr[c] * x[c];
247
+ y[r] = acc * scales[r];
248
+ }
249
+ return 0;
250
+ }
251
+
252
+ // get a scalar of a 2-D tensor
253
+ static float ttm_elem2(const ttm_model *m, const char *name, int r, int c) {
254
+ int dims; int32_t d0, d1;
255
+ const uint8_t *t = ttm_tensor(m, name, &dims, &d0, &d1);
256
+ if (!t || dims != 2 || r >= d0 || c >= d1) return 0.f;
257
+ uint64_t row_bytes = 0;
258
+ for (int i = 0; i < m->n_tensors; i++)
259
+ if (strcmp(m->tensors[i].name, name) == 0) { row_bytes = m->tensors[i].row_bytes; break; }
260
+ if (row_bytes == 0) {
261
+ return ((const float *)t)[(size_t)r * d1 + c];
262
+ }
263
+ const float *scales = (const float *)t;
264
+ const int8_t *q = (const int8_t *)(t + 4 * (size_t)d0);
265
+ size_t row_stride = row_bytes / (size_t)d0;
266
+ return scales[r] * (float)q[(size_t)r * row_stride + c];
267
+ }
268
+
269
+ // 1-D f32 tensor
270
+ static const float *ttm_vec(const ttm_model *m, const char *name, int *len) {
271
+ int dims; int32_t d0, d1;
272
+ const uint8_t *t = ttm_tensor(m, name, &dims, &d0, &d1);
273
+ if (!t || dims != 1) { fprintf(stderr, "ttm1: bad vec %s\n", name); return NULL; }
274
+ if (len) *len = d0;
275
+ return (const float *)t;
276
+ }
277
+
278
+ // ---- vocab ----
279
+ // entry: u8 blen, bytes, u16 zero, f32 score
280
+ static const uint8_t *ttm_vocab_entry(const ttm_model *m, uint32_t id) {
281
+ if (id >= m->vocab_count) return NULL;
282
+ const uint8_t *p = m->vocab + 4;
283
+ for (uint32_t i = 0; i < id; i++) {
284
+ uint8_t bl = *p;
285
+ p += 1 + bl + 2 + 4;
286
+ }
287
+ return p;
288
+ }
289
+
290
+ static const char *ttm_vocab_str(const ttm_model *m, uint32_t id, int *len, float *score) {
291
+ const uint8_t *p = ttm_vocab_entry(m, id);
292
+ if (!p) { *len = 0; if (score) *score = -20.f; return ""; }
293
+ uint8_t bl = *p;
294
+ if (score) { memcpy(score, p + 1 + bl + 2, 4); }
295
+ *len = bl;
296
+ return (const char *)(p + 1);
297
+ }
298
+
299
+
300
+ // ---- tokenizer: unigram viterbi over metaspace-prefixed whitespace pieces ----
301
+ #define TTM_UNK_ID 2
302
+
303
+ static int ttm_is_ws(uint8_t c) {
304
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f';
305
+ }
306
+
307
+ static int ttm_utf8_len(const uint8_t *p, int remaining) {
308
+ if (remaining <= 0) return 0;
309
+ if (p[0] < 0x80) return 1;
310
+ int n = (p[0] >= 0xc2 && p[0] <= 0xdf) ? 2 :
311
+ (p[0] >= 0xe0 && p[0] <= 0xef) ? 3 :
312
+ (p[0] >= 0xf0 && p[0] <= 0xf4) ? 4 : 0;
313
+ if (!n || n > remaining) return 0;
314
+ for (int i = 1; i < n; i++) if ((p[i] & 0xc0) != 0x80) return 0;
315
+ if (n == 3 && p[0] == 0xe0 && p[1] < 0xa0) return 0;
316
+ if (n == 3 && p[0] == 0xed && p[1] >= 0xa0) return 0;
317
+ if (n == 4 && p[0] == 0xf0 && p[1] < 0x90) return 0;
318
+ if (n == 4 && p[0] == 0xf4 && p[1] >= 0x90) return 0;
319
+ return n;
320
+ }
321
+
322
+ // viterbi over one byte buffer; returns token count, fills ids + byte spans
323
+ static int ttm_viterbi(const ttm_model *m, const uint8_t *buf, int blen,
324
+ uint32_t *ids, int *byte_start, int *byte_end, int max_tokens) {
325
+ if (blen <= 0) return 0;
326
+ static int32_t dp[6004];
327
+ static int32_t prev[6004];
328
+ static uint16_t tok[6004];
329
+ const int NEG = INT32_MIN / 4;
330
+ const int Q = 1 << 12;
331
+ dp[0] = 0; prev[0] = -1;
332
+ for (int i = 1; i <= blen; i++) dp[i] = NEG;
333
+ for (int i = 0; i < blen; i++) {
334
+ if (dp[i] == NEG) continue;
335
+ const uint8_t *p = m->vocab + 4;
336
+ for (uint32_t v = 0; v < m->vocab_count; v++) {
337
+ uint8_t l = *p;
338
+ const uint8_t *s = p + 1;
339
+ float score;
340
+ memcpy(&score, p + 1 + l + 2, 4);
341
+ if (i + l <= blen && memcmp(s, buf + i, l) == 0) {
342
+ int32_t sc = dp[i] + (int32_t)(score * Q);
343
+ if (sc > dp[i + l]) {
344
+ dp[i + l] = sc;
345
+ prev[i + l] = i;
346
+ tok[i + l] = (uint16_t)v;
347
+ }
348
+ }
349
+ p += 1 + l + 2 + 4;
350
+ }
351
+ // unknown fallback consumes one complete utf-8 code point, matching
352
+ // tokenizers and ensuring copied unknowns remain valid utf-8.
353
+ {
354
+ int cp = ttm_utf8_len(buf + i, blen - i);
355
+ if (cp <= 0) cp = 1;
356
+ int32_t sc = dp[i] + (int32_t)(-20.0f * Q);
357
+ if (sc > dp[i + cp]) {
358
+ dp[i + cp] = sc;
359
+ prev[i + cp] = i;
360
+ tok[i + cp] = TTM_UNK_ID;
361
+ }
362
+ }
363
+ }
364
+ if (dp[blen] == NEG) return 0;
365
+ int pos = blen, n = 0;
366
+ while (pos > 0 && n < max_tokens) {
367
+ int p0 = prev[pos];
368
+ ids[n] = tok[pos];
369
+ byte_start[n] = p0;
370
+ byte_end[n] = pos;
371
+ n++;
372
+ pos = p0;
373
+ }
374
+ for (int i = 0; i < n / 2; i++) {
375
+ uint32_t t = ids[i]; ids[i] = ids[n - 1 - i]; ids[n - 1 - i] = t;
376
+ int s = byte_start[i]; byte_start[i] = byte_start[n - 1 - i]; byte_start[n - 1 - i] = s;
377
+ int e = byte_end[i]; byte_end[i] = byte_end[n - 1 - i]; byte_end[n - 1 - i] = e;
378
+ }
379
+ return n;
380
+ }
381
+
382
+ // tokenizers' metaspace pre-tokenizer replaces each whitespace boundary with ▁
383
+ // and prepends one at the start. process each word independently to avoid a
384
+ // second input-sized workspace while preserving identical unigram pieces.
385
+ static int ttm_tokenize(const ttm_model *m, const uint8_t *buf, int blen,
386
+ uint32_t *ids, int *byte_start, int *byte_end, int max_tokens) {
387
+ int n = 0, i = 0;
388
+ while (i < blen && n < max_tokens) {
389
+ while (i < blen && ttm_is_ws(buf[i])) i++;
390
+ if (i >= blen) break;
391
+ int j = i;
392
+ while (j < blen && !ttm_is_ws(buf[j])) j++;
393
+ int word_len = j - i;
394
+ uint8_t piece[TTM_MAX_BYTES + 3];
395
+ piece[0] = 0xe2; piece[1] = 0x96; piece[2] = 0x81; // utf-8 ▁
396
+ memcpy(piece + 3, buf + i, (size_t)word_len);
397
+ uint32_t pids[TTM_MAX_SRC * 4];
398
+ int pst[TTM_MAX_SRC * 4], pen[TTM_MAX_SRC * 4];
399
+ int cap = max_tokens - n;
400
+ if (cap > TTM_MAX_SRC * 4) cap = TTM_MAX_SRC * 4;
401
+ int k = ttm_viterbi(m, piece, word_len + 3, pids, pst, pen, cap);
402
+ for (int t = 0; t < k && n < max_tokens; t++) {
403
+ ids[n] = pids[t];
404
+ int s = pst[t] - 3, e = pen[t] - 3;
405
+ if (s < 0) s = 0;
406
+ if (e < 0) e = 0;
407
+ if (s > word_len) s = word_len;
408
+ if (e > word_len) e = word_len;
409
+ byte_start[n] = i + s;
410
+ byte_end[n] = i + e;
411
+ n++;
412
+ }
413
+ i = j;
414
+ }
415
+ return n;
416
+ }
417
+
418
+ #endif