chantier B TTS #5: P3.3 = vrai CP runner intégré (bit-exact)
cp_inference.h/cpp : extrait de cp_runner.cpp standalone (kazeia-tts-decoder-ggml/),
adapté en module réutilisable. Architecture identique (qwen3 5L, GQA 16/8, head_dim
128, RoPE NEOX theta=1e6, q/k-norm AVANT RoPE, recompute T<=16 sans KV cache).
API : cp_load() + cp_predict(hidden[1024], cb0_emb[1024]) -> int32[15] = CB1..CB15.
cp_validate.cpp : reproduit sur tablette les conditions du standalone cp_runner
-> 495/495 codes match (33/33 frames parfaits) vs cp.generate(do_sample=False)
greedy golden, sur les 33 frames du dump 'Bonjour' historique. CP intégré =
bit-exact au standalone, qui est lui-même bit-exact à PyTorch.
tts_orchestrate.cpp mis à jour : si cp_f16.gguf/cp_heads/cp_codec_embs présents
dans <dump_dir>, le CP est ENABLED et remplace le teacher-forcing CB1..15.
Hidden state Talker capturé via llama_get_embeddings_ith(ctx,-1) après chaque
decode_embeds (pas besoin de re-instrumenter le forward Talker).
Mesure sur 'Bonjour je m'appelle Kazeia' (N=26 frames, audio 2.17s) tablette CPU 4t :
Talker prefill (T=19) : 0.124 s
Loop (N=26 steps) : 7.705 s talker_decode=0.763, cp=6.942
per-step talker : 29.3 ms
per-step CP (15 pass): 267.0 ms
Total Talker+CP : 7.829 s -> RTF talker+cp = 3.61
Le CP est le nouveau goulot (267ms × 15 passes/step). Levier P3.5 = KV cache CP
(recompute -> incremental), threads 8, ou Q8_0 du CP.
CB0 match golden = 3/26 = trajectoire stochastique Python diverge rapidement vs
notre greedy ; PAS un bug CP (cp_validate prouve la conformité bit-exact).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7a998dec6b
commit
746bbe77cd
|
|
@ -0,0 +1,195 @@
|
|||
#include "cp_inference.h"
|
||||
#include <ggml.h>
|
||||
#include <gguf.h>
|
||||
#include <ggml-cpu.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
|
||||
// Hyperparams CP (Qwen3-TTS Code Predictor) — fixés par l'architecture du modèle.
|
||||
static const int N_EMBD = 1024;
|
||||
static const int N_LAYER = 5;
|
||||
static const int N_HEAD = 16;
|
||||
static const int N_KV = 8;
|
||||
static const int HEAD_DIM = 128;
|
||||
static const int N_VOCAB = 2048;
|
||||
static const int N_CB = 15;
|
||||
static const float ROPE_BASE = 1000000.0f;
|
||||
static const float RMS_EPS = 1e-6f;
|
||||
|
||||
static std::vector<float> read_floats(const char* path, size_t expect) {
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) { fprintf(stderr, "cp_load: open fail %s\n", path); return {}; }
|
||||
std::vector<float> v(expect);
|
||||
if (fread(v.data(), sizeof(float), expect, f) != expect) {
|
||||
fprintf(stderr, "cp_load: short read %s\n", path); fclose(f); return {};
|
||||
}
|
||||
fclose(f); return v;
|
||||
}
|
||||
|
||||
bool cp_load(CPState& s, const char* gguf_path, const char* heads_path, const char* embs_path) {
|
||||
ggml_context* meta = nullptr;
|
||||
gguf_init_params p; p.no_alloc = true; p.ctx = &meta;
|
||||
gguf_context* g = gguf_init_from_file(gguf_path, p);
|
||||
if (!g) { fprintf(stderr, "cp_load: gguf open fail %s\n", gguf_path); return false; }
|
||||
int64_t n = gguf_get_n_tensors(g);
|
||||
size_t bytes = 0;
|
||||
for (int64_t i = 0; i < n; i++) {
|
||||
ggml_tensor* mt = ggml_get_tensor(meta, gguf_get_tensor_name(g, i));
|
||||
bytes += ggml_nelements(mt) * sizeof(float);
|
||||
}
|
||||
ggml_init_params ip = { bytes + (size_t)n * ggml_tensor_overhead() + (1u << 20), nullptr, false };
|
||||
s.weights_ctx = ggml_init(ip);
|
||||
|
||||
FILE* f = fopen(gguf_path, "rb");
|
||||
const size_t off = gguf_get_data_offset(g);
|
||||
std::vector<uint8_t> tmp;
|
||||
for (int64_t i = 0; i < n; i++) {
|
||||
const char* name = gguf_get_tensor_name(g, i);
|
||||
ggml_tensor* mt = ggml_get_tensor(meta, name);
|
||||
ggml_tensor* t32 = ggml_new_tensor(s.weights_ctx, GGML_TYPE_F32, ggml_n_dims(mt), mt->ne);
|
||||
size_t nb = ggml_nbytes(mt);
|
||||
int64_t ne = ggml_nelements(mt);
|
||||
tmp.resize(nb);
|
||||
fseek(f, off + gguf_get_tensor_offset(g, i), SEEK_SET);
|
||||
if (fread(tmp.data(), 1, nb, f) != nb) { fprintf(stderr, "cp_load read fail %s\n", name); fclose(f); return false; }
|
||||
if (mt->type == GGML_TYPE_F32) memcpy(t32->data, tmp.data(), nb);
|
||||
else if (mt->type == GGML_TYPE_F16) ggml_fp16_to_fp32_row((const ggml_fp16_t*)tmp.data(), (float*)t32->data, ne);
|
||||
else { fprintf(stderr, "cp_load unsupported type %s for %s\n", ggml_type_name(mt->type), name); fclose(f); return false; }
|
||||
s.tensors[name] = t32;
|
||||
}
|
||||
fclose(f);
|
||||
gguf_free(g);
|
||||
|
||||
const size_t TAB = (size_t)N_CB * N_VOCAB * N_EMBD;
|
||||
s.heads = read_floats(heads_path, TAB); if (s.heads.empty()) return false;
|
||||
s.codec_embs = read_floats(embs_path, TAB); if (s.codec_embs.empty()) return false;
|
||||
fprintf(stderr, "cp_load: %lld tensors + heads(%.0f MB) + codec_embs(%.0f MB) OK\n",
|
||||
(long long)n, TAB * 4 / 1048576.0, TAB * 4 / 1048576.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void cp_free(CPState& s) {
|
||||
if (s.weights_ctx) ggml_free(s.weights_ctx);
|
||||
s.weights_ctx = nullptr;
|
||||
s.tensors.clear();
|
||||
s.heads.clear();
|
||||
s.codec_embs.clear();
|
||||
}
|
||||
|
||||
// Forward un transformer 5L sur X[N_EMBD, L] -> out_hn = hidden après output_norm à la position `last`.
|
||||
// Architecture identique à cp_runner.cpp (la référence bit-exact validée).
|
||||
static void cp_forward_lastpos(CPState& s, const std::vector<float>& embeds_flat, int L, int last,
|
||||
std::vector<float>& out_hn) {
|
||||
size_t mem = 128ULL * 1024 * 1024;
|
||||
ggml_init_params p = { mem, nullptr, false };
|
||||
ggml_context* ctx = ggml_init(p);
|
||||
|
||||
ggml_tensor* x = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, N_EMBD, L);
|
||||
memcpy(x->data, embeds_flat.data(), (size_t)N_EMBD * L * sizeof(float));
|
||||
ggml_tensor* pos = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, L);
|
||||
for (int i = 0; i < L; i++) ((int32_t*)pos->data)[i] = i;
|
||||
ggml_tensor* mask = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, L, L);
|
||||
{
|
||||
float* m = (float*)mask->data;
|
||||
for (int q = 0; q < L; q++)
|
||||
for (int k = 0; k < L; k++)
|
||||
m[k + L * q] = (k > q) ? -INFINITY : 0.0f;
|
||||
}
|
||||
const float scale = 1.0f / sqrtf((float)HEAD_DIM);
|
||||
|
||||
auto W = [&](const std::string& k) -> ggml_tensor* {
|
||||
auto it = s.tensors.find(k);
|
||||
if (it == s.tensors.end()) { fprintf(stderr, "CP missing tensor %s\n", k.c_str()); abort(); }
|
||||
return it->second;
|
||||
};
|
||||
|
||||
for (int L_i = 0; L_i < N_LAYER; L_i++) {
|
||||
char pre[32]; snprintf(pre, sizeof(pre), "blk.%d", L_i);
|
||||
std::string b = pre;
|
||||
ggml_tensor* res = x;
|
||||
|
||||
ggml_tensor* xn = ggml_rms_norm(ctx, x, RMS_EPS);
|
||||
xn = ggml_mul(ctx, xn, W(b + ".attn_norm.weight"));
|
||||
|
||||
ggml_tensor* Q = ggml_mul_mat(ctx, W(b + ".attn_q.weight"), xn);
|
||||
ggml_tensor* K = ggml_mul_mat(ctx, W(b + ".attn_k.weight"), xn);
|
||||
ggml_tensor* V = ggml_mul_mat(ctx, W(b + ".attn_v.weight"), xn);
|
||||
|
||||
Q = ggml_reshape_3d(ctx, Q, HEAD_DIM, N_HEAD, L);
|
||||
K = ggml_reshape_3d(ctx, K, HEAD_DIM, N_KV, L);
|
||||
V = ggml_reshape_3d(ctx, V, HEAD_DIM, N_KV, L);
|
||||
|
||||
// q/k-norm AVANT RoPE (gotcha)
|
||||
Q = ggml_mul(ctx, ggml_rms_norm(ctx, Q, RMS_EPS), W(b + ".attn_q_norm.weight"));
|
||||
K = ggml_mul(ctx, ggml_rms_norm(ctx, K, RMS_EPS), W(b + ".attn_k_norm.weight"));
|
||||
|
||||
Q = ggml_rope_ext(ctx, Q, pos, NULL, HEAD_DIM, GGML_ROPE_TYPE_NEOX, 0,
|
||||
ROPE_BASE, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f);
|
||||
K = ggml_rope_ext(ctx, K, pos, NULL, HEAD_DIM, GGML_ROPE_TYPE_NEOX, 0,
|
||||
ROPE_BASE, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f);
|
||||
|
||||
Q = ggml_cont(ctx, ggml_permute(ctx, Q, 0, 2, 1, 3));
|
||||
K = ggml_cont(ctx, ggml_permute(ctx, K, 0, 2, 1, 3));
|
||||
V = ggml_cont(ctx, ggml_permute(ctx, V, 1, 2, 0, 3));
|
||||
|
||||
ggml_tensor* KQ = ggml_mul_mat(ctx, K, Q);
|
||||
KQ = ggml_soft_max_ext(ctx, KQ, mask, scale, 0.0f);
|
||||
ggml_tensor* KQV = ggml_mul_mat(ctx, V, KQ);
|
||||
KQV = ggml_cont(ctx, ggml_permute(ctx, KQV, 0, 2, 1, 3));
|
||||
KQV = ggml_reshape_2d(ctx, KQV, HEAD_DIM * N_HEAD, L);
|
||||
|
||||
ggml_tensor* attn = ggml_mul_mat(ctx, W(b + ".attn_output.weight"), KQV);
|
||||
x = ggml_add(ctx, res, attn);
|
||||
|
||||
res = x;
|
||||
ggml_tensor* xn2 = ggml_rms_norm(ctx, x, RMS_EPS);
|
||||
xn2 = ggml_mul(ctx, xn2, W(b + ".ffn_norm.weight"));
|
||||
ggml_tensor* g = ggml_silu(ctx, ggml_mul_mat(ctx, W(b + ".ffn_gate.weight"), xn2));
|
||||
ggml_tensor* u = ggml_mul_mat(ctx, W(b + ".ffn_up.weight"), xn2);
|
||||
ggml_tensor* ff = ggml_mul_mat(ctx, W(b + ".ffn_down.weight"), ggml_mul(ctx, g, u));
|
||||
x = ggml_add(ctx, res, ff);
|
||||
}
|
||||
x = ggml_rms_norm(ctx, x, RMS_EPS);
|
||||
x = ggml_mul(ctx, x, W("output_norm.weight"));
|
||||
|
||||
ggml_cgraph* gf = ggml_new_graph_custom(ctx, 8192, false);
|
||||
ggml_build_forward_expand(gf, x);
|
||||
ggml_graph_compute_with_ctx(ctx, gf, s.n_threads);
|
||||
|
||||
out_hn.resize(N_EMBD);
|
||||
memcpy(out_hn.data(), (float*)x->data + (size_t)N_EMBD * last, N_EMBD * sizeof(float));
|
||||
ggml_free(ctx);
|
||||
}
|
||||
|
||||
void cp_predict(CPState& s, const float* hidden, const float* cb0_emb, int32_t* out_codes) {
|
||||
// embeds = [hidden, cb0_emb, codec_embs[0][cb1], codec_embs[1][cb2], ..., codec_embs[13][cb14]]
|
||||
// step s (1..15) : forward sur les (s+1) tokens, sortir hidden à la position `s`, head[s-1] -> argmax.
|
||||
std::vector<float> embeds; embeds.reserve((size_t)17 * N_EMBD);
|
||||
embeds.insert(embeds.end(), hidden, hidden + N_EMBD);
|
||||
embeds.insert(embeds.end(), cb0_emb, cb0_emb + N_EMBD);
|
||||
|
||||
std::vector<float> hn;
|
||||
for (int step = 1; step <= N_CB; step++) {
|
||||
const int L = step + 1;
|
||||
cp_forward_lastpos(s, embeds, L, step, hn);
|
||||
|
||||
// tête[step-1] : argmax_j sum_k hn[k] * heads[step-1, j, k]
|
||||
const float* Wh = &s.heads[(size_t)(step - 1) * N_VOCAB * N_EMBD];
|
||||
int best = 0; float bv = -1e30f;
|
||||
for (int j = 0; j < N_VOCAB; j++) {
|
||||
const float* wj = Wh + (size_t)j * N_EMBD;
|
||||
float dot = 0.f;
|
||||
for (int k = 0; k < N_EMBD; k++) dot += hn[k] * wj[k];
|
||||
if (dot > bv) { bv = dot; best = j; }
|
||||
}
|
||||
out_codes[step - 1] = best;
|
||||
|
||||
// feedback : append codec_embs[step-1][best] aux embeds
|
||||
if (step < N_CB) {
|
||||
const float* e = &s.codec_embs[(size_t)(step - 1) * N_VOCAB * N_EMBD + (size_t)best * N_EMBD];
|
||||
embeds.insert(embeds.end(), e, e + N_EMBD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// Code Predictor (Qwen3-TTS) inference, extrait de
|
||||
// /opt/Kazeia/kazeia-tts-decoder-ggml/src/cp_runner.cpp et exposé en API
|
||||
// utilisable depuis tts_orchestrate. Architecture : transformer qwen3 5 couches,
|
||||
// hidden 1024, GQA 16/8, head_dim 128, RMS eps 1e-6, RoPE NEOX theta=1e6.
|
||||
// q_norm/k_norm AVANT RoPE (gotcha critique).
|
||||
//
|
||||
// Forward autoregressif RVQ : 15 passes pour produire CB1..CB15 depuis
|
||||
// (hidden_Talker[1024], cb0_emb[1024]). Approche "recompute" (T<=16 -> coût
|
||||
// quadratique négligeable, pas de KV cache manuel).
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
struct ggml_context;
|
||||
struct ggml_tensor;
|
||||
|
||||
struct CPState {
|
||||
ggml_context * weights_ctx = nullptr;
|
||||
std::unordered_map<std::string, ggml_tensor*> tensors; // weights par nom
|
||||
std::vector<float> heads; // [15, 2048, 1024] f32
|
||||
std::vector<float> codec_embs; // [15, 2048, 1024] f32
|
||||
int n_threads = 4;
|
||||
};
|
||||
|
||||
// Charge cp_f16.gguf + cp_heads.bin + cp_codec_embs.bin. Retourne false en cas d'échec.
|
||||
bool cp_load(CPState& s, const char* gguf_path, const char* heads_path, const char* embs_path);
|
||||
|
||||
// Libère les ressources.
|
||||
void cp_free(CPState& s);
|
||||
|
||||
// Prédit CB1..CB15 (15 codes int32) depuis le hidden state du Talker et l'embed de CB0.
|
||||
// hidden : float[1024]
|
||||
// cb0_emb: float[1024]
|
||||
// out_codes : int32_t[15] (CB1..CB15)
|
||||
void cp_predict(CPState& s, const float* hidden, const float* cb0_emb, int32_t* out_codes);
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// Validation : mon cp_inference (intégré dans tts_orchestrate) doit reproduire
|
||||
// cp_runner standalone bit-exact. Sur test_cp_input.bin (33 frames hidden+CB0
|
||||
// du Talker PyTorch) → comparer codes CB1..15 vs test_codes_greedy.bin
|
||||
// (golden cp.generate do_sample=False). Cible : 33/33 frames parfaits.
|
||||
#include "cp_inference.h"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 6) {
|
||||
printf("usage: %s <cp_gguf> <cp_heads.bin> <cp_codec_embs.bin> <test_cp_input.bin> <golden.bin>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
CPState s; s.n_threads = 4;
|
||||
if (!cp_load(s, argv[1], argv[2], argv[3])) { printf("cp_load FAILED\n"); return 1; }
|
||||
|
||||
// input : int32 T, T * (hidden[1024] + cb0_emb[1024]) f32
|
||||
FILE* fi = fopen(argv[4], "rb"); if (!fi) { printf("input fail\n"); return 1; }
|
||||
int32_t T = 0; if (fread(&T, 4, 1, fi) != 1) return 1;
|
||||
std::vector<float> inbuf((size_t)T * 2 * 1024);
|
||||
if (fread(inbuf.data(), sizeof(float), inbuf.size(), fi) != inbuf.size()) return 1;
|
||||
fclose(fi);
|
||||
|
||||
// golden : pour test_codes_greedy.bin pas de header (T * 16 int32 direct, 33 * 16 * 4 = 2112 bytes ≈ 2116)
|
||||
FILE* fg = fopen(argv[5], "rb"); if (!fg) return 1;
|
||||
fseek(fg, 0, SEEK_END); long gsz = ftell(fg); fseek(fg, 0, SEEK_SET);
|
||||
// 2 layouts possibles : (a) header int32+T*16 int32 (size = 4 + T*64),
|
||||
// (b) raw T*16 int32 (size = T*64). On déduit.
|
||||
bool has_hdr = (gsz == 4 + (long)T * 64);
|
||||
if (has_hdr) { int32_t ng = 0; fread(&ng, 4, 1, fg); printf("golden has header, ng=%d\n", ng); }
|
||||
std::vector<int32_t> gold((size_t)T * 16);
|
||||
size_t expect = T * 16;
|
||||
if (fread(gold.data(), sizeof(int32_t), expect, fg) != expect) {
|
||||
// Maybe golden has fewer or different format
|
||||
printf("golden short read; size %ld, expected %zu*4=%zu bytes (T=%d)\n", gsz, expect, expect*4, T);
|
||||
fclose(fg); return 1;
|
||||
}
|
||||
fclose(fg);
|
||||
|
||||
printf("running CP on %d frames...\n", T);
|
||||
long n_match_cb15 = 0, total_codes_cb15 = 0;
|
||||
int n_perfect_cb15 = 0;
|
||||
for (int f = 0; f < T; f++) {
|
||||
const float* hidden = &inbuf[(size_t)f * 2 * 1024];
|
||||
const float* cb0_emb = &inbuf[(size_t)f * 2 * 1024 + 1024];
|
||||
int32_t codes15[15];
|
||||
cp_predict(s, hidden, cb0_emb, codes15);
|
||||
|
||||
// golden format = [CB0 ... CB15] (16 entries per frame)
|
||||
const int32_t* gf16 = &gold[(size_t)f * 16];
|
||||
int match = 0;
|
||||
for (int c = 0; c < 15; c++) if (codes15[c] == gf16[1 + c]) match++;
|
||||
n_match_cb15 += match; total_codes_cb15 += 15;
|
||||
if (match == 15) n_perfect_cb15++;
|
||||
if (f < 3 || match < 15) {
|
||||
printf("frame %2d: CB1..15 match %2d/15 -> CP=[%d,%d,%d,%d] PY=[%d,%d,%d,%d]\n",
|
||||
f, match, codes15[0], codes15[1], codes15[2], codes15[3],
|
||||
gf16[1], gf16[2], gf16[3], gf16[4]);
|
||||
}
|
||||
}
|
||||
printf("\n=== cp_predict (engine) vs cp.generate greedy (golden) ===\n");
|
||||
printf("codes match : %ld/%ld (%.1f%%)\n", n_match_cb15, total_codes_cb15, 100.0 * n_match_cb15 / total_codes_cb15);
|
||||
printf("frames parfaits : %d/%d\n", n_perfect_cb15, T);
|
||||
cp_free(s);
|
||||
return (n_match_cb15 == total_codes_cb15) ? 0 : 5;
|
||||
}
|
||||
|
|
@ -21,8 +21,15 @@
|
|||
#include <vector>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include "llama.h"
|
||||
#include "ggml-backend.h"
|
||||
#include "cp_inference.h"
|
||||
|
||||
static double now_s() {
|
||||
using clk = std::chrono::steady_clock;
|
||||
return std::chrono::duration<double>(clk::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
static ggml_backend_dev_t find_htp() {
|
||||
for (size_t i = 0; i < ggml_backend_dev_count(); ++i) {
|
||||
|
|
@ -54,13 +61,23 @@ static std::vector<int32_t> read_i32(const std::string& p, size_t n_expected) {
|
|||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 4) { printf("usage: %s <gguf> <dump_dir> <out_codes.bin> [cpu|htp] [max_steps]\n", argv[0]); return 1; }
|
||||
if (argc < 4) { printf("usage: %s <talker_gguf> <dump_dir> <out_codes.bin> [cpu|htp] [max_steps]\n", argv[0]); return 1; }
|
||||
const char* gguf = argv[1];
|
||||
std::string D = argv[2]; if (D.back() != '/') D += '/';
|
||||
const char* out_codes = argv[3];
|
||||
bool force_cpu = (argc >= 5 && !strcmp(argv[4], "cpu"));
|
||||
int max_steps_arg = (argc >= 6) ? atoi(argv[5]) : 0;
|
||||
|
||||
// CP runner : charge cp_f16.gguf + cp_heads.bin + cp_codec_embs.bin depuis dump_dir.
|
||||
// Si l'un manque -> fallback teacher-forcing (mode legacy de tts_orchestrate).
|
||||
CPState cp_state;
|
||||
cp_state.n_threads = 4;
|
||||
bool cp_ok = cp_load(cp_state,
|
||||
(D + "cp_f16.gguf").c_str(),
|
||||
(D + "cp_heads.bin").c_str(),
|
||||
(D + "cp_codec_embs.bin").c_str());
|
||||
printf("CP : %s\n", cp_ok ? "ENABLED" : "DISABLED (teacher-forced CB1..15)");
|
||||
|
||||
// manifest
|
||||
int T_prefill = 0, N_steps_golden = 0, n_embd = 1024, n_vocab = 3072;
|
||||
int N_codebooks = 16, cp_vocab = 2048, codec_eos_token_id = 2150;
|
||||
|
|
@ -123,13 +140,15 @@ int main(int argc, char** argv) {
|
|||
sids[i] = &sid0[i];
|
||||
}
|
||||
lg[T_prefill - 1] = 1;
|
||||
const double t_prefill0 = now_s();
|
||||
{
|
||||
llama_batch b{};
|
||||
b.n_tokens = T_prefill; b.embd = prefill_embeds.data();
|
||||
b.pos = pos.data(); b.n_seq_id = nsd.data(); b.seq_id = sids.data(); b.logits = lg.data();
|
||||
if (llama_decode(ctx, b) != 0) { printf("prefill FAILED\n"); return 1; }
|
||||
}
|
||||
printf("prefill OK (T=%d)\n", T_prefill);
|
||||
const double t_prefill = now_s() - t_prefill0;
|
||||
printf("prefill OK (T=%d) en %.3f s\n", T_prefill, t_prefill);
|
||||
|
||||
// sample CB0 at prefill output (first step's "input_ids")
|
||||
auto logits = llama_get_logits_ith(ctx, -1);
|
||||
|
|
@ -140,18 +159,43 @@ int main(int argc, char** argv) {
|
|||
|
||||
// boucle decode
|
||||
std::vector<int32_t> codes_engine(N * N_codebooks, 0);
|
||||
int n_match_cb0 = 0;
|
||||
int n_match_cb0 = 0, n_match_full = 0;
|
||||
int n_eos = -1;
|
||||
double t_compose = 0, t_decode = 0, t_cp = 0;
|
||||
const double t_loop0 = now_s();
|
||||
// capture du hidden state du Talker (pour passer au CP) = ce que renvoie
|
||||
// llama_get_embeddings_ith(ctx,-1) après chaque decode. On l'a déjà au prefill (h ci-dessous).
|
||||
const float* h_last = llama_get_embeddings_ith(ctx, -1);
|
||||
std::vector<float> hidden_for_cp(n_embd);
|
||||
if (h_last) memcpy(hidden_for_cp.data(), h_last, n_embd * sizeof(float));
|
||||
|
||||
for (int s = 0; s < N; ++s) {
|
||||
// Codes pour ce frame s : CB0 = engine greedy ; CB1..CB15 = teacher-forced from golden[s, 1..15]
|
||||
// CB0 = engine greedy
|
||||
codes_engine[s * N_codebooks + 0] = cb0;
|
||||
for (int i = 1; i < N_codebooks; ++i) {
|
||||
codes_engine[s * N_codebooks + i] = codes_golden[s * N_codebooks + i];
|
||||
// CB1..15 : soit CP réel, soit teacher-forced
|
||||
const double tcp0 = now_s();
|
||||
if (cp_ok) {
|
||||
const float* cb0_emb = tok_embd.data() + (size_t)cb0 * n_embd;
|
||||
int32_t out_cb15[15];
|
||||
cp_predict(cp_state, hidden_for_cp.data(), cb0_emb, out_cb15);
|
||||
for (int i = 1; i < N_codebooks; ++i) codes_engine[s * N_codebooks + i] = out_cb15[i - 1];
|
||||
} else {
|
||||
for (int i = 1; i < N_codebooks; ++i) {
|
||||
codes_engine[s * N_codebooks + i] = codes_golden[s * N_codebooks + i];
|
||||
}
|
||||
}
|
||||
t_cp += now_s() - tcp0;
|
||||
if (cb0 == codes_golden[s * N_codebooks + 0]) n_match_cb0++;
|
||||
{
|
||||
int m = 0;
|
||||
for (int i = 0; i < N_codebooks; ++i)
|
||||
if (codes_engine[s * N_codebooks + i] == codes_golden[s * N_codebooks + i]) m++;
|
||||
if (m == N_codebooks) n_match_full++;
|
||||
}
|
||||
if (cb0 == codec_eos_token_id && n_eos < 0) { n_eos = s; printf(" step %d: EOS atteint\n", s); }
|
||||
|
||||
// next_embed = tok_embd[cb0] + sum cp_codec_embs[i-1, CB(i)] + tts_pad
|
||||
const double tc0 = now_s();
|
||||
std::vector<float> next_embed(n_embd, 0.0f);
|
||||
const float* e_cb0 = tok_embd.data() + (size_t)cb0 * n_embd;
|
||||
for (int d = 0; d < n_embd; ++d) next_embed[d] = e_cb0[d];
|
||||
|
|
@ -161,6 +205,7 @@ int main(int argc, char** argv) {
|
|||
for (int d = 0; d < n_embd; ++d) next_embed[d] += e[d];
|
||||
}
|
||||
for (int d = 0; d < n_embd; ++d) next_embed[d] += tts_pad[d];
|
||||
t_compose += now_s() - tc0;
|
||||
|
||||
// valid : next_embed (= input à injecter au decode step s) vs step_inputs_py[s]
|
||||
// (= ce que Python a injecté au decode step s). Si codes_engine[s] == codes_golden[s]
|
||||
|
|
@ -192,15 +237,32 @@ int main(int argc, char** argv) {
|
|||
llama_batch b{};
|
||||
b.n_tokens = 1; b.embd = next_embed.data();
|
||||
b.pos = pos1; b.n_seq_id = &n; b.seq_id = &sp; b.logits = &l;
|
||||
const double td0 = now_s();
|
||||
if (llama_decode(ctx, b) != 0) { printf("step %d FAILED\n", s); break; }
|
||||
t_decode += now_s() - td0;
|
||||
|
||||
// sample CB0 pour le prochain step
|
||||
// sample CB0 pour le prochain step + capture hidden pour CP
|
||||
logits = llama_get_logits_ith(ctx, -1);
|
||||
cb0 = 0; mx = logits[0];
|
||||
for (int i = 1; i < n_vocab; ++i) if (logits[i] > mx) { mx = logits[i]; cb0 = i; }
|
||||
const float* hh = llama_get_embeddings_ith(ctx, -1);
|
||||
if (hh) memcpy(hidden_for_cp.data(), hh, n_embd * sizeof(float));
|
||||
}
|
||||
const double t_loop = now_s() - t_loop0;
|
||||
|
||||
printf("DONE: N=%d, CB0 matches golden = %d/%d (info)\n", N, n_match_cb0, N);
|
||||
const double audio_s = N / 12.0; // 12 Hz codec
|
||||
printf("DONE: N=%d (audio %.2f s @ 12Hz)\n", N, audio_s);
|
||||
printf(" CB0 match golden : %d/%d\n", n_match_cb0, N);
|
||||
printf(" All 16 codes match : %d/%d (info, dépend du sampling Python)\n", n_match_full, N);
|
||||
printf("=== TIMING ===\n");
|
||||
printf(" Talker prefill (T=%d) : %.3f s\n", T_prefill, t_prefill);
|
||||
printf(" Loop (N=%d steps) : %.3f s talker_decode=%.3f, cp=%.3f, compose=%.3f, autres=%.3f\n",
|
||||
N, t_loop, t_decode, t_cp, t_compose, t_loop - t_decode - t_cp - t_compose);
|
||||
printf(" per-step talker : %.1f ms\n", t_decode * 1000.0 / N);
|
||||
printf(" per-step CP (15 pass) : %.1f ms\n", t_cp * 1000.0 / N);
|
||||
printf(" Total Talker+CP : %.3f s -> RTF talker+cp side = %.3f\n",
|
||||
t_prefill + t_loop, (t_prefill + t_loop) / audio_s);
|
||||
cp_free(cp_state);
|
||||
|
||||
// dump codes_engine
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue