Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions app/gguf/main.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#include "engine/framework/assets/tensor_source.h"
#include "engine/framework/assets/weight_adapter.h"
#include "engine/framework/debug/trace.h"
#include "engine/framework/io/filesystem.h"
#include "engine/framework/io/json.h"
#include "engine/framework/runtime/options.h"

#include <algorithm>
#include <cctype>
Expand Down Expand Up @@ -578,6 +581,9 @@ void print_usage() {
"[--bnb-nf4-type q8_0] [--exclude-prefix <logical-prefix>] "
"[--keep-type <tensor-prefix>*=<type>] "
"[--fold-weight-norm <tensor-prefix>*] "
"[--weight-adapter <normalized.safetensors>] [--adapter-strength <float>] "
"[--adapter-merge-mode delta_f32|rankwise_f32|rounded_bf16_delta] "
"[--log] "
"[--overwrite] [--no-sidecars] "
"[--allow-missing-model-spec]\n"
<< " audiocpp_gguf --inspect <model.gguf>\n";
Expand All @@ -596,6 +602,11 @@ int main(int argc, char ** argv) {
std::optional<std::string> family;
std::string type;
engine::assets::GgufConversionOptions conversion_options;
std::filesystem::path adapter_path;
engine::assets::WeightAdapterReadOptions adapter_options;
adapter_options.require_scaling_metadata = true;
auto adapter_mode = engine::assets::WeightMergeMode::DeltaF32;
bool adapter_options_set = false;
bool overwrite = false;
bool embed_sidecars = true;
bool allow_missing_model_spec = false;
Expand All @@ -605,6 +616,10 @@ int main(int argc, char ** argv) {
print_usage();
return 0;
}
if (arg == "--log") {
engine::debug::configure_logging({true});
continue;
}
if (arg == "--overwrite") {
overwrite = true;
continue;
Expand All @@ -620,6 +635,7 @@ int main(int argc, char ** argv) {
if ((arg == "--input" || arg == "--output" || arg == "--type" || arg == "--inspect" || arg == "--root" ||
arg == "--sidecar" || arg == "--family" || arg == "--model-spec" || arg == "--model-spec-override" ||
arg == "--bnb-nf4-type" || arg == "--exclude-prefix" || arg == "--keep-type" ||
arg == "--weight-adapter" || arg == "--adapter-strength" || arg == "--adapter-merge-mode" ||
arg == "--fold-weight-norm") &&
i + 1 < argc) {
const std::string value = argv[++i];
Expand All @@ -633,6 +649,19 @@ int main(int argc, char ** argv) {
output = value;
else if (arg == "--type")
type = value;
else if (arg == "--weight-adapter")
adapter_path = std::filesystem::u8path(value);
else if (arg == "--adapter-strength") {
adapter_options.strength = *engine::runtime::parse_finite_float_option(
{{"adapter_strength", value}}, {"adapter_strength"});
adapter_options_set = true;
} else if (arg == "--adapter-merge-mode") {
if (value == "delta_f32") adapter_mode = engine::assets::WeightMergeMode::DeltaF32;
else if (value == "rankwise_f32") adapter_mode = engine::assets::WeightMergeMode::RankWiseF32;
else if (value == "rounded_bf16_delta") adapter_mode = engine::assets::WeightMergeMode::RoundedBF16Delta;
else throw std::runtime_error("unsupported adapter merge mode: " + value);
adapter_options_set = true;
}
else if (arg == "--bnb-nf4-type")
conversion_options.bnb_nf4_type = engine::assets::parse_tensor_storage_type(value);
else if (arg == "--exclude-prefix")
Expand Down Expand Up @@ -737,6 +766,20 @@ int main(int argc, char ** argv) {
*conversion_options.bnb_nf4_type != engine::assets::TensorStorageType::Q8_0) {
throw std::runtime_error("--bnb-nf4-type currently supports q8_0");
}
if (adapter_path.empty() && adapter_options_set) {
throw std::runtime_error("adapter strength/mode requires --weight-adapter");
}
if (!adapter_path.empty()) {
auto adapter = engine::assets::read_weight_adapter(adapter_path, adapter_options);
if (adapter.updates.empty() && adapter.replacements.empty()) {
throw std::runtime_error("weight adapter contains no updates or replacements");
}
for (auto & entry : adapter.updates) {
entry.second.merge_mode = adapter_mode;
}
conversion_options.weight_adapter = std::make_shared<engine::assets::WeightAdapter>(std::move(adapter));
conversion_options.weight_adapter_merge_mode = adapter_mode;
}
engine::assets::convert_tensor_sources_to_gguf(inputs, output, storage_type, overwrite, embed_sidecars,
resolved_sidecar_root, sidecars, embedded_model_spec,
conversion_options);
Expand Down
2 changes: 2 additions & 0 deletions docs/asr.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# ASR Models

For Qwen3-ASR adapter loading and merged GGUF export, see the [LoRA guide](lora.md).

| Model | Family | Mode(s) | Quick Start |
|---|---|---|---|
| Audio Flamingo 3 / Next | `audio_flamingo` | offline | [Audio Flamingo](models/audio_flamingo.md) |
Expand Down
141 changes: 141 additions & 0 deletions docs/lora.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# LoRA

LoRA adapters must match the base checkpoint's architecture, size, and tensor
layout. An adapter trained for another release of the same family is not
automatically compatible.

## Supported Models

| Model | Family | Session options | Adapter format and target |
|---|---|---|---|
| Qwen3-TTS | `qwen3_tts` | `qwen3_tts.lora`, `qwen3_tts.lora_scale` | PEFT Talker/Subtalker adapters. Custom-voice packages can also include `speaker_embedding.safetensors` and `tts_config.json`. |
| Qwen3-ASR | `qwen3_asr` | `qwen3_asr.lora`, `qwen3_asr.lora_scale` | PEFT encoder/decoder adapters; both original and Transformers weight layouts are supported. |
| VoxCPM2 | `voxcpm2` | `voxcpm2.lora`, `voxcpm2.lora_scale` | Native `lora_weights.safetensors` with adjacent `lora_config.json`; generator only, not AudioVAE. |
| VoxCPM-0.5B | `voxcpm1` | `voxcpm1.lora`, `voxcpm1.lora_scale` | Native VoxCPM adapter and `lora_config.json`; upstream tensor names are mapped to the GGUF layout. |
| Fish Audio S2 Pro | `fish_audio` | `fish_audio.lora`, `fish_audio.lora_scale` | Prepared Fish Speech adapters for slow/fast decoder projections and embeddings; codec weights are unchanged. |
| CosyVoice3 | `cosyvoice3` | `cosyvoice3.lora`, `cosyvoice3.lora_scale` | PEFT LLM adapters, or a native checkpoint with `lora_weights.pt`, `config.json`, `speech_embedding.pt`, and `llm_decoder.pt`. |
| F5-TTS | `f5_tts` | `f5_tts.lora`, `f5_tts.lora_scale` | PEFT DiT adapters. Reconvert the matching original checkpoint with the current converter; do not use the currently published GGUF packages as the base. Vocos is unchanged. |
| OuteTTS | `outetts` | `outetts.lora`, `outetts.lora_scale` | PEFT Llama adapters for the matching OuteTTS checkpoint. DAC is unchanged. |
| YuE2 | `yue2` | `yue2.ar_lora`, `yue2.ar_lora_scale`, `yue2.nar_lora`, `yue2.nar_lora_scale` | Separate AR/NAR adapters, including NAR projection replacements. |
| ACE-Step | `ace_step` | `ace_step.lora`, `ace_step.lora_scale` | DiT matrix LoRA/LoKr in PEFT or ComfyUI/LyCORIS format. |
| VibeVoice | `vibevoice` | `vibevoice.lora`, `vibevoice.lora_scale` | PEFT language-model adapters with optional diffusion-head and connector replacements. Also accepted as load options. |

## Session Loading

Container format and adapter layout are independent. Shared tensor I/O accepts
safetensors and PyTorch checkpoints (`.pt`, `.pth`, `.bin`, `.ckpt`); the suffix
does not select PEFT, native LoRA, or LoKr semantics. Runtime loading expects a
tensor state dictionary, not an arbitrary training checkpoint.

Directory discovery uses logical names such as `adapter_model`, `lora_weights`,
and `speaker_embedding`, with the supported extensions. It prefers safetensors,
then `.pt`, `.pth`, `.bin`, and `.ckpt`. A malformed selected file is an error;
the loader does not silently try another file. The filenames below show the
usual upstream packaging, not a restriction to that container.

CosyVoice3's native `lora_weights` package uses `config.json` and requires
speech-embedding/output-head sidecars. PEFT uses `adapter_config.json`.
When both layouts are present, directory loading and export select the native
package. An explicit file selects that file; `lora_weights` retains native
configuration and required sidecars, while `adapter_model` uses PEFT metadata.

Use `--session-option` on the CLI, or the same keys in a server model's
`session_options`. Adapters are applied once while loading session weights;
they do not add inference graph operations or modify the base files.
Pre-v1 GGUF packages still require a current external or compiled model spec.

```bash
audiocpp_cli --family qwen3_asr --task asr --model base.gguf \
--backend cuda --audio speech.wav \
--session-option qwen3_asr.lora=adapters/domain \
--session-option qwen3_asr.lora_scale=1 --log
```

Keep PEFT's `adapter_config.json` next to `adapter_model.safetensors`. For native
VoxCPM, point to the directory containing `lora_weights.safetensors` and
`lora_config.json`. Fish Speech Lightning checkpoints need preparation:

```bash
python tools/lora/prepare_fish_audio_adapter.py \
--input lora.ckpt --alpha 16 --output adapter.safetensors
```

Use the training alpha, not alpha/r. The exporter handles Fish embedding-factor
orientation and records scaling in the output.

For Qwen3-TTS custom voices, keep the trained speaker embedding and configuration
with the adapter and select the configured name with `voice_id`. A sidecar-backed
custom voice uses the TTS route, not the Base checkpoint's reference-clone prompt.

## Scaling

The new integrations use `lora_scale=1` as a multiplier for the adapter's
alpha/r coefficient; PEFT rsLoRA uses alpha/sqrt(rank). Full-weight replacements
are not scaled. To restore the unmodified model, omit the adapter rather than
setting its strength to zero when it includes replacements.

Existing controls retain their behavior: VibeVoice's optional `lora_scale`
overrides the effective coefficient, YuE2 native factors use their direct
AR/NAR strengths, and ACE-Step uses an adapter-strength multiplier with zero
disabling the adapter.

YuE2 export supports custom strengths for A/B adapters; LoKr export currently
requires unit strength.

## Merged GGUF

Use the shared model-aware merge tool for every family in the table:

The tool requires PyTorch, `safetensors`, `gguf`, and a built `audiocpp_gguf`.
Fish Lightning preparation also requires `omegaconf`.

```bash
python tools/lora/merge_lora.py --family qwen3_tts \
--base models/qwen3-tts-base-bf16.gguf --adapter adapters/my-voice \
--type q8_0 --output models/qwen3-tts-my-voice-q8_0.gguf
```

Original converter-compatible safetensors and shard indexes can be passed directly,
with the same component namespaces as `audiocpp_gguf`. For Qwen3-TTS:

```bash
python tools/lora/merge_lora.py --family qwen3_tts \
--input model_weights=models/Qwen3-TTS/model.safetensors \
--input speech_tokenizer_weights=models/Qwen3-TTS/speech_tokenizer/model.safetensors \
--root models/Qwen3-TTS --adapter adapters/my-voice \
--type bf16 --output models/qwen3-tts-my-voice-bf16.gguf
```

Checkpoints requiring model-specific preparation still use their existing
conversion tool first. The merge tool does not guess or rewrite an unsupported
base tensor layout.

| Model | Additional merge arguments |
|---|---|
| YuE2 | `--adapter <AR>` and/or `--nar-adapter <NAR>`; strengths are `--scale` and `--nar-scale`. |
| ACE-Step | Select `--component dit_turbo_weights` (or the matching DiT component) when the base bundles multiple variants. LoRA/LoKr parsing and merging use the shared framework. |
| Fish Audio S2 Pro | A prepared adapter, or a Lightning `.ckpt` plus its training `--alpha`. |
| Qwen3-TTS | Speaker embedding/config sidecars are applied and embedded automatically. |
| CosyVoice3 | Native speech-embedding and output-head sidecars are merged automatically. |
| VibeVoice | Diffusion-head and connector sidecars are merged when included. |
| Other listed models | Use `--adapter` and optional `--scale`; default strength is `1`. |

`--scale` is an adapter-strength multiplier, including for VibeVoice; it is not
VibeVoice's runtime coefficient override. `--keep-type '<pattern>=<type>'` can be
repeated. `--converter <path>` selects an existing `audiocpp_gguf` executable.
Use `--overwrite` to replace an existing output. The base files are never changed.

The script stages only adapter factors and any changed sidecars. It does not
write a full intermediate merged checkpoint, and removes its staging directory
after conversion. Run the resulting GGUF without passing the adapter again.

The GGUF converter can merge a normalized, fully bound adapter before applying
the requested weight type. Use a 16-bit or original-dtype base where available;
merging into quantized weights first dequantizes them, then requantizes the
result. Quantization can change output independently of LoRA.

The converter's `--weight-adapter` input uses complete GGUF tensor names, including
component namespaces. `--adapter-merge-mode` selects `delta_f32`, `rankwise_f32`
(VibeVoice), or `rounded_bf16_delta` (YuE2). Model-specific adapter names must be
bound before using this low-level interface. Quantization and `--keep-type`
follow the normal [GGUF conversion rules](gguf.md).
2 changes: 2 additions & 0 deletions docs/models/cosyvoice3.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ audiocpp_cli \
| Option | Values | Default | Meaning |
|---|---|---:|---|
| `cosyvoice3.reference_cache_slots` | integer >= 0 | `4` | Prepared reference-audio cache slots. |
| `cosyvoice3.lora` | adapter directory or file | not set | PEFT LoRA, or a native checkpoint containing `lora_weights.pt`, `config.json`, `speech_embedding.pt`, and `llm_decoder.pt`. Only LLM weights are adapted. |
| `cosyvoice3.lora_scale` | finite float | `1` | Multiplier for alpha/r. Full embedding/head replacements are not scaled. |
| `cosyvoice3.mem_saver` | bool | `false` | Release cached runtime graphs after request phases. |
| `cosyvoice3.exec_plan` | JSON object | `{}` | Component execution plans. The only component is `flow`; see the RKNN section. |

Expand Down
8 changes: 8 additions & 0 deletions docs/models/f5_tts.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ leaks into the output.

### Converting checkpoints to GGUF

For LoRA support, reconvert the matching original checkpoint with the current
converter; do not use the currently published F5-TTS GGUF packages as the base.
PEFT DiT adapters can then be loaded with `--session-option f5_tts.lora=<path>`.
Keep `adapter_config.json` beside the adapter weights. The optional
`f5_tts.lora_scale` multiplies alpha/r and defaults to `1`. Adapted weights and
DiT graph caches are session-owned; the Vocos vocoder is not adapted.
See the [LoRA routing table](../lora.md).

`tools/community_models/convert_f5_tts.py` wraps `audiocpp_gguf` and produces one self-contained GGUF per
checkpoint (`transformer` + `vocos` namespaces) plus the standalone vocoder package:

Expand Down
11 changes: 11 additions & 0 deletions docs/models/fish_audio.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ this is output streaming, not incremental text input.
| Option | Values | Default | Meaning |
|---|---|---:|---|
| `fish_audio.mem_saver` | bool | `false` | Release cached AR runtime graphs after each request. |
| `fish_audio.lora` | checkpoint file | not set | Prepared Fish Speech adapter. Slow/fast decoder projections and embeddings are supported; codec weights are unchanged. |
| `fish_audio.lora_scale` | finite float | `1` | Multiplier for the adapter's alpha/r scaling. |
| `fish_audio.reference_cache_slots` | integer | `1` | Prepared reference-audio cache slots. |
| `fish_audio.weight_type` | `native`, `f32`, `f16`, `bf16`, `q8_0` | `native` | AR matmul weight storage type. |
| `fish_audio.codec_weight_type` | `native`, `f32`, `f16`, `q8_0` | `native` | Codec conv/matmul weight storage type. |
Expand All @@ -104,6 +106,15 @@ this is output streaming, not incremental text input.
| `fish_audio.codec_graph_arena_mb` | integer MiB >= 1 | `512` | Codec graph arena size. |
| `fish_audio.codec_weight_context_mb` | integer MiB >= 1 | `32` | Codec weight context size. |

To prepare a Fish Speech Lightning checkpoint, use its training alpha (not alpha/r):

```bash
python tools/lora/prepare_fish_audio_adapter.py --input lora.ckpt --alpha 16 --output adapter.safetensors
```

The exporter transposes embedding factors to the runtime's matrix convention and
stores alpha with every target. Use an adapter trained for the exact base checkpoint.

## Multi-Reference Conditioning

`multi_reference_cond` is a Fish Audio request option. It takes an ordered JSON
Expand Down
2 changes: 2 additions & 0 deletions docs/models/outetts.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ audiocpp_cli --task tts --family outetts \
The legacy session keys `outetts.aligner_model_path` and
`outetts.dac_graph_context_mb` remain accepted for backward compatibility.
| `--session-option outetts.reference_cache_slots=<n>` | integer | `1` | LRU slots for prepared reference profiles (alignment, DAC codes, and word features). Set `0` to disable reuse. |
| `--session-option outetts.lora=<path>` | directory or checkpoint file | not set | PEFT LoRA for the same Llama-OuteTTS checkpoint. Keep `adapter_config.json` beside its weights. DAC weights are unchanged. |
| `--session-option outetts.lora_scale=<value>` | finite float | `1` | Multiplier for the adapter's alpha/r scaling. |
| `--session-option outetts.mem_saver=true\|false` | bool | `false` | Release the reusable Llama cached-step graph after each generated chunk and release the aligner runtime after preparing a reference. Model and DAC weights stay resident; later requests rebuild released state. |

With logging enabled, OuteTTS reports framework chunk count and token budget, per-chunk word/character counts, recommended and effective generation limits, the natural stop reason, reference-profile cache hits/evictions, Llama runtime and step-graph rebuild/reuse, released cache capacity, and timings for reference alignment, DAC encode/decode, prompt construction, generation, and the complete session request. See [OuteTTS validation](../reports/outetts_validation.md) for the reproducible long-lived session and memory test.
2 changes: 2 additions & 0 deletions docs/models/qwen3_asr.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ audiocpp_gguf --input models/QwenCleo-ASR/model.safetensors --root models/QwenCl

| Option | Values | Default | Meaning |
|---|---|---:|---|
| `qwen3_asr.lora` | adapter directory or checkpoint file | not set | Load a PEFT LoRA at session creation. Encoder and decoder targets are supported; use an adapter trained for the same checkpoint size. Keep `adapter_config.json` beside the weights. |
| `qwen3_asr.lora_scale` | finite float | `1` | Strength multiplier applied after the adapter's alpha/r scaling. |
| `--audio` | WAV path | required | Speech audio; use 16 kHz WAV for the examples. |
| `--text` | text | empty string | Context prompt. |
| `--language` | language code | empty string | Recognition language hint. |
Expand Down
Loading
Loading