koboldcpp/tools/mtmd/README-dev.md
Xuan-Son Nguyen 6e62ba5384
mtmd: support pocket-tts (#26871)
* adapt the api

* text model ok

* working impl, need verify and clean up

* mtmd: build the pocket-tts transposed convolutions as GEMM + col2im

ggml_conv_transpose_1d has no grouped mode, so the depthwise upsample
was built as one convolution and one concat per channel, which floods
the graph with small nodes and makes kernel launches dominate the
decoder.

Fold both cases into the column form the seanet decoder already needs:
the general case reshapes the kernel to [IC, K * OC] and matmuls it
with the input, the depthwise case batches a matmul over the channels
so a step scales its own kernel. A single col2im_1d then scatter-adds
the columns back to the signal, with the same shape as before, so the
overlap-add tail, the streaming state and the bias are untouched.

Generation time per frame drops by 80% on CUDA and by 50% on CPU. The
output matches the previous implementation sample for sample, with a
correlation of 0.999994 and identical frame counts.

* flow_temp +  frames_after_eos

* chunking

* mtmd: carry the remaining pocket-tts per-pack settings

The language packs also tune the end-of-speech padding and the padding
of short prompts, next to the temperature already carried in the
mmproj: french_24l asks for 8 tail frames instead of the guessed 3,
english_2026-01 asks for short prompts to be padded with spaces.

Write both in the mmproj as clip.gen.audio.frames_after_eos and
clip.gen.audio.pad_short_text, keyed on the pack in the conversion
script like the temperature. The loader keeps them optional, so a
mmproj without them behaves as before. Map semicolons to commas for
every pack instead, the reference only asks for it on three of them and
it costs nothing elsewhere.

Existing mmproj files must be converted again to carry the two keys.

On a long french text the port now lands within 2% of the reference:
22.96s against 23.44s, with the same peak level and the same amount of
silence.

* clip.gen.audio.model_variant

* clean up code comments

* nit: drop the dead flow_temp hparam, the pack table holds the default

* update docs

* address security problems

* less invasive base.py

* lint

* add mtmd_gen_inp_default

* add docs

* rm gen_flow_temp

---------

Co-authored-by: Pascal <admin@serveurperso.com>
2026-08-11 14:18:30 +02:00

5.9 KiB

libmtmd dev guide

History

Please refer to multimodal.md for a broader context.

In short:

  • libmtmd started as a wrapper around libllava / clip.cpp
  • Various components that used to be in clip.cpp are moved progressively to mtmd. For example, preprocessor is now part of mtmd

Terminologies

  • mtmd: MulTiMoDal
  • bitmap: representing a raw input data, for example: RGB image, PCM audio
  • tiles / slices: for llava-uhd-style models, the preprocessor breaks a large input into smaller square images called tiles or slices
  • chunk: a mtmd_input_chunk represents a preprocessed input that can then be passed through mtmd_encode()

Pipeline

A typical pipeline of the core libmtmd is as follows:

  • A bitmap (RGB image or PCM audio) is created
  • Bitmap and the text prompt is provided to mtmd_tokenize() that breaks the input into chunks
    • The tokenizer function first expands a "lazy" bitmap if it finds one. Typically, this is used by video, so that one media token corresponds to one input bitmap
    • For models that support "fused" temporal frames like Qwen-VL, the tokenizer tries to merge pair of consecutive frames into one batch
    • The preprocessor will then be called, which produces a list of chunks
    • Depending on the model itself, special tokens will be injected to separate image chunks (i.e. llava-uhd-style models)
  • Multiple bitmaps may be batched together to form a larger mtmd_batch()
  • Single image or batch is encoded, via mtmd_encode() or mtmd_batch_encode()
  • Get the output embeddings

Helper

We provide a set of helper functions via mtmd_helper to make using libmtmd easier. The helper provides:

  • Image, audio and video file decoding (for example, decode raw JPEG into RGB bitmap)
  • Manage llama_batch and calls to llama_decode

Audio generation support

Audio generation is added to mtmd in PR #26254

Currently, we support the 3-stage pipeline below which should cover most TTS models:

  • Stage 1: Backbone / Semantic Stage: Backbone model accepts text prompt and reference voice as input
  • Stage 2: Acoustic Detail Generator: A model takes the hidden state from backbone and generate audio details (usually as audio codes or mel-spectrogram)
  • Stage 3: Waveform Reconstruction: Convert the semantic and acoustic data from previous stages to the final waveform

For example, Qwen3-TTS:

  • Reference voice is encoded using ECAPA-TDNN speaker encoder (speaker_encoder)
  • Text prompt and reference voice are processed via a backbone (talker.model)
  • A model converts sampled semantic token and hidden state from stage 2 into a list of 15 acoustic codes (talker.code_predictor)
  • 16 generated codes are converted into waveform (code2wav)

API design constraints

Due to wide variety of audio generation pipelines, the mtmd_gen_audio system is designed to be flexible and reusable by new models.

mtmd_gen_audio is split into 2 main API:

  • Core API mtmd.h: handles main inference. Important: the API surface must be stateless; caller must handle state management and audio frame accumulation.
  • Helper API mtmd-helper.h: provides a model-agnostic stateful API. Usage example can be found in the tools/tts directory.

Checklist for porting new audio generation models to mtmd

  1. Make sure to consult merged PRs about adding new TTS models, especially reviewer comments
  2. Establish a list of reusable and missing components from the current mtmd implementation.
  3. For GGUF conversion:
    • Backbone model should be converted to a normal text model (loadable via libllama)
      • If model used hard-coded embedding row ID, append them to token embeddings and assign token name for them (see qwen3tts.py)
      • If model have a specific output logits head for audio codes (usually semantic code), keep the head as-is and pad the logits at inference time (see src/models/qwen3vl.cpp)
    • Sidecar models (code2wav, bigvgan, etc) must live inside the mmproj GGUF (but can be in different clip_context if necessary)
      • Note: it should use ggml_build_forward_select to select graphs if multiple graphs living in the same context
    • Reuse existing GGUF metadata key name and tensor name whenever possible; think twice before adding extensive changes to GGUF writer. For example, Qwen3-TTS hard-code part of the hparams to clip.cpp as they won't likely to change.
    • For tensor naming:
      • Prefixed with a.* for tensors used by speaker encoder pipeline
      • Prefixed with a.gen.* for generation stages (code / mel-spectrogram / PCM generation)
    • For GGUF metadata:
      • Reuse as many existing keys as possible
      • In most cases, you can hard-code model configs in the model graph class, or in clip_hparams
      • If some values need to be exposed to the mtmd_helper layer, hard-code them in mtmd_helper and distinguish by pipeline and mtmd_gen_audio_info::model_variant if necessary
      • Do NOT add new GGUF metadata or new fields to mtmd_gen_audio_info unless you can prove that you absolutely need them
  4. Make sure most of the changes happen inside mtmd-helper-gen.cpp. A good PR looks like this:
    • 10-20% changes is to add new backbone (text) model and conversion
    • 60% changes inside mtmd-helper-gen.cpp
    • 10% changes inside libmtmd and clip.cpp systems
    • The rest downstream code (CLI, server) should have no changes at all
  5. Update usage documentation in tools/tts/README.md

IMPORTANT: If your model needs changes that don't fit the existing infrastructure, open an issue first for discussion.

No-go checklist (these will get the PR rejected and require discussion before proceeding):

  • Violating the API design constraints stated above
  • Adding a new model-specific binary: the API and binary surface must stay model-agnostic