| |
| |
| #include "ttm.h" |
| #include <math.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <sys/resource.h> |
|
|
| |
| #define T_EMB "emb.weight" |
| #define T_ENC_F_W "enc.fwd.w.weight" |
| #define T_ENC_F_U "enc.fwd.uh.weight" |
| #define T_ENC_F_B "enc.fwd.b" |
| #define T_ENC_R_W "enc.bwd.w.weight" |
| #define T_ENC_R_U "enc.bwd.uh.weight" |
| #define T_ENC_R_B "enc.bwd.b" |
| #define T_DEC_W "dec.w.weight" |
| #define T_DEC_CTX "dec_ctx.weight" |
| #define T_DEC_U "dec.uh.weight" |
| #define T_DEC_B "dec.b" |
| #define T_WATT_V "w_att_v.weight" |
| #define T_WATT_H "w_att_h.weight" |
| #define T_V_ATT "v_att.weight" |
| #define T_COV "cov.weight" |
| #define T_WC "w_c.weight" |
| #define T_WO "w_o.weight" |
| #define T_GATE "gate.weight" |
| #define T_GATE_B "gate.bias" |
| #define T_DEC_INIT "dec_init.weight" |
| #define T_DEC_INIT_B "dec_init.bias" |
|
|
| #define D_EMB TTM_D_EMB |
| #define D_ENC_HALF TTM_D_ENC |
| #define D_ENC (2 * TTM_D_ENC) |
| #define D_DEC TTM_D_DEC |
| #define D_ATT TTM_D_ATT |
| #define V_MAX TTM_MAX_VOCAB |
|
|
|
|
| |
| static void matvec(const ttm_model *m, const char *name, int rows, int cols, |
| const float *x, float *y, float *rowbuf) { |
| (void)rowbuf; |
| if (ttm_matvec(m, name, rows, cols, x, y) != 0) { |
| fprintf(stderr, "ttm1: invalid matvec tensor %s\n", name); |
| memset(y, 0, (size_t)rows * sizeof(float)); |
| } |
| } |
|
|
| |
| |
| static void gru_cell(const ttm_model *m, const char *w_name, const char *u_name, |
| const char *b_name, int in, int hid, const float *x, const float *h, |
| float *h_out, float *scratch) { |
| float *gx = scratch; |
| float *gh = scratch + 3 * hid; |
| float *rowbuf = scratch + 6 * hid; |
| matvec(m, w_name, 3 * hid, in, x, gx, rowbuf); |
| matvec(m, u_name, 3 * hid, hid, h, gh, rowbuf); |
| const float *bias = ttm_vec(m, b_name, NULL); |
| for (int i = 0; i < 3 * hid; i++) gx[i] += bias[i]; |
| for (int i = 0; i < hid; i++) { |
| float rr = 1.f / (1.f + expf(-(gx[i] + gh[i]))); |
| float zz = 1.f / (1.f + expf(-(gx[hid + i] + gh[hid + i]))); |
| float nn = tanhf(gx[2 * hid + i] + rr * gh[2 * hid + i]); |
| h_out[i] = (1.f - zz) * nn + zz * h[i]; |
| } |
| } |
|
|
| |
| static void encode(const ttm_model *m, const uint32_t *ids, int n, |
| float *src_enc, float *work) { |
| float *emb = work; |
| float *hf = work + (size_t)n * D_EMB; |
| float *hb = hf + D_ENC_HALF; |
| float *tmp = hb + D_ENC_HALF; |
| float *gscratch = tmp + D_ENC_HALF; |
| for (int i = 0; i < n; i++) |
| ttm_row(m, T_EMB, (int)ids[i], emb + (size_t)i * D_EMB); |
| memset(hf, 0, D_ENC_HALF * sizeof(float)); |
| memset(hb, 0, D_ENC_HALF * sizeof(float)); |
| for (int i = 0; i < n; i++) { |
| gru_cell(m, T_ENC_F_W, T_ENC_F_U, T_ENC_F_B, |
| D_EMB, D_ENC_HALF, emb + (size_t)i * D_EMB, hf, tmp, gscratch); |
| memcpy(hf, tmp, D_ENC_HALF * sizeof(float)); |
| memcpy(src_enc + (size_t)i * D_ENC, hf, D_ENC_HALF * sizeof(float)); |
| } |
| for (int i = n - 1; i >= 0; i--) { |
| gru_cell(m, T_ENC_R_W, T_ENC_R_U, T_ENC_R_B, |
| D_EMB, D_ENC_HALF, emb + (size_t)i * D_EMB, hb, tmp, gscratch); |
| memcpy(hb, tmp, D_ENC_HALF * sizeof(float)); |
| memcpy(src_enc + (size_t)i * D_ENC + D_ENC_HALF, |
| hb, D_ENC_HALF * sizeof(float)); |
| } |
| } |
|
|
| |
| |
| |
| |
| static void decode_step(const ttm_model *m, const float *h, const float *prev_emb, |
| const float *src_enc, int n, |
| const float *cov, float *attn, float *logits, float *p_gen, |
| float *ctx_out, float *scratch) { |
| float *e = scratch; |
| float *ctx = scratch + (size_t)n * D_ATT; |
| float *o = ctx + D_ENC; |
| float *li = o + D_EMB; |
| float *rowbuf = li + D_EMB; |
|
|
| float *wh = rowbuf; |
| matvec(m, T_WATT_H, D_ATT, D_DEC, h, wh, rowbuf + D_ATT); |
| float *vr = rowbuf + 2 * D_ATT; |
| |
| |
| for (int i = 0; i < n; i++) { |
| matvec(m, T_WATT_V, D_ATT, D_ENC, src_enc + (size_t)i * D_ENC, e + (size_t)i * D_ATT, vr); |
| for (int j = 0; j < D_ATT; j++) e[(size_t)i * D_ATT + j] += wh[j]; |
| |
| |
| for (int j = 0; j < D_ATT; j++) { |
| float cw = ttm_elem2(m, T_COV, j, 0); |
| e[(size_t)i * D_ATT + j] += cw * cov[i]; |
| } |
| } |
| |
| float mx = -1e30f; |
| for (int i = 0; i < n; i++) { |
| float s = 0.f; |
| float *ei = e + (size_t)i * D_ATT; |
| for (int j = 0; j < D_ATT; j++) { |
| float vw = ttm_elem2(m, T_V_ATT, 0, j); |
| s += vw * tanhf(ei[j]); |
| } |
| attn[i] = s; |
| if (s > mx) mx = s; |
| } |
| |
| float sum = 0.f; |
| for (int i = 0; i < n; i++) { attn[i] = expf(attn[i] - mx); sum += attn[i]; } |
| for (int i = 0; i < n; i++) attn[i] /= sum; |
| |
| memset(ctx, 0, D_ENC * sizeof(float)); |
| for (int i = 0; i < n; i++) |
| for (int j = 0; j < D_ENC; j++) |
| ctx[j] += attn[i] * src_enc[(size_t)i * D_ENC + j]; |
| memcpy(ctx_out, ctx, D_ENC * sizeof(float)); |
| const int context_in = D_DEC + D_ENC; |
| const int output_in = D_DEC + D_EMB; |
| const int gate_in = D_DEC + D_ENC + D_EMB; |
| float *hc = rowbuf; |
| memcpy(hc, h, D_DEC * sizeof(float)); |
| memcpy(hc + D_DEC, ctx, D_ENC * sizeof(float)); |
| float *rowbuf2 = hc + gate_in; |
| matvec(m, T_WC, D_EMB, context_in, hc, li, rowbuf2); |
| memcpy(hc, h, D_DEC * sizeof(float)); |
| memcpy(hc + D_DEC, prev_emb, D_EMB * sizeof(float)); |
| matvec(m, T_WO, D_EMB, output_in, hc, o, rowbuf2); |
| for (int i = 0; i < D_EMB; i++) li[i] += o[i]; |
| |
| float *tanh_li = rowbuf2; |
| for (int i = 0; i < D_EMB; i++) tanh_li[i] = tanhf(li[i]); |
| if (getenv("TTM_DEBUG")) { |
| fprintf(stderr, "[dbg] li[:4]=%.6f %.6f %.6f %.6f ctx[:3]=%.6f %.6f %.6f\n", |
| li[0], li[1], li[2], li[3], ctx[0], ctx[1], ctx[2]); |
| } |
| for (int v = 0; v < V_MAX; v++) logits[v] = 0.f; |
| matvec(m, T_EMB, (int)m->vocab_count, D_EMB, tanh_li, logits, NULL); |
| memcpy(hc, h, D_DEC * sizeof(float)); |
| memcpy(hc + D_DEC, ctx, D_ENC * sizeof(float)); |
| memcpy(hc + D_DEC + D_ENC, prev_emb, D_EMB * sizeof(float)); |
| const float *gate_bias = ttm_vec(m, T_GATE_B, NULL); |
| float g = gate_bias[0]; |
| for (int j = 0; j < gate_in; j++) g += ttm_elem2(m, T_GATE, 0, j) * hc[j]; |
| *p_gen = 1.f / (1.f + expf(-g)); |
|
|
| if (getenv("TTM_DEBUG")) { |
| |
| int top5[5]; float topv[5]; |
| for (int k = 0; k < 5; k++) { top5[k] = -1; topv[k] = -1e30f; } |
| for (int v = 0; v < (int)m->vocab_count; v++) { |
| for (int k = 0; k < 5; k++) { |
| if (logits[v] > topv[k]) { |
| for (int kk = 4; kk > k; kk--) { top5[kk] = top5[kk-1]; topv[kk] = topv[kk-1]; } |
| top5[k] = v; topv[k] = logits[v]; |
| break; |
| } |
| } |
| } |
| fprintf(stderr, "[dbg] p_gen=%f attn0=%f top:", *p_gen, attn[0]); |
| for (int k = 0; k < 5; k++) fprintf(stderr, " %d(%.5f)", top5[k], topv[k]); |
| fprintf(stderr, "\n"); |
| } |
| if (getenv("TTM_LOGITS")) { |
| fprintf(stderr, "[logits]"); |
| for (int v = 0; v < 64 && v < (int)m->vocab_count; v++) fprintf(stderr, " %.6f", logits[v]); |
| fprintf(stderr, "\n"); |
| } |
| } |
|
|
| static void vocab_probs(float *probs, const float *logits, float p_gen, float temp, int V) { |
| float mx = -1e30f; |
| for (int v = 0; v < V; v++) if (logits[v] > mx) mx = logits[v]; |
| float sum = 0.f; |
| for (int v = 0; v < V; v++) { |
| probs[v] = expf((logits[v] - mx) / temp); |
| sum += probs[v]; |
| } |
| for (int v = 0; v < V; v++) probs[v] = p_gen * probs[v] / sum; |
| } |
|
|
| |
| |
| static uint32_t utf8_codepoint(const uint8_t *p, int n) { |
| if (n == 1) return p[0]; |
| if (n == 2) return ((uint32_t)(p[0] & 0x1f) << 6) | (p[1] & 0x3f); |
| if (n == 3) return ((uint32_t)(p[0] & 0x0f) << 12) | |
| ((uint32_t)(p[1] & 0x3f) << 6) | (p[2] & 0x3f); |
| return ((uint32_t)(p[0] & 0x07) << 18) | |
| ((uint32_t)(p[1] & 0x3f) << 12) | |
| ((uint32_t)(p[2] & 0x3f) << 6) | (p[3] & 0x3f); |
| } |
|
|
| static int lexical_codepoint(uint32_t cp) { |
| return (cp >= 'a' && cp <= 'z') || (cp >= 'A' && cp <= 'Z') || |
| (cp >= '0' && cp <= '9') || cp == '_' || (cp >= 0x00c0 && cp <= 0x024f); |
| } |
|
|
| static int lexical_connector(uint32_t cp) { |
| return cp == '\'' || cp == '-' || cp == '.' || cp == 0x2019; |
| } |
|
|
| static int lexical_spans(const char *text, int *starts, int *ends, int max_words) { |
| const uint8_t *s = (const uint8_t *)text; |
| int len = (int)strlen(text), n = 0, i = 0; |
| while (i < len && n < max_words) { |
| int cp_len = ttm_utf8_len(s + i, len - i); |
| uint32_t cp = utf8_codepoint(s + i, cp_len); |
| int leading_dot = cp == '.'; |
| if (leading_dot) { |
| int next = i + cp_len; |
| if (next >= len) { i += cp_len; continue; } |
| int next_len = ttm_utf8_len(s + next, len - next); |
| if (!lexical_codepoint(utf8_codepoint(s + next, next_len))) { |
| i += cp_len; |
| continue; |
| } |
| } else if (!lexical_codepoint(cp)) { |
| i += cp_len; |
| continue; |
| } |
| int start = i; |
| i += cp_len; |
| while (i < len) { |
| cp_len = ttm_utf8_len(s + i, len - i); |
| cp = utf8_codepoint(s + i, cp_len); |
| if (lexical_codepoint(cp)) { i += cp_len; continue; } |
| if (cp == '+' || cp == '#') { i += cp_len; continue; } |
| if (lexical_connector(cp)) { |
| int next = i + cp_len; |
| if (next < len) { |
| int next_len = ttm_utf8_len(s + next, len - next); |
| uint32_t next_cp = utf8_codepoint(s + next, next_len); |
| if (lexical_codepoint(next_cp)) { i = next; continue; } |
| } |
| } |
| break; |
| } |
| starts[n] = start; |
| ends[n] = i; |
| n++; |
| } |
| return n; |
| } |
|
|
| static int valid_input_bytes(const char *text) { |
| const uint8_t *p = (const uint8_t *)text; |
| int remaining = (int)strlen(text); |
| while (remaining > 0) { |
| if (*p < 0x20 && !ttm_is_ws(*p)) return 0; |
| int n = ttm_utf8_len(p, remaining); |
| if (n <= 0) return 0; |
| p += n; |
| remaining -= n; |
| } |
| return 1; |
| } |
|
|
| static int ascii_word_eq(const char *a, const char *b, int n) { |
| for (int i = 0; i < n; i++) { |
| uint8_t x = (uint8_t)a[i], y = (uint8_t)b[i]; |
| if (x >= 'A' && x <= 'Z') x += 'a' - 'A'; |
| if (y >= 'A' && y <= 'Z') y += 'a' - 'A'; |
| if (x != y) return 0; |
| } |
| return 1; |
| } |
|
|
| static void collapse_duplicate_words(char *s) { |
| char out[512]; |
| int oi = 0; |
| for (int i = 0; s[i] && oi < (int)sizeof(out) - 1;) { |
| while (s[i] == ' ') i++; |
| if (!s[i]) break; |
| int start = i; |
| while (s[i] && s[i] != ' ') i++; |
| int len = i - start, duplicate = 0; |
| for (int p = 0; p < oi;) { |
| while (p < oi && out[p] == ' ') p++; |
| int q = p; |
| while (q < oi && out[q] != ' ') q++; |
| if (q - p == len && ascii_word_eq(out + p, s + start, len)) { |
| duplicate = 1; |
| break; |
| } |
| p = q; |
| } |
| if (duplicate) continue; |
| if (oi > 0) out[oi++] = ' '; |
| memcpy(out + oi, s + start, (size_t)len); |
| oi += len; |
| } |
| out[oi] = 0; |
| memcpy(s, out, (size_t)oi + 1); |
| } |
|
|
| static int ascii_small_word(const char *s, int len) { |
| static const char *small[] = { |
| "a", "an", "and", "as", "at", "but", "by", "for", "from", "in", |
| "into", "nor", "of", "on", "or", "over", "per", "the", "to", "via", |
| "vs", "with" |
| }; |
| for (size_t k = 0; k < sizeof(small) / sizeof(small[0]); k++) { |
| int n = (int)strlen(small[k]); |
| if (n != len) continue; |
| int same = 1; |
| for (int i = 0; i < len; i++) { |
| uint8_t c = (uint8_t)s[i]; |
| if (c >= 'A' && c <= 'Z') c += 'a' - 'A'; |
| if (c != (uint8_t)small[k][i]) { same = 0; break; } |
| } |
| if (same) return 1; |
| } |
| return 0; |
| } |
|
|
| static void title_case_ascii(char *s) { |
| int word_index = 0; |
| for (int i = 0; s[i];) { |
| while (s[i] == ' ') i++; |
| if (!s[i]) break; |
| int start = i; |
| while (s[i] && s[i] != ' ') i++; |
| int end = i, internal_upper = 0, first_alpha = -1; |
| for (int j = start; j < end; j++) { |
| uint8_t c = (uint8_t)s[j]; |
| if (first_alpha < 0 && ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) |
| first_alpha = j; |
| if (j > start && c >= 'A' && c <= 'Z') internal_upper = 1; |
| } |
| int small = word_index > 0 && ascii_small_word(s + start, end - start); |
| if (small || !internal_upper) { |
| for (int j = start; j < end; j++) |
| if (s[j] >= 'A' && s[j] <= 'Z') s[j] += 'a' - 'A'; |
| } |
| if (!small && !internal_upper && first_alpha >= 0 && |
| s[first_alpha] >= 'a' && s[first_alpha] <= 'z') |
| s[first_alpha] -= 'a' - 'A'; |
| word_index++; |
| } |
| } |
|
|
| static int render_token(const ttm_model *m, uint32_t id, char *out) { |
| int len; |
| const char *s = ttm_vocab_str(m, id, &len, NULL); |
| int written = 0; |
| for (int i = 0; i < len;) { |
| if (i + 2 < len && (uint8_t)s[i] == 0xe2 && |
| (uint8_t)s[i + 1] == 0x96 && (uint8_t)s[i + 2] == 0x81) { |
| out[written++] = ' '; |
| i += 3; |
| } else { |
| out[written++] = s[i++]; |
| } |
| } |
| return written; |
| } |
|
|
| #define TTM_BEAM 2 |
|
|
| typedef struct { |
| float h[D_DEC]; |
| float prev_emb[D_EMB]; |
| float cov[TTM_MAX_SRC]; |
| float score; |
| int tokens; |
| int done; |
| char title[512]; |
| int title_len; |
| } beam_hyp; |
|
|
| static int rendered_word_count(const char *s) { |
| int count = 0, in_word = 0; |
| for (; *s; s++) { |
| int ws = *s == ' ' || *s == '\t' || *s == '\n' || *s == '\r'; |
| if (!ws && !in_word) { count++; in_word = 1; } |
| if (ws) in_word = 0; |
| } |
| return count; |
| } |
|
|
| static float beam_rank(const beam_hyp *b) { |
| if (b->done && rendered_word_count(b->title) < 2) return -1e30f; |
| float length_penalty = powf((5.f + (float)(b->tokens > 0 ? b->tokens : 1)) / 6.f, 0.6f); |
| return b->score / length_penalty; |
| } |
|
|
| static void append_generated(beam_hyp *b, const ttm_model *m, uint32_t id) { |
| char rendered[256]; |
| int len = render_token(m, id, rendered); |
| int skip = b->title_len == 0 && len > 0 && rendered[0] == ' '; |
| if (skip) { len--; memmove(rendered, rendered + 1, (size_t)len); } |
| if (len > (int)sizeof(b->title) - 1 - b->title_len) |
| len = (int)sizeof(b->title) - 1 - b->title_len; |
| memcpy(b->title + b->title_len, rendered, (size_t)len); |
| b->title_len += len; |
| b->title[b->title_len] = 0; |
| } |
|
|
| static void append_copied_word(beam_hyp *b, const char *text, int bs, int be) { |
| if (b->title_len > 0 && b->title[b->title_len - 1] != ' ' && |
| b->title_len < (int)sizeof(b->title) - 1) b->title[b->title_len++] = ' '; |
| int len = be - bs; |
| if (len > (int)sizeof(b->title) - 1 - b->title_len) |
| len = (int)sizeof(b->title) - 1 - b->title_len; |
| memcpy(b->title + b->title_len, text + bs, (size_t)len); |
| b->title_len += len; |
| b->title[b->title_len] = 0; |
| } |
|
|
| int main(int argc, char **argv) { |
| if (argc < 2) { fprintf(stderr, "usage: %s <model.ttm1> [\"user message\"]\n", argv[0]); return 1; } |
| ttm_model m; |
| if (ttm_load(argv[1], &m) != 0) return 1; |
| int emb_dims = 0; int32_t emb_rows = 0, emb_cols = 0; |
| ttm_tensor(&m, T_EMB, &emb_dims, &emb_rows, &emb_cols); |
| if (ttm_find_tensor(&m, T_DEC_CTX) < 0 || emb_dims != 2 || |
| emb_rows != (int32_t)m.vocab_count || emb_cols != D_EMB) { |
| fprintf(stderr, "incompatible ttm1: expected word-copy dimensions\n"); |
| ttm_unload(&m); |
| return 1; |
| } |
| if (getenv("TTM_TOUCH")) ttm_touch_all(&m); |
|
|
| const char *text = (argc >= 3) ? argv[2] : NULL; |
| char buf[TTM_MAX_BYTES + 2]; |
| if (!text) { |
| if (!fgets(buf, sizeof(buf), stdin)) return 1; |
| buf[strcspn(buf, "\n")] = 0; |
| text = buf; |
| } |
| if (strlen(text) > TTM_MAX_BYTES) { fprintf(stderr, "input too long\n"); return 1; } |
| if (!valid_input_bytes(text)) { fprintf(stderr, "input contains control bytes\n"); return 1; } |
|
|
| |
| uint32_t ids[TTM_MAX_SRC * 4]; |
| int bstart[TTM_MAX_SRC * 4], bend[TTM_MAX_SRC * 4]; |
| int n = ttm_tokenize(&m, (const uint8_t *)text, (int)strlen(text), ids, bstart, bend, TTM_MAX_SRC * 4); |
| if (n <= 0) { fprintf(stderr, "empty input\n"); ttm_unload(&m); return 1; } |
|
|
| if (argc >= 4 && strcmp(argv[3], "--dump-tokens") == 0) { |
| for (int i = 0; i < n; i++) printf("%u%c", ids[i], i + 1 < n ? ' ' : '\n'); |
| ttm_unload(&m); |
| return 0; |
| } |
|
|
| |
| int offs[TTM_MAX_SRC]; |
| if (n <= TTM_MAX_SRC) { |
| for (int i = 0; i < n; i++) offs[i] = i; |
| } else { |
| int keep_first = 192; |
| struct { float s; int i; } scored[TTM_MAX_SRC * 4]; |
| int nscored = 0; |
| for (int i = keep_first; i < n; i++) { |
| int l = bend[i] - bstart[i]; |
| const char *w = text + bstart[i]; |
| float s = 0.f; |
| int has_digit = 0, cap = 0; |
| if (l >= 1 && w[0] >= 'A' && w[0] <= 'Z') cap = 1; |
| for (int c = 0; c < l; c++) if (w[c] >= '0' && w[c] <= '9') { has_digit = 1; break; } |
| if (cap) s += 0.8f; |
| if (has_digit) s += 0.5f; |
| if (ids[i] == TTM_UNK_ID) s += 1.5f; |
| s += 0.3f * (l < 8 ? l : 8); |
| s += 0.02f * (i - keep_first); |
| scored[nscored].s = s; scored[nscored].i = i; |
| nscored++; |
| } |
| int m64 = nscored < 64 ? nscored : 64; |
| for (int a = 0; a < m64; a++) { |
| int best = a; |
| for (int b = a + 1; b < nscored; b++) |
| if (scored[b].s > scored[best].s) best = b; |
| float ts = scored[a].s; int ti = scored[a].i; |
| scored[a].s = scored[best].s; scored[a].i = scored[best].i; |
| scored[best].s = ts; scored[best].i = ti; |
| } |
| int picked[64]; |
| for (int a = 0; a < m64; a++) picked[a] = scored[a].i; |
| for (int a = 0; a < m64; a++) |
| for (int b = a + 1; b < m64; b++) |
| if (picked[b] < picked[a]) { int t = picked[a]; picked[a] = picked[b]; picked[b] = t; } |
| for (int i = 0; i < keep_first; i++) offs[i] = i; |
| for (int a = 0; a < m64; a++) offs[keep_first + a] = picked[a]; |
| n = keep_first + m64; |
| } |
| uint32_t sel_ids[TTM_MAX_SRC]; |
| int sel_bs[TTM_MAX_SRC], sel_be[TTM_MAX_SRC]; |
| for (int i = 0; i < n; i++) { |
| sel_ids[i] = ids[offs[i]]; |
| sel_bs[i] = bstart[offs[i]]; |
| sel_be[i] = bend[offs[i]]; |
| } |
|
|
| |
| |
| int all_word_bs[TTM_MAX_SRC * 4], all_word_be[TTM_MAX_SRC * 4]; |
| int all_word_count = lexical_spans(text, all_word_bs, all_word_be, TTM_MAX_SRC * 4); |
| int represented[TTM_MAX_SRC * 4]; |
| for (int i = 0; i < all_word_count; i++) represented[i] = -1; |
| int token_word[TTM_MAX_SRC], word_bs[TTM_MAX_SRC], word_be[TTM_MAX_SRC]; |
| for (int i = 0; i < n; i++) token_word[i] = -1; |
| int word_count = 0; |
| for (int i = 0; i < n; i++) { |
| for (int w = 0; w < all_word_count; w++) { |
| if (sel_be[i] > all_word_bs[w] && sel_bs[i] < all_word_be[w]) { |
| if (represented[w] < 0 && word_count < TTM_MAX_SRC) { |
| represented[w] = word_count; |
| word_bs[word_count] = all_word_bs[w]; |
| word_be[word_count] = all_word_be[w]; |
| word_count++; |
| } |
| token_word[i] = represented[w]; |
| break; |
| } |
| } |
| } |
|
|
| |
| size_t work_size = (size_t)n * D_EMB + 3 * D_ENC_HALF + 7 * D_ENC_HALF; |
| float *work = malloc(work_size * sizeof(float)); |
| float *src_enc = malloc((size_t)n * D_ENC * sizeof(float)); |
| float *attn = malloc((size_t)n * sizeof(float)); |
| float *cov = calloc((size_t)n, sizeof(float)); |
| size_t gate_in = D_DEC + D_ENC + D_EMB; |
| size_t dsize = (size_t)n * D_ATT + D_ENC + 2 * D_EMB + |
| gate_in + 2 * D_ATT + gate_in + D_ENC + 64; |
| float *dscratch = malloc(dsize * sizeof(float)); |
| float *logits = malloc(V_MAX * sizeof(float)); |
| float *probs = malloc(V_MAX * sizeof(float)); |
| if (!work || !src_enc || !attn || !cov || !dscratch || !logits || !probs) { perror("alloc"); return 1; } |
|
|
| |
| encode(&m, sel_ids, n, src_enc, work); |
|
|
| |
| float h[D_DEC]; |
| { |
| float summary[D_ENC], row[D_ENC], diw[D_DEC]; |
| memcpy(summary, src_enc + (size_t)(n - 1) * D_ENC, |
| D_ENC_HALF * sizeof(float)); |
| memcpy(summary + D_ENC_HALF, src_enc + D_ENC_HALF, |
| D_ENC_HALF * sizeof(float)); |
| const float *bias = ttm_vec(&m, T_DEC_INIT_B, NULL); |
| matvec(&m, T_DEC_INIT, D_DEC, D_ENC, summary, diw, row); |
| for (int i = 0; i < D_DEC; i++) h[i] = tanhf(diw[i] + bias[i]); |
| } |
|
|
| |
| |
| const int max_tokens = 16, eos_id = 1; |
| int beam_width = 1; |
| const char *beam_env = getenv("TTM_BEAM_WIDTH"); |
| if (beam_env && atoi(beam_env) == 2) beam_width = TTM_BEAM; |
| beam_hyp beams[TTM_BEAM], candidates[TTM_BEAM * TTM_BEAM]; |
| memset(beams, 0, sizeof(beams)); |
| memcpy(beams[0].h, h, sizeof(h)); |
| ttm_row(&m, T_EMB, 0, beams[0].prev_emb); |
|
|
| int beam_count = 1; |
|
|
| for (int t = 0; t < max_tokens; t++) { |
| int candidate_count = 0; |
| for (int b = 0; b < beam_count; b++) { |
| if (beams[b].done) { |
| candidates[candidate_count++] = beams[b]; |
| continue; |
| } |
| float p_gen, step_ctx[D_ENC]; |
| decode_step(&m, beams[b].h, beams[b].prev_emb, src_enc, n, |
| beams[b].cov, attn, logits, &p_gen, step_ctx, dscratch); |
| vocab_probs(probs, logits, p_gen, 1.0f, m.vocab_count); |
| if (t < 2 || rendered_word_count(beams[b].title) < 2) probs[eos_id] = 0.f; |
| probs[0] = 0.f; |
| float word_probs[TTM_MAX_SRC] = {0}; |
| for (int i = 0; i < n; i++) |
| if (token_word[i] >= 0) word_probs[token_word[i]] += (1.f - p_gen) * attn[i]; |
|
|
| int top[TTM_BEAM] = {0, 0}; |
| float top_p[TTM_BEAM] = {-1.f, -1.f}; |
| for (int action = 0; action < (int)m.vocab_count + word_count; action++) { |
| float probability = action < (int)m.vocab_count ? probs[action] : |
| word_probs[action - (int)m.vocab_count]; |
| for (int k = 0; k < beam_width; k++) { |
| if (probability > top_p[k]) { |
| for (int q = beam_width - 1; q > k; q--) { |
| top_p[q] = top_p[q - 1]; top[q] = top[q - 1]; |
| } |
| top_p[k] = probability; top[k] = action; |
| break; |
| } |
| } |
| } |
|
|
| for (int k = 0; k < beam_width; k++) { |
| if (top_p[k] <= 0.f || candidate_count >= TTM_BEAM * TTM_BEAM) continue; |
| beam_hyp child = beams[b]; |
| child.score += logf(top_p[k] + 1e-30f); |
| child.tokens++; |
| int action = top[k]; |
| if (action == eos_id) { |
| child.done = 1; |
| candidates[candidate_count++] = child; |
| continue; |
| } |
|
|
| if (action < (int)m.vocab_count) { |
| append_generated(&child, &m, (uint32_t)action); |
| ttm_row(&m, T_EMB, action, child.prev_emb); |
| } else { |
| int group = action - (int)m.vocab_count; |
| append_copied_word(&child, text, word_bs[group], word_be[group]); |
| memset(child.prev_emb, 0, sizeof(child.prev_emb)); |
| int group_tokens = 0; |
| float token_emb[D_EMB]; |
| for (int i = 0; i < n; i++) if (token_word[i] == group) { |
| ttm_row(&m, T_EMB, (int)sel_ids[i], token_emb); |
| for (int j = 0; j < D_EMB; j++) child.prev_emb[j] += token_emb[j]; |
| group_tokens++; |
| } |
| if (group_tokens > 0) |
| for (int j = 0; j < D_EMB; j++) child.prev_emb[j] /= (float)group_tokens; |
| } |
| float ctx_proj[D_EMB], dec_input[D_EMB], rowbuf[D_ENC]; |
| matvec(&m, T_DEC_CTX, D_EMB, D_ENC, step_ctx, ctx_proj, rowbuf); |
| for (int i = 0; i < D_EMB; i++) |
| dec_input[i] = child.prev_emb[i] + tanhf(ctx_proj[i]); |
| float h_new[D_DEC], gscratch[7 * D_DEC]; |
| gru_cell(&m, T_DEC_W, T_DEC_U, T_DEC_B, D_EMB, D_DEC, |
| dec_input, beams[b].h, h_new, gscratch); |
| memcpy(child.h, h_new, sizeof(h_new)); |
| for (int i = 0; i < n; i++) child.cov[i] = beams[b].cov[i] + attn[i]; |
| candidates[candidate_count++] = child; |
| } |
| } |
| if (candidate_count == 0) break; |
| for (int i = 0; i < candidate_count; i++) { |
| int best = i; |
| for (int j = i + 1; j < candidate_count; j++) |
| if (beam_rank(&candidates[j]) > beam_rank(&candidates[best])) best = j; |
| if (best != i) { beam_hyp tmp = candidates[i]; candidates[i] = candidates[best]; candidates[best] = tmp; } |
| } |
| beam_count = candidate_count < beam_width ? candidate_count : beam_width; |
| for (int i = 0; i < beam_count; i++) beams[i] = candidates[i]; |
| int all_done = 1; |
| for (int i = 0; i < beam_count; i++) if (!beams[i].done) all_done = 0; |
| if (all_done) break; |
| } |
| int winner = 0; |
| for (int i = 1; i < beam_count; i++) |
| if (beam_rank(&beams[i]) > beam_rank(&beams[winner])) winner = i; |
| beams[winner].title[beams[winner].title_len] = 0; |
| collapse_duplicate_words(beams[winner].title); |
| title_case_ascii(beams[winner].title); |
| printf("%s\n", beams[winner].title); |
| if (getenv("TTM_RSS")) { |
| |
| |
| FILE *pf = fopen("/proc/self/status", "r"); |
| char line[256]; |
| while (pf && fgets(line, sizeof(line), pf)) { |
| if (strncmp(line, "VmHWM:", 6) == 0) { |
| fprintf(stderr, "[rss] peak=%s kb\n", line + 6); |
| break; |
| } |
| } |
| if (pf) fclose(pf); |
| } |
|
|
| free(work); free(src_enc); free(attn); free(cov); free(dscratch); free(logits); free(probs); |
| ttm_unload(&m); |
| return 0; |
| } |
|
|