char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
6e62ba538
char/aliceai-t5-moe
1#include "mtmd.h" 2#include "mtmd-helper.h" 3#include "mtmd-helper-common.h" 4#include "llama.h" 5#include "../src/llama-ext.h" 6 7#include <algorithm> 8#include <cctype> 9#include <cmath> 10#include <cstring> 11#include <memory> 12#include <string> 13#include <unordered_map> 14#include <vector> 15 16#ifdef MTMD_INTERNAL_HEADER 17#error "mtmd-helper is a public library outside of mtmd. it must not include internal headers" 18#endif 19 20// 21// Audio generation helpers 22// 23 24// --tts-lang codes -> language names used by the codec_language special tokens 25static const std::unordered_map < std::string , std::string > tts_lang_codes = { 26 {"zh" ,"chinese" }, 27 {"en" ,"english" }, 28 {"de" ,"german" }, 29 {"it" ,"italian" }, 30 {"pt" ,"portuguese" }, 31 {"es" ,"spanish" }, 32 {"ja" ,"japanese" }, 33 {"ko" ,"korean" }, 34 {"fr" ,"french" }, 35 {"ru" ,"russian" }, 36}; 37 38static std::string tts_resolve_lang (const std::string & lang ) { 39auto it = tts_lang_codes .find (lang ); 40return it != tts_lang_codes .end () ?it -> second :lang ; 41} 42 43static llama_token find_special_token (const llama_vocab * vocab ,const std::string & piece ) { 44const int32_t n = llama_vocab_n_tokens (vocab ); 45for (llama_token t = 0 ;t < n ;t ++ ) { 46if (piece == llama_vocab_get_text (vocab ,t )) { 47return t ; 48 } 49 } 50return LLAMA_TOKEN_NULL ; 51} 52 53static bool write_wav16 (std::vector < char > & buf ,const std::vector < float > & pcm ,int32_t rate ) { 54// RIFF chunk sizes are 32-bit; refuse to emit a file with a truncated header 55if (pcm .size ()> ((size_t )UINT32_MAX - 36 ) /2 ) { 56return false; 57 } 58const uint32_t data_sz = (uint32_t ) (pcm .size ()* 2 ); 59const uint32_t riff_sz = 36 + data_sz ; 60const uint32_t fmt_sz = 16 ,byte_rate = (uint32_t )rate * 2 ; 61const uint16_t fmt = 1 ,ch = 1 ,align = 2 ,bits = 16 ; 62const uint32_t rate32 = (uint32_t )rate ; 63auto put = [& ](const void * p ,size_t n ) { 64const char * c = (const char * )p ; 65buf .insert (buf .end (),c ,c + n ); 66 }; 67put ("RIFF" ,4 );put (& riff_sz ,4 );put ("WAVE" ,4 ); 68put ("fmt " ,4 );put (& fmt_sz ,4 ); 69put (& fmt ,2 );put (& ch ,2 );put (& rate32 ,4 ); 70put (& byte_rate ,4 );put (& align ,2 );put (& bits ,2 ); 71put ("data" ,4 );put (& data_sz ,4 ); 72for (float v :pcm ) { 73int16_t s = (int16_t ) (std::max (-1.0f , std::min (1.0f ,v ))* 32767.0f ); 74put (& s ,2 ); 75 } 76return true; 77} 78 79class mtmd_gen_audio_pipeline { 80public : 81mtmd_gen_audio_pipeline (llama_context * lctx ,mtmd_context * mctx ) 82 :lctx (lctx ),mctx (mctx ),model (llama_get_model (lctx )),vocab (llama_model_get_vocab (model )), 83n_embd (llama_model_n_embd (model )),info (mtmd_gen_audio_get_info (mctx )) {} 84virtual ~mtmd_gen_audio_pipeline ()= default ; 85 86virtual void reset ()= 0 ; 87virtual int32_t set_input (const mtmd_helper_gen_audio_inp * inp )= 0 ; 88// decodes at most n_batch prompt tokens; returns remaining count (0 = done), <0 on error 89virtual int32_t step_prompt (int32_t n_batch )= 0 ; 90// sampled can be LLAMA_TOKEN_NULL for pipelines with no discrete backbone token, 91// those read what they need from h_state_in instead 92// set out_stop on end-of-speech, h_state_out must be null if no frame is generated 93virtual int32_t step_gen (llama_token sampled ,const float * h_state_in ,const float ** h_state_out ,bool * out_stop )= 0 ; 94virtual int32_t get_output (int32_t * out_sample_rate ,const char ** out_data ,size_t * out_data_len ,int64_t * out_n_samples )= 0 ; 95 96protected : 97llama_context * lctx ; 98mtmd_context * mctx ; 99const llama_model * model ; 100const llama_vocab * vocab ; 101int n_embd ; 102mtmd_gen_audio_info info ; 103}; 104 105// Qwen3-TTS: backbone samples codec_0, code_predictor gives the other 15 codebooks, 106// then code2wav decodes them to PCM 107class qwen3tts_gen_audio_pipeline :public mtmd_gen_audio_pipeline { 108public : 109using mtmd_gen_audio_pipeline::mtmd_gen_audio_pipeline ; 110 111void reset ()override { 112seq_id = 0 ; 113pos = 0 ; 114codes_buf .clear (); 115c2w_state .clear (); 116audio_pcm .clear (); 117overlay .clear (); 118h_state_buf .clear (); 119out_buf .clear (); 120prompt_embd_buf .clear (); 121prompt_batch .reset (); 122n_prompt = 0 ; 123prompt_pos = 0 ; 124 } 125 126int32_t set_input (const mtmd_helper_gen_audio_inp * inp )override { 127reset (); 128seq_id = inp -> seq_id ; 129 130if (!ensure_cache ()) { 131return 1 ; 132 } 133 134const std::string lang = tts_resolve_lang ((inp -> lang && inp -> lang [0 ]) ?inp -> lang :"english" ); 135const llama_token c_lang = find_special_token (vocab , ("<|codec_language_" + lang + "|>" ).c_str ()); 136if (c_lang == LLAMA_TOKEN_NULL ) { 137LOG_ERR ("mtmd_helper_gen_audio: unknown language '%s'\n" ,lang .c_str ()); 138return 1 ; 139 } 140 141 std::vector < float > speaker_embd ; 142if (inp -> speaker_ref ) { 143if (!encode_speaker (inp -> speaker_ref ,speaker_embd )) { 144return 1 ; 145 } 146 } 147 148const int n_e = n_embd ; 149auto row = [& ](llama_token t ) { 150return std::vector < float > (tok_embd .begin ()+ (size_t )t * n_e , 151tok_embd .begin ()+ (size_t ) (t + 1 )* n_e ); 152 }; 153auto sum_row = [& ](llama_token a ,llama_token b ) { 154 std::vector < float > va = row (a ),vb = row (b ); 155for (int i = 0 ;i < n_e ;i ++ )va [(size_t )i ]+= vb [(size_t )i ]; 156return va ; 157 }; 158auto sum_vec = [& ](llama_token a ,const std::vector < float > & vb ) { 159 std::vector < float > va = row (a ); 160for (int i = 0 ;i < n_e ;i ++ )va [(size_t )i ]+= vb [(size_t )i ]; 161return va ; 162 }; 163 164// upstream chat wrap, then slices: [0:3] role, [3:-5] utterance body 165const std::string full = "<|im_start|>assistant\n" + std::string (inp -> prompt ,inp -> prompt_len )+ 166"<|im_end|>\n<|im_start|>assistant\n" ; 167 std::vector < llama_token > ids (full .size ()+ 16 ); 168int n_ids = llama_tokenize (vocab ,full .c_str (), (int32_t )full .size (),ids .data (), (int32_t )ids .size (), 169 false, true); 170if (n_ids < 8 ) { 171LOG_ERR ("mtmd_helper_gen_audio: tokenization failed\n" ); 172return 1 ; 173 } 174ids .resize ((size_t )n_ids ); 175 176 std::vector < std::vector < float >> prompt ; 177for (int i = 0 ;i < 3 ;i ++ )prompt .push_back (row (ids [(size_t )i ])); 178prompt .push_back (sum_row (tts_pad ,c_think )); 179prompt .push_back (sum_row (tts_pad ,c_think_b )); 180prompt .push_back (sum_row (tts_pad ,c_lang )); 181prompt .push_back (sum_row (tts_pad ,c_think_e )); 182if (!speaker_embd .empty ())prompt .push_back (sum_vec (tts_pad ,speaker_embd )); 183prompt .push_back (sum_row (tts_bos ,codec_pad )); 184for (int i = 3 ;i < n_ids - 5 ;i ++ )prompt .push_back (sum_row (ids [(size_t )i ],codec_pad )); 185prompt .push_back (sum_row (tts_eos ,codec_pad )); 186prompt .push_back (sum_row (tts_pad ,codec_bos )); 187 188n_prompt = (int )prompt .size (); 189 190// the talker uses the qwen3vl interleaved mrope, all sections are equal for a text/codec stream 191mrope = llama_model_rope_type (model )== LLAMA_ROPE_TYPE_MROPE || 192llama_model_rope_type (model )== LLAMA_ROPE_TYPE_IMROPE ; 193const int n_pos_per_embd = mrope ?4 :1 ; 194 195prompt_embd_buf .resize ((size_t )n_prompt * (size_t )n_e ); 196for (int i = 0 ;i < n_prompt ;i ++ ) { 197memcpy (prompt_embd_buf .data ()+ (size_t )i * n_e ,prompt [(size_t )i ].data (), (size_t )n_e * sizeof (float )); 198 } 199 200prompt_batch .reset (new decode_embd_batch (prompt_embd_buf .data (),n_prompt ,n_pos_per_embd ,n_e )); 201if (mrope )prompt_batch -> set_position_mrope_1d (0 ,seq_id ); 202else prompt_batch -> set_position_normal (0 ,seq_id ); 203prompt_pos = 0 ; 204 205pos = 0 ; 206const mtmd_gen_inp def = mtmd_gen_inp_default (mctx ); 207top_k = inp -> top_k > 0 ?inp -> top_k :def .top_k ; 208top_p = inp -> top_p > 0 ?inp -> top_p :def .top_p ; 209seed = inp -> seed ; 210out_type = inp -> out_type ; 211 212// the prompt above holds the whole text stream up to tts_eos, so every generated 213// frame adds tts_pad on top of the codes embedding 214overlay = row (tts_pad ); 215 216return 0 ; 217 } 218 219int32_t step_prompt (int32_t n_batch )override { 220GGML_ASSERT (n_batch > 0 ); 221if (prompt_pos >=n_prompt ) { 222return 0 ; 223 } 224const int32_t n_tokens_batch = std::min (n_batch ,n_prompt - prompt_pos ); 225llama_batch batch_view = prompt_batch -> get_view (prompt_pos ,n_tokens_batch ); 226 227const bool is_last_batch = (prompt_pos + n_tokens_batch )== n_prompt ; 228if (is_last_batch ) { 229batch_view .logits [n_tokens_batch - 1 ]= 1 ; 230 } 231 232if (llama_decode (lctx ,batch_view )!= 0 ) { 233LOG_ERR ("mtmd_helper_gen_audio: prompt decode failed\n" ); 234return -1 ; 235 } 236 237pos += n_tokens_batch ; 238prompt_pos += n_tokens_batch ; 239 240if (prompt_pos >=n_prompt ) { 241// prompt fully processed, its embedding buffer is no longer needed 242prompt_batch .reset (); 243prompt_embd_buf .clear (); 244return 0 ; 245 } 246return n_prompt - prompt_pos ; 247 } 248 249int32_t step_gen (llama_token sampled ,const float * h_state_in ,const float ** h_state_out ,bool * out_stop )override { 250if (sampled == LLAMA_TOKEN_NULL ) { 251LOG_ERR ("mtmd_helper_gen_audio: qwen3tts requires a token sampled from the backbone\n" ); 252return 1 ; 253 } 254 255// backbone signals end-of-speech with a token, no frame for this step 256if (sampled == codec_eos || llama_vocab_is_eog (vocab ,sampled )) { 257* out_stop = true; 258* h_state_out = nullptr ; 259return 0 ; 260 } 261 262mtmd_gen_inp inp = mtmd_gen_inp_default (mctx ); 263inp .type = MTMD_GEN_PROCESS_TYPE_GEN_CODE ; 264inp .code0 = sampled - codec_0 ; 265inp .embd = const_cast < float *> (h_state_in ); 266inp .top_k = top_k ; 267inp .top_p = top_p ; 268inp .seed = seed ; 269mtmd_gen_out out {}; 270if (mtmd_gen_audio_process (mctx ,& inp ,& out )!= 0 ) { 271LOG_ERR ("mtmd_helper_gen_audio: gen_code process failed\n" ); 272return 1 ; 273 } 274 275codes_buf .insert (codes_buf .end (),out .codes ,out .codes + out .n_codes ); 276if (out .n_codes > 0 && codes_buf .size () /out .n_codes >=window_frames ) { 277if (!flush_gen_wav ()) { 278return 1 ; 279 } 280 } 281 282 std::vector < float > fb (out .embd ,out .embd + n_embd ); 283for (int i = 0 ;i < n_embd ;i ++ )fb [(size_t )i ]+= overlay [(size_t )i ]; 284 285const int n_pos_per_embd = mrope ?4 :1 ; 286decode_embd_batch batch_embd (fb .data (),1 ,n_pos_per_embd ,n_embd ); 287if (mrope )batch_embd .set_position_mrope_1d (pos ,seq_id ); 288else batch_embd .set_position_normal (pos ,seq_id ); 289batch_embd .batch .logits [0 ]= 1 ; 290pos ++ ; 291 292if (llama_decode (lctx ,batch_embd .batch )!= 0 ) { 293LOG_ERR ("mtmd_helper_gen_audio: decode failed\n" ); 294return 1 ; 295 } 296 297const float * he = llama_get_embeddings_ith (lctx ,-1 ); 298h_state_buf .assign (he ,he + n_embd ); 299* h_state_out = h_state_buf .data (); 300 301return 0 ; 302 } 303 304int32_t get_output (int32_t * out_sample_rate ,const char ** out_data ,size_t * out_data_len ,int64_t * out_n_samples )override { 305if (!flush_gen_wav ()) { 306return 1 ; 307 } 308 309* out_sample_rate = info .sample_rate ; 310if (out_n_samples ) { 311* out_n_samples = (int64_t )audio_pcm .size (); 312 } 313 314if (out_type == MTMD_HELPER_GEN_AUDIO_OUTTYPE_PCM ) { 315* out_data = (const char * )audio_pcm .data (); 316* out_data_len = audio_pcm .size ()* sizeof (float ); 317return 0 ; 318 } 319 320out_buf .clear (); 321if (!write_wav16 (out_buf ,audio_pcm ,info .sample_rate )) { 322LOG_ERR ("mtmd_helper_gen_audio: output too large for WAV\n" ); 323return 1 ; 324 } 325* out_data = out_buf .data (); 326* out_data_len = out_buf .size (); 327return 0 ; 328 } 329 330private : 331bool ensure_cache () { 332if (specials_ok ) { 333return true; 334 } 335codec_0 = find_special_token (vocab ,"<|codec_0|>" ); 336codec_bos = find_special_token (vocab ,"<|codec_bos|>" ); 337codec_eos = find_special_token (vocab ,"<|codec_eos_token|>" ); 338codec_pad = find_special_token (vocab ,"<|codec_pad|>" ); 339c_think = find_special_token (vocab ,"<|codec_think|>" ); 340c_think_b = find_special_token (vocab ,"<|codec_think_bos|>" ); 341c_think_e = find_special_token (vocab ,"<|codec_think_eos|>" ); 342tts_pad = find_special_token (vocab ,"<tts_pad>" ); 343tts_bos = find_special_token (vocab ,"<tts_text_bos>" ); 344tts_eos = find_special_token (vocab ,"<tts_text_eod>" ); 345for (llama_token t : {codec_0 ,codec_bos ,codec_eos ,codec_pad , 346c_think ,c_think_b ,c_think_e , 347tts_pad ,tts_bos ,tts_eos }) { 348if (t == LLAMA_TOKEN_NULL ) { 349LOG_ERR ("mtmd_helper_gen_audio: missing a required special token in vocab\n" ); 350return false; 351 } 352 } 353const uint32_t n_tok_embd = llama_model_get_tok_embd (model ,nullptr ); 354if (n_tok_embd == 0 ) { 355LOG_ERR ("mtmd_helper_gen_audio: model has no token embeddings\n" ); 356return false; 357 } 358tok_embd .resize (n_tok_embd ); 359if (llama_model_get_tok_embd (model ,tok_embd .data ())!= n_tok_embd ) { 360LOG_ERR ("mtmd_helper_gen_audio: token embedding copy failed\n" ); 361return false; 362 } 363specials_ok = true; 364return true; 365 } 366 367// runs the reference wav through the speaker encoder, returns one x-vector embedding row 368bool encode_speaker (mtmd_bitmap * bitmap , std::vector < float > & out ) { 369if (!mtmd_support_audio (mctx )) { 370LOG_ERR ("mtmd_helper_gen_audio: mmproj has no speaker/audio encoder\n" ); 371return false; 372 } 373const std::string marker = mtmd_default_marker (); 374mtmd_input_text text {marker .c_str (),marker .size (), false, true }; 375mtmd_input_chunks * chunks = mtmd_input_chunks_init (); 376const mtmd_bitmap * bptr = bitmap ; 377bool ok = mtmd_tokenize (mctx ,chunks ,& text ,& bptr ,1 )== 0 ; 378if (ok ) { 379ok = false; 380for (size_t i = 0 ;i < mtmd_input_chunks_size (chunks );i ++ ) { 381const mtmd_input_chunk * chunk = mtmd_input_chunks_get (chunks ,i ); 382if (mtmd_input_chunk_get_type (chunk )!= MTMD_INPUT_CHUNK_TYPE_AUDIO ) { 383continue ; 384 } 385if (mtmd_encode_chunk (mctx ,chunk )!= 0 ) { 386LOG_ERR ("mtmd_helper_gen_audio: speaker encode failed\n" ); 387break ; 388 } 389const float * embd = mtmd_get_output_embd (mctx ); 390const size_t n = (size_t )llama_model_n_embd_inp (model )* mtmd_input_chunk_get_n_tokens (chunk ); 391out .assign (embd ,embd + n ); 392ok = true; 393break ; 394 } 395 } 396mtmd_input_chunks_free (chunks ); 397return ok ; 398 } 399 400// one GEN_WAV process() call over the buffered codes, state is carried across batches 401bool flush_gen_wav () { 402if (codes_buf .empty ()) { 403return true; 404 } 405mtmd_gen_inp inp = mtmd_gen_inp_default (mctx ); 406inp .type = MTMD_GEN_PROCESS_TYPE_GEN_WAV ; 407inp .codes = codes_buf .data (); 408inp .n_codes = codes_buf .size (); 409inp .seed = seed ;// same seed as gen_code, else clip reseeds mid-generation 410inp .state_data = c2w_state .empty () ?nullptr : (const char * )c2w_state .data (); 411inp .state_size = c2w_state .size (); 412mtmd_gen_out out {}; 413if (mtmd_gen_audio_process (mctx ,& inp ,& out )!= 0 ) { 414LOG_ERR ("mtmd_helper_gen_audio: gen_wav process failed\n" ); 415return false; 416 } 417audio_pcm .insert (audio_pcm .end (),out .audio ,out .audio + out .n_samples ); 418c2w_state .assign (out .state_data ,out .state_data + out .state_size ); 419codes_buf .clear (); 420return true; 421 } 422 423// vocab specials fixed across the whole session, looked up once 424bool specials_ok = false; 425llama_token codec_0 = LLAMA_TOKEN_NULL ; 426llama_token codec_bos = LLAMA_TOKEN_NULL ; 427llama_token codec_eos = LLAMA_TOKEN_NULL ; 428llama_token codec_pad = LLAMA_TOKEN_NULL ; 429llama_token c_think = LLAMA_TOKEN_NULL ; 430llama_token c_think_b = LLAMA_TOKEN_NULL ; 431llama_token c_think_e = LLAMA_TOKEN_NULL ; 432llama_token tts_pad = LLAMA_TOKEN_NULL ; 433llama_token tts_bos = LLAMA_TOKEN_NULL ; 434llama_token tts_eos = LLAMA_TOKEN_NULL ; 435 std::vector < float > tok_embd ;// whole token embedding matrix, n_vocab * n_embd 436 437// must match hparams.wav_tfm_swa hardcoded in clip.cpp 438size_t window_frames = 72 ; 439 440// per-generation state, cleared by reset() 441llama_seq_id seq_id = 0 ; 442bool mrope = false; 443int pos = 0 ; 444// prompt decode state, consumed batch-by-batch by step_prompt() 445 std::vector < float > prompt_embd_buf ; 446 std::unique_ptr < decode_embd_batch > prompt_batch ; 447int n_prompt = 0 ; 448int prompt_pos = 0 ; 449int32_t top_k = 50 ; 450float top_p = 1.0f ; 451uint32_t seed = UINT32_MAX ; 452 std::vector < int32_t > codes_buf ; 453 std::vector < uint8_t > c2w_state ; 454 std::vector < float > audio_pcm ; 455 std::vector < float > overlay ; 456 std::vector < float > h_state_buf ; 457mtmd_helper_gen_audio_outtype out_type = MTMD_HELPER_GEN_AUDIO_OUTTYPE_WAV ; 458 std::vector < char > out_buf ; 459}; 460 461// settings that only live in the reference's per-pack yaml, not in the checkpoint 462// the english packs share the same shapes and tokenizer, but disagree on these 463// all three are 0 / false when the pack does not tune them, the model default is then used 464struct pockettts_pack_settings { 465float temp = 0.0f ; 466int frames_after_eos = 0 ; 467bool pad_short_text = false; 468}; 469 470static pockettts_pack_settings pockettts_pack (const char * variant ) { 471static const std::unordered_map < std::string ,pockettts_pack_settings > packs = { 472 {"english" , {0.3f ,0 , false } }, 473 {"english_2026-01" , {0.7f ,0 , true } }, 474 {"english_2026-04" , {0.3f ,0 , false } }, 475 {"french_24l" , {0.7f ,8 , false } }, 476 }; 477auto it = packs .find (variant ?variant :"" ); 478if (it == packs .end ()) { 479LOG_WRN ("mtmd_helper_gen_audio: no tuned settings for pocket-tts variant \"%s\"\n" , 480variant ?variant :"" ); 481return {}; 482 } 483return it -> second ; 484} 485 486// pocket-tts: the backbone emits no token, the flow net turns each hidden state into a latent 487// the end-of-speech head also lives in the mmproj 488class pockettts_gen_audio_pipeline :public mtmd_gen_audio_pipeline { 489public : 490using mtmd_gen_audio_pipeline::mtmd_gen_audio_pipeline ; 491 492void reset ()override { 493seq_id = 0 ; 494pos = 0 ; 495feats_buf .clear (); 496dec_state .clear (); 497audio_pcm .clear (); 498h_state_buf .clear (); 499out_buf .clear (); 500prompt_embd_buf .clear (); 501prompt_batch .reset (); 502n_prompt = 0 ; 503prompt_pos = 0 ; 504step_idx = 0 ; 505eos_step = -1 ; 506chunks .clear (); 507chunk_idx = 0 ; 508n_voice_pos = 0 ; 509chunk_budget = 0 ; 510 } 511 512int32_t set_input (const mtmd_helper_gen_audio_inp * inp )override { 513reset (); 514seq_id = inp -> seq_id ; 515 516if (!ensure_cache ()) { 517return 1 ; 518 } 519 520 std::vector < float > voice ; 521if (inp -> speaker_ref ) { 522if (!encode_speaker (inp -> speaker_ref ,voice )) { 523return 1 ; 524 } 525 } 526 527pack = pockettts_pack (info .model_variant ); 528 529const std::string text = prepare_text (std::string (inp -> prompt ,inp -> prompt_len ), 530pack .pad_short_text ); 531if (text .empty ()) { 532LOG_ERR ("mtmd_helper_gen_audio: empty prompt\n" ); 533return 1 ; 534 } 535 536 std::vector < llama_token > ids (text .size ()+ 16 ); 537int n_ids = llama_tokenize (vocab ,text .c_str (), (int32_t )text .size (),ids .data (), 538 (int32_t )ids .size (), false, false); 539if (n_ids <=0 ) { 540LOG_ERR ("mtmd_helper_gen_audio: tokenization failed\n" ); 541return 1 ; 542 } 543ids .resize ((size_t )n_ids ); 544 545// long inputs degrade badly, so each chunk restarts from the voice conditioning 546// see split_into_best_sentences() in the reference 547chunks = split_chunks (ids ); 548chunk_idx = 0 ; 549if (chunks .size ()> 1 ) { 550LOG_INF ("mtmd_helper_gen_audio: %d tokens split into %zu chunks\n" ,n_ids ,chunks .size ()); 551 } 552 553const int n_e = n_embd ; 554 555// sequence order is voice, then text, then the audio BOS that starts generation 556if (!voice .empty ()) { 557GGML_ASSERT (voice .size () % (size_t )n_e == 0 ); 558if (bos_before_voice != LLAMA_TOKEN_NULL ) { 559push_embd_row (prompt_embd_buf ,bos_before_voice ); 560 } 561prompt_embd_buf .insert (prompt_embd_buf .end (),voice .begin (),voice .end ()); 562 } 563// every later chunk rewinds to here and re-prompts, so the voice stays primed 564n_voice_pos = (int ) (prompt_embd_buf .size () / (size_t )n_e ); 565 566for (llama_token t :chunks [0 ]) { 567push_embd_row (prompt_embd_buf ,t ); 568 } 569push_embd_row (prompt_embd_buf ,audio_bos ); 570arm_chunk_budget (0 ); 571 572n_prompt = (int ) (prompt_embd_buf .size () / (size_t )n_e ); 573prompt_batch .reset (new decode_embd_batch (prompt_embd_buf .data (),n_prompt ,1 ,n_e )); 574prompt_batch -> set_position_normal (0 ,seq_id ); 575prompt_pos = 0 ; 576 577seed = inp -> seed ; 578out_type = inp -> out_type ; 579 580return 0 ; 581 } 582 583int32_t step_prompt (int32_t n_batch )override { 584GGML_ASSERT (n_batch > 0 ); 585if (prompt_pos >=n_prompt ) { 586return 0 ; 587 } 588const int32_t n_tokens_batch = std::min (n_batch ,n_prompt - prompt_pos ); 589llama_batch batch_view = prompt_batch -> get_view (prompt_pos ,n_tokens_batch ); 590 591if ((prompt_pos + n_tokens_batch )== n_prompt ) { 592batch_view .logits [n_tokens_batch - 1 ]= 1 ; 593 } 594 595if (llama_decode (lctx ,batch_view )!= 0 ) { 596LOG_ERR ("mtmd_helper_gen_audio: prompt decode failed\n" ); 597return -1 ; 598 } 599 600pos += n_tokens_batch ; 601prompt_pos += n_tokens_batch ; 602 603if (prompt_pos >=n_prompt ) { 604prompt_batch .reset (); 605prompt_embd_buf .clear (); 606return 0 ; 607 } 608return n_prompt - prompt_pos ; 609 } 610 611int32_t step_gen (llama_token sampled ,const float * h_state_in ,const float ** h_state_out ,bool * out_stop )override { 612 (void )sampled ;// the backbone output is continuous, there is no token to consume 613 614mtmd_gen_inp inp = mtmd_gen_inp_default (mctx ); 615inp .type = MTMD_GEN_PROCESS_TYPE_GEN_CODE ; 616inp .embd = const_cast < float *> (h_state_in ); 617// clip only reseeds when the seed changes, so pass the same one on every step 618inp .seed = seed ; 619if (pack .temp > 0.0f ) { 620inp .temp = pack .temp ; 621 } 622mtmd_gen_out out {}; 623if (mtmd_gen_audio_process (mctx ,& inp ,& out )!= 0 ) { 624LOG_ERR ("mtmd_helper_gen_audio: flow decode failed\n" ); 625return 1 ; 626 } 627if (out .is_eos && eos_step < 0 ) { 628eos_step = step_idx ; 629 } 630// the frame of the stopping step is discarded, matching _autoregressive_generation(). 631// the budget is the reference's fallback for a chunk whose eos head never fires 632const bool chunk_done = (eos_step >=0 && step_idx >=eos_step + frames_after_eos )|| 633step_idx >=chunk_budget ; 634if (chunk_done ) { 635if (eos_step < 0 ) { 636LOG_WRN ("mtmd_helper_gen_audio: chunk %zu hit its budget without end-of-speech\n" ,chunk_idx ); 637 } 638return finish_chunk (h_state_out ,out_stop ); 639 } 640 641feats_buf .insert (feats_buf .end (),out .feats ,out .feats + out .n_feats ); 642step_idx ++ ; 643if (out .n_feats > 0 && feats_buf .size () /out .n_feats >=window_frames ) { 644if (!flush_gen_wav ()) { 645return 1 ; 646 } 647 } 648 649decode_embd_batch batch_embd (const_cast < float *> (out .embd ),1 ,1 ,n_embd ); 650batch_embd .set_position_normal (pos ,seq_id ); 651batch_embd .batch .logits [0 ]= 1 ; 652pos ++ ; 653 654if (llama_decode (lctx ,batch_embd .batch )!= 0 ) { 655LOG_ERR ("mtmd_helper_gen_audio: decode failed\n" ); 656return 1 ; 657 } 658 659const float * he = llama_get_embeddings_ith (lctx ,-1 ); 660h_state_buf .assign (he ,he + n_embd ); 661* h_state_out = h_state_buf .data (); 662 663return 0 ; 664 } 665 666int32_t get_output (int32_t * out_sample_rate ,const char ** out_data ,size_t * out_data_len ,int64_t * out_n_samples )override { 667if (!flush_gen_wav ()) { 668return 1 ; 669 } 670 671* out_sample_rate = info .sample_rate ; 672if (out_n_samples ) { 673* out_n_samples = (int64_t )audio_pcm .size (); 674 } 675 676if (out_type == MTMD_HELPER_GEN_AUDIO_OUTTYPE_PCM ) { 677* out_data = (const char * )audio_pcm .data (); 678* out_data_len = audio_pcm .size ()* sizeof (float ); 679return 0 ; 680 } 681 682out_buf .clear (); 683if (!write_wav16 (out_buf ,audio_pcm ,info .sample_rate )) { 684LOG_ERR ("mtmd_helper_gen_audio: output too large for WAV\n" ); 685return 1 ; 686 } 687* out_data = out_buf .data (); 688* out_data_len = out_buf .size (); 689return 0 ; 690 } 691 692private : 693bool ensure_cache () { 694if (specials_ok ) { 695return true; 696 } 697// bos_before_voice is optional, some packs do not insert it 698bos_before_voice = find_special_token (vocab ,"<|bos_before_voice|>" ); 699audio_bos = find_special_token (vocab ,"<|audio_bos|>" ); 700if (audio_bos == LLAMA_TOKEN_NULL ) { 701LOG_ERR ("mtmd_helper_gen_audio: missing <|audio_bos|> in vocab\n" ); 702return false; 703 } 704const uint32_t n_tok_embd = llama_model_get_tok_embd (model ,nullptr ); 705if (n_tok_embd == 0 ) { 706LOG_ERR ("mtmd_helper_gen_audio: model has no token embeddings\n" ); 707return false; 708 } 709tok_embd .resize (n_tok_embd ); 710if (llama_model_get_tok_embd (model ,tok_embd .data ())!= n_tok_embd ) { 711LOG_ERR ("mtmd_helper_gen_audio: token embedding copy failed\n" ); 712return false; 713 } 714GGML_ASSERT (n_embd > 0 && n_tok_embd % (uint32_t )n_embd == 0 ); 715specials_ok = true; 716return true; 717 } 718 719// the table can be shorter than the vocab, so bound the row lookup 720void push_embd_row (std::vector < float > & dst ,llama_token t )const { 721const size_t n_rows = tok_embd .size () / (size_t )n_embd ; 722GGML_ASSERT (t >=0 && (size_t )t < n_rows ); 723dst .insert (dst .end (), 724tok_embd .begin ()+ (size_t )t * n_embd , 725tok_embd .begin ()+ (size_t ) (t + 1 )* n_embd ); 726 } 727 728// token ids of the pieces the reference splits on, see split_into_best_sentences(). 729// the leading token is dropped, it is the tokenizer's dummy prefix 730 std::vector < llama_token > punct_ids (const char * s )const { 731 std::vector < llama_token > ids (16 ); 732const int n = llama_tokenize (vocab ,s , (int32_t )strlen (s ),ids .data (), (int32_t )ids .size (), false, false); 733if (n <=1 ) { 734return {}; 735 } 736return std::vector < llama_token > (ids .begin ()+ 1 ,ids .begin ()+ n ); 737 } 738 739// cut after runs of boundary tokens, so punctuation stays with the sentence it ends 740static std::vector < std::vector < llama_token >> split_on (const std::vector < llama_token > & ids , 741const std::vector < llama_token > & boundary ) { 742 std::vector < std::vector < llama_token >> out ; 743size_t start = 0 ; 744bool prev_was_boundary = false; 745for (size_t i = 0 ;i < ids .size ();i ++ ) { 746const bool is_boundary = std::find (boundary .begin (),boundary .end (),ids [i ])!= boundary .end (); 747if (!is_boundary && prev_was_boundary ) { 748out .emplace_back (ids .begin ()+ start ,ids .begin ()+ i ); 749start = i ; 750 } 751prev_was_boundary = is_boundary ; 752 } 753out .emplace_back (ids .begin ()+ start ,ids .end ()); 754return out ; 755 } 756 757 std::vector < std::vector < llama_token >> split_chunks (const std::vector < llama_token > & ids )const { 758if ((int )ids .size () <=max_chunk_tokens ) { 759return {ids }; 760 } 761const std::vector < llama_token > eos_punct = punct_ids (".!...?" ); 762const std::vector < llama_token > mid_punct = punct_ids (",;:" ); 763 764// oversized sentences are split again on weaker punctuation, else words get skipped 765 std::vector < std::vector < llama_token >> segments ; 766for (auto & seg :split_on (ids ,eos_punct )) { 767if ((int )seg .size () <=max_chunk_tokens ) { 768segments .push_back (std::move (seg )); 769continue ; 770 } 771auto sub = split_on (seg ,mid_punct ); 772if (sub .size ()> 1 ) { 773for (auto & s :sub ) { 774segments .push_back (std::move (s )); 775 } 776 }else { 777segments .push_back (std::move (seg )); 778 } 779 } 780 781 std::vector < std::vector < llama_token >> out ; 782for (auto & seg :segments ) { 783if (seg .empty ()) { 784continue ; 785 } 786if (!out .empty ()&& (int ) (out .back ().size ()+ seg .size ()) <=max_chunk_tokens ) { 787out .back ().insert (out .back ().end (),seg .begin (),seg .end ()); 788 }else { 789out .push_back (std::move (seg )); 790 } 791 } 792if (out .empty ()) { 793out .push_back (ids ); 794 } 795for (const auto & c :out ) { 796if ((int )c .size ()> max_chunk_tokens ) { 797LOG_WRN ("mtmd_helper_gen_audio: chunk of %zu tokens exceeds the %d token budget, " 798"generation may skip words\n" ,c .size (),max_chunk_tokens ); 799 } 800 } 801return out ; 802 } 803 804// _estimate_max_gen_len() plus the per-chunk tail guess, both in frames 805void arm_chunk_budget (size_t idx ) { 806const int n_tok = (int )chunks [idx ].size (); 807chunk_budget = (int ) std::ceil ((n_tok /3.0 + 2.0 )* frame_rate ); 808// the pack may pin the tail, else the reference guesses it from the word count 809frames_after_eos = pack .frames_after_eos > 0 ?pack .frames_after_eos : (n_tok <=6 ?5 :3 ); 810step_idx = 0 ; 811eos_step = -1 ; 812 } 813 814// ends the current chunk and, if there is another, re-prompts it on top of the voice 815int32_t finish_chunk (const float ** h_state_out ,bool * out_stop ) { 816if (!flush_gen_wav ()) { 817return 1 ; 818 } 819// the decoder restarts too, the next chunk's audio is not continuous with this one 820dec_state .clear (); 821 822if (chunk_idx + 1 >=chunks .size ()) { 823* out_stop = true; 824* h_state_out = nullptr ; 825return 0 ; 826 } 827chunk_idx ++ ; 828 829// drop this chunk's text and audio, keep the voice conditioning 830llama_memory_seq_rm (llama_get_memory (lctx ),seq_id ,n_voice_pos ,-1 ); 831pos = n_voice_pos ; 832 833const int n_e = n_embd ; 834prompt_embd_buf .clear (); 835for (llama_token t :chunks [chunk_idx ]) { 836push_embd_row (prompt_embd_buf ,t ); 837 } 838push_embd_row (prompt_embd_buf ,audio_bos ); 839arm_chunk_budget (chunk_idx ); 840 841const int n_rows = (int ) (prompt_embd_buf .size () / (size_t )n_e ); 842GGML_ASSERT (n_rows > 0 ); 843decode_embd_batch batch (prompt_embd_buf .data (),n_rows ,1 ,n_e ); 844batch .set_position_normal (pos ,seq_id ); 845batch .batch .logits [n_rows - 1 ]= 1 ; 846if (llama_decode (lctx ,batch .batch )!= 0 ) { 847LOG_ERR ("mtmd_helper_gen_audio: chunk prompt decode failed\n" ); 848return 1 ; 849 } 850pos += n_rows ; 851prompt_embd_buf .clear (); 852 853const float * he = llama_get_embeddings_ith (lctx ,-1 ); 854h_state_buf .assign (he ,he + n_embd ); 855* h_state_out = h_state_buf .data (); 856* out_stop = false; 857return 0 ; 858 } 859 860// same normalization as prepare_text_prompt() in the reference, it affects quality 861static std::string prepare_text (const std::string & in ,bool pad_short ) { 862 std::string s ; 863s .reserve (in .size ()+ 1 ); 864for (char c :in ) { 865if (c == '\n' || c == '\r' ) { 866s += ' ' ; 867 }else if (c == ';' ) { 868s += ',' ; 869 }else { 870s += c ; 871 } 872 } 873const size_t b = s .find_first_not_of (' ' ); 874const size_t e = s .find_last_not_of (' ' ); 875if (b == std::string::npos ) { 876return "" ; 877 } 878s = s .substr (b ,e - b + 1 ); 879if (s [0 ] >='a' && s [0 ] <='z' ) { 880s [0 ]= (char ) (s [0 ]- 'a' + 'A' ); 881 } 882const unsigned char last = (unsigned char )s .back (); 883if (std::isalnum (last )) { 884s += '.' ; 885 } 886if (pad_short && count_words (s )< 5 ) { 887s = std::string (8 ,' ' )+ s ; 888 } 889return s ; 890 } 891 892static int count_words (const std::string & s ) { 893int n = 0 ; 894bool in_word = false; 895for (char c :s ) { 896if (c == ' ' ) { 897in_word = false; 898 }else if (!in_word ) { 899in_word = true; 900n ++ ; 901 } 902 } 903return n ; 904 } 905 906// runs the reference wav through the mimi encoder, returns one row per 12.5Hz frame 907bool encode_speaker (mtmd_bitmap * bitmap , std::vector < float > & out ) { 908if (!mtmd_support_audio (mctx )) { 909LOG_ERR ("mtmd_helper_gen_audio: mmproj has no voice encoder\n" ); 910return false; 911 } 912const std::string marker = mtmd_default_marker (); 913mtmd_input_text text {marker .c_str (),marker .size (), false, true }; 914mtmd_input_chunks * chunks = mtmd_input_chunks_init (); 915const mtmd_bitmap * bptr = bitmap ; 916bool ok = mtmd_tokenize (mctx ,chunks ,& text ,& bptr ,1 )== 0 ; 917if (ok ) { 918ok = false; 919for (size_t i = 0 ;i < mtmd_input_chunks_size (chunks );i ++ ) { 920const mtmd_input_chunk * chunk = mtmd_input_chunks_get (chunks ,i ); 921if (mtmd_input_chunk_get_type (chunk )!= MTMD_INPUT_CHUNK_TYPE_AUDIO ) { 922continue ; 923 } 924if (mtmd_encode_chunk (mctx ,chunk )!= 0 ) { 925LOG_ERR ("mtmd_helper_gen_audio: voice encode failed\n" ); 926break ; 927 } 928const float * embd = mtmd_get_output_embd (mctx ); 929const size_t n = (size_t )llama_model_n_embd_inp (model )* mtmd_input_chunk_get_n_tokens (chunk ); 930out .assign (embd ,embd + n ); 931ok = true; 932break ; 933 } 934 } 935mtmd_input_chunks_free (chunks ); 936return ok ; 937 } 938 939// decodes the buffered latents, the mimi decoder state carries over between calls 940bool flush_gen_wav () { 941if (feats_buf .empty ()) { 942return true; 943 } 944mtmd_gen_inp inp = mtmd_gen_inp_default (mctx ); 945inp .type = MTMD_GEN_PROCESS_TYPE_GEN_WAV ; 946inp .feats = feats_buf .data (); 947inp .n_feats = feats_buf .size (); 948inp .seed = seed ; 949inp .state_data = dec_state .empty () ?nullptr : (const char * )dec_state .data (); 950inp .state_size = dec_state .size (); 951mtmd_gen_out out {}; 952if (mtmd_gen_audio_process (mctx ,& inp ,& out )!= 0 ) { 953LOG_ERR ("mtmd_helper_gen_audio: mimi decode failed\n" ); 954return false; 955 } 956audio_pcm .insert (audio_pcm .end (),out .audio ,out .audio + out .n_samples ); 957dec_state .assign (out .state_data ,out .state_data + out .state_size ); 958feats_buf .clear (); 959return true; 960 } 961 962pockettts_pack_settings pack ; 963bool specials_ok = false; 964llama_token bos_before_voice = LLAMA_TOKEN_NULL ; 965llama_token audio_bos = LLAMA_TOKEN_NULL ; 966 std::vector < float > tok_embd ; 967 968llama_seq_id seq_id = 0 ; 969int pos = 0 ; 970 std::vector < float > prompt_embd_buf ; 971 std::unique_ptr < decode_embd_batch > prompt_batch ; 972int n_prompt = 0 ; 973int prompt_pos = 0 ; 974uint32_t seed = UINT32_MAX ; 975// end-of-speech is latched, then a few more frames are generated as tail padding 976int step_idx = 0 ; 977int eos_step = -1 ; 978int frames_after_eos = 3 ; 979static constexpr int max_chunk_tokens = 50 ;// MAX_TOKEN_PER_CHUNK in the reference 980static constexpr double frame_rate = 12.5 ; 981 std::vector < std::vector < llama_token >> chunks ; 982size_t chunk_idx = 0 ; 983int n_voice_pos = 0 ;// KV positions held by the voice conditioning 984int chunk_budget = 0 ; 985 986// latents are decoded a window at a time, the decoder state bridges the windows 987size_t window_frames = 8 ; 988 std::vector < float > feats_buf ; 989 std::vector < uint8_t > dec_state ; 990 std::vector < float > audio_pcm ; 991 std::vector < float > h_state_buf ; 992mtmd_helper_gen_audio_outtype out_type = MTMD_HELPER_GEN_AUDIO_OUTTYPE_WAV ; 993 std::vector < char > out_buf ; 994}; 995 996static std::unique_ptr < mtmd_gen_audio_pipeline > make_pipeline (llama_context * lctx ,mtmd_context * mctx ) { 997switch (mtmd_gen_audio_get_info (mctx ).type ) { 998case MTMD_GEN_AUDIO_TYPE_QWEN3TTS : 999return std::unique_ptr < mtmd_gen_audio_pipeline > (new qwen3tts_gen_audio_pipeline (lctx ,mctx )); 1000case MTMD_GEN_AUDIO_TYPE_POCKETTTS : 1001return std::unique_ptr < mtmd_gen_audio_pipeline > (new pockettts_gen_audio_pipeline (lctx ,mctx )); 1002default : 1003return nullptr ; 1004 } 1005} 1006 1007struct mtmd_helper_gen_audio { 1008 std::unique_ptr < mtmd_gen_audio_pipeline > pipeline ; 1009}; 1010 1011mtmd_helper_gen_audio * mtmd_helper_gen_audio_init (struct llama_context * lctx ,struct mtmd_context * mctx ) { 1012auto * ctx = new mtmd_helper_gen_audio (); 1013ctx -> pipeline = make_pipeline (lctx ,mctx ); 1014return ctx ; 1015} 1016 1017void mtmd_helper_gen_audio_free (mtmd_helper_gen_audio * ctx ) { 1018delete ctx ; 1019} 1020 1021void mtmd_helper_gen_audio_reset (mtmd_helper_gen_audio * ctx ) { 1022if (ctx -> pipeline ) { 1023ctx -> pipeline -> reset (); 1024 } 1025} 1026 1027int32_t mtmd_helper_gen_audio_set_input (mtmd_helper_gen_audio * ctx ,const mtmd_helper_gen_audio_inp * inp ) { 1028if (!ctx -> pipeline ) { 1029LOG_ERR ("mtmd_helper_gen_audio: unsupported or missing gen-audio pipeline\n" ); 1030return 1 ; 1031 } 1032return ctx -> pipeline -> set_input (inp ); 1033} 1034 1035int32_t mtmd_helper_gen_audio_step_prompt (mtmd_helper_gen_audio * ctx ,int32_t n_batch ) { 1036if (!ctx -> pipeline ) { 1037return -1 ; 1038 } 1039return ctx -> pipeline -> step_prompt (n_batch ); 1040} 1041 1042int32_t mtmd_helper_gen_audio_step_gen (mtmd_helper_gen_audio * ctx ,llama_token sampled , 1043const float * h_state_in ,const float ** h_state_out , 1044bool * out_stop ) { 1045if (!ctx -> pipeline ) { 1046return 1 ; 1047 } 1048bool stop = false; 1049const int32_t ret = ctx -> pipeline -> step_gen (sampled ,h_state_in ,h_state_out ,& stop ); 1050if (out_stop ) { 1051* out_stop = stop ; 1052 } 1053return ret ; 1054} 1055 1056int32_t mtmd_helper_gen_audio_get_output (mtmd_helper_gen_audio * ctx ,int32_t * out_sample_rate , 1057const char ** out_data ,size_t * out_data_len ,int64_t * out_n_samples ) { 1058if (!ctx -> pipeline ) { 1059return 1 ; 1060 } 1061return ctx -> pipeline -> get_output (out_sample_rate ,out_data ,out_data_len ,out_n_samples ); 1062}