Uploaded runtime
Browse files- runtime/Makefile +20 -0
- runtime/main.c +704 -0
- 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
|