Skip to content

Commit d550af8

Browse files
committed
Merge branch 'fix/cyrillic-unicode-decomposition' into ci/cuda12
2 parents 303c3a9 + fe0e85d commit d550af8

15 files changed

Lines changed: 332 additions & 60 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ audio.cpp would not be moving this quickly without generous contributors bringin
5050

5151
Task tags: `TTS` text to speech, `Clone` voice cloning, `VC` voice conversion, `ASR` speech recognition, `Align` forced alignment, `VAD` voice activity detection, `Diar` speaker diarization, `Codec` audio codec, `Sep` source separation, `Music` music/song generation, `SFX` sound effects, `Edit` audio/music editing, `Design` voice design, `Dialogue` multi-speaker dialogue TTS, `Ctrl` TTS/clone voice control such as emotion, style, instruction, caption, or non-verbal tag control.
5252

53-
Runtime tags: safetensors is the default model loading path. `GGUF 16/Q8` means both 16-bit and `q8_0` GGUF paths are tested; `GGUF Q8` means only `q8_0` is tested; `GGUF F32` means the original-F32 GGUF path is tested. See [docs/gguf.md](docs/gguf.md) for precision/status details. `Bundled` means the tiny runtime asset ships under `assets/framework/models` and needs no separate model download. `Stream` means the family exposes a streaming server/session path.
53+
Runtime tags: safetensors is the default model loading path. `GGUF 16/Q8/Q4` means those GGUF precision or quantization paths are tested; `GGUF Q8` means only `q8_0` is tested; `GGUF F32` means the original-F32 GGUF path is tested. See [docs/gguf.md](docs/gguf.md) for precision/status details. `Bundled` means the tiny runtime asset ships under `assets/framework/models` and needs no separate model download. `Stream` means the family exposes a streaming server/session path.
5454

5555
| Family | Task | Lang | Variants | Runtime |
5656
|---|---|---|---|---|
@@ -80,7 +80,7 @@ Runtime tags: safetensors is the default model loading path. `GGUF 16/Q8` means
8080
| **vevo2** | TTS, Music, VC, Edit | en, zh | Vevo2 with Qwen2.5-0.5B AR model | GGUF 16 |
8181
| **vibevoice** | TTS, Dialogue | en, zh | VibeVoice-1.5B, VibeVoice-7B | GGUF 16/Q8 |
8282
| **vibevoice_asr** | ASR | auto | VibeVoice ASR | GGUF 16/Q8 |
83-
| **voxtral_realtime** | ASR | auto | Voxtral-Mini-4B-Realtime-2602 | GGUF 16/Q8, Stream |
83+
| **voxtral_realtime** | ASR | auto | Voxtral-Mini-4B-Realtime-2602 | GGUF 16/Q8/Q4, Stream |
8484
| **voxcpm2** | TTS, Clone, Design, Ctrl | ar, da, de, el, en, es, fi, fr, he, hi, id, it, ja, km, ko, lo, ms, my, nl, no, pl, pt, ru, sv, sw, th, tl, tr, vi, zh | VoxCPM2-2B, 48 kHz | GGUF 16/Q8, Stream |
8585
| **index_tts2** | TTS, Clone, Ctrl | zh, en | IndexTTS-2 | GGUF 16/Q8 |
8686
| **irodori_tts** | TTS, Clone, Design, Ctrl | ja | Irodori-TTS-500M-v3, Irodori-TTS-600M-v3-VoiceDesign | GGUF 16/Q8 |

docs/asr.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,12 +255,13 @@ audiocpp_cli --task asr --family vibevoice_asr --model models/VibeVoice-ASR --ba
255255

256256
## Voxtral Realtime
257257

258-
Voxtral Realtime is a Mistral realtime ASR model with offline and streaming sessions. The model manager installs the Q8_0 standalone GGUF package by default; native Hugging Face directories and other standalone GGUF variants can also be used when provided directly.
258+
Voxtral Realtime is a Mistral realtime ASR model with offline and streaming sessions. The model manager installs the Q8_0 standalone GGUF package by default; native Hugging Face directories and other standalone GGUF variants can also be used when provided directly. A Q4_K GGUF package is also available for lower memory use and faster CUDA runs; in a quick path check its transcripts matched Q8_0 except for one capitalization-only difference.
259259

260260
| Field | Value |
261261
|---|---|
262262
| Family | `voxtral_realtime` |
263263
| Model path | `models/Voxtral-Mini-4B-Realtime-2602-GGUF/voxtral-mini-4b-realtime-2602-q8_0.gguf` when installed through the model manager |
264+
| GGUF variants | `bf16`, `q8_0`, `q4_k` |
264265
| Task | `asr` |
265266
| Modes | `offline`, `streaming` |
266267
| Output | Transcription text |

docs/gguf.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ Status labels:
9494
| `voxcpm2` | Done | Pass | Pass | Pass (ASR match, drift) | Pass (ASR match, drift) |
9595
| `voxtral_realtime` | Done | Pass | --- | Pass | Pass |
9696

97+
Additional lower-bit checks:
98+
99+
| Family | Format | Tested |
100+
|---|---|---|
101+
| `voxtral_realtime` | `q4_k` | Pass (quick CUDA check; transcripts match Q8 except one capitalization-only difference) |
102+
97103
Q8 packaging notes:
98104

99105
- `chatterbox` Q8 is intentionally mixed type. Graph-sensitive scalar, norm,
@@ -109,6 +115,9 @@ Q8 packaging notes:
109115
projection or linear weights, while leaving the speaker encoder, lookup, and
110116
codebook-sensitive tensors unquantized. Quantizing those speaker-side tensors
111117
can produce long-form quality problems such as large silence.
118+
- `voxtral_realtime` also has a tested `q4_k` package. In a quick CUDA path
119+
check it was smaller and faster than Q8_0, while transcript output matched
120+
Q8_0 except for one capitalization-only difference.
112121

113122
## Build The Converter
114123

docs/reports/gguf_q8_performance.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,21 @@ Q8 gives the clearest end-to-end wins on the larger AR-style models:
2222

2323
Smaller or already memory-light models still load and run with Q8, but the speed gain can be modest. Treat Q8 as a measured route choice, not a guaranteed win for every model.
2424

25+
## Voxtral Q4_K Quick Check
26+
27+
Voxtral also has a `q4_k` GGUF package. In this quick CUDA path-test check,
28+
Q4_K was smaller and faster than Q8_0 while producing effectively the same
29+
transcripts. Four requests matched exactly; one differed only by capitalization
30+
(`Mother Nature` vs `mother nature`).
31+
32+
| Route | Q8 RTF | Q4_K RTF | Q4_K vs Q8 |
33+
|---|---:|---:|---:|
34+
| Offline short | 0.0862 | 0.0629 | 1.37x |
35+
| Offline medium | 0.0643 | 0.0476 | 1.35x |
36+
| Offline longer | 0.0576 | 0.0439 | 1.31x |
37+
| Offline sampled | 0.0630 | 0.0500 | 1.26x |
38+
| Streaming path | 0.1036 | 0.0904 | 1.15x |
39+
2540
## TTS Offline Long-Lived Session
2641

2742
| Model | 16-bit speed vs real time | Q8 speed vs real time | Q8 vs 16-bit | 16-bit peak VRAM | Q8 peak VRAM |

scripts/build_windows.ps1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ param(
1717
)
1818

1919
Set-StrictMode -Version Latest
20-
$ErrorActionPreference = "Stop"
20+
$ErrorActionPreference = "Continue" # let native command stderr (e.g. cmake warnings) flow without aborting the script
2121

2222
function Invoke-Checked {
2323
param(
@@ -154,7 +154,7 @@ function Find-VsCMake {
154154
return $cmake
155155
}
156156
$cmd = Get-Command "cmake.exe" -ErrorAction SilentlyContinue
157-
return if ($cmd) { $cmd.Source } else { "" }
157+
if ($cmd) { return $cmd.Source } else { return "" }
158158
}
159159

160160
function Find-VsNinja {
@@ -164,7 +164,7 @@ function Find-VsNinja {
164164
return $ninja
165165
}
166166
$cmd = Get-Command "ninja.exe" -ErrorAction SilentlyContinue
167-
return if ($cmd) { $cmd.Source } else { "" }
167+
if ($cmd) { return $cmd.Source } else { return "" }
168168
}
169169

170170
function Find-WindowsKitTool {

src/framework/sampling/torch_random.cpp

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ TorchCudaSamplingPolicy resolve_torch_cuda_sampling_policy(
312312
log_default_policy(log_category, "backend is not CUDA");
313313
return policy;
314314
}
315-
#ifdef GGML_USE_CUDA
315+
// CUDA runtime probe via dynamic library (works with both static and GGML_BACKEND_DL builds)
316316
const engine::io::DynamicLibraryHandle driver = engine::io::open_dynamic_library(
317317
{"libcuda.so.1", "libcuda.so", "libcuda.dylib", "nvcuda.dll"});
318318
if (driver == nullptr) {
@@ -366,14 +366,6 @@ TorchCudaSamplingPolicy resolve_torch_cuda_sampling_policy(
366366
policy.cuda_fast_path = true;
367367
policy.cuda_device_index = device_index;
368368
return policy;
369-
#else
370-
(void) device_index;
371-
if (failure_mode == TorchCudaSamplingPolicyFailureMode::FallbackToDefault) {
372-
log_default_policy(log_category, "build does not include CUDA support");
373-
return policy;
374-
}
375-
throw std::runtime_error(std::string(model_name) + " generation requires a build with CUDA support");
376-
#endif
377369
}
378370

379371
uint64_t torch_cuda_tensor_iterator_offset_blocks(

src/models/qwen3_tts/tokenizer_speech_decoder.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,9 +1047,7 @@ class Qwen3SpeechTokenizerDecoderGraph {
10471047
int64_t code_frames,
10481048
ggml_backend_t backend,
10491049
int threads) const {
1050-
const bool frame_match = perf_mode_ == Qwen3TTSPerfMode::FlashAttention
1051-
? code_frames_ == code_frames
1052-
: code_frames_ >= code_frames;
1050+
const bool frame_match = code_frames_ == code_frames;
10531051
return weights_.get() == &weights && frame_match && backend_ == backend &&
10541052
compute_threads_ == std::max(1, threads);
10551053
}
@@ -1156,7 +1154,6 @@ runtime::AudioBuffer Qwen3SpeechTokenizerDecoderRuntime::decode(const Qwen3Speec
11561154
}
11571155
std::vector<float> samples;
11581156
samples.reserve(static_cast<size_t>(codec_codes.frames * kDecodeSamplesPerCode));
1159-
const int64_t graph_capacity_frames = std::min<int64_t>(codec_codes.frames, kChunkCodes + kLeftContextCodes);
11601157
double graph_build_ms = 0.0;
11611158
double input_upload_ms = 0.0;
11621159
double graph_compute_ms = 0.0;
@@ -1183,7 +1180,7 @@ runtime::AudioBuffer Qwen3SpeechTokenizerDecoderRuntime::decode(const Qwen3Speec
11831180
graph_.reset();
11841181
graph_ = std::make_unique<Qwen3SpeechTokenizerDecoderGraph>(
11851182
weights_,
1186-
std::max(chunk_frames, graph_capacity_frames),
1183+
chunk_frames,
11871184
*execution_context_,
11881185
*constants_,
11891186
graph_arena_bytes_,

src/models/supertonic/tokenizer_text.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,47 @@ bool append_japanese_kana_decomposition(uint32_t codepoint, std::vector<uint32_t
139139
return false;
140140
}
141141

142+
bool append_cyrillic_decomposition(uint32_t codepoint, std::vector<uint32_t> & out) {
143+
constexpr uint32_t kBreve = 0x0306;
144+
constexpr uint32_t kDiaeresis = 0x0308;
145+
static constexpr std::pair<uint32_t, uint32_t> kBreveLetters[] = {
146+
{0x0439, 0x0438}, // й -> и
147+
{0x0419, 0x0418}, // Й -> И
148+
};
149+
static constexpr std::pair<uint32_t, uint32_t> kDiaeresisLetters[] = {
150+
{0x0451, 0x0435}, // ё -> е
151+
{0x0401, 0x0415}, // Ё -> Е
152+
{0x0457, 0x0456}, // ї -> і
153+
{0x0407, 0x0406}, // Ї -> І
154+
};
155+
156+
for (const auto & [composed, base] : kBreveLetters) {
157+
if (codepoint == composed) {
158+
out.push_back(base);
159+
out.push_back(kBreve);
160+
return true;
161+
}
162+
}
163+
for (const auto & [composed, base] : kDiaeresisLetters) {
164+
if (codepoint == composed) {
165+
out.push_back(base);
166+
out.push_back(kDiaeresis);
167+
return true;
168+
}
169+
}
170+
return false;
171+
}
172+
142173
std::vector<uint32_t> decompose_known_text_codepoints(const std::vector<uint32_t> & codepoints) {
143174
std::vector<uint32_t> out;
144175
out.reserve(codepoints.size() * 2);
145176
for (const uint32_t codepoint : codepoints) {
146177
if (append_japanese_kana_decomposition(codepoint, out)) {
147178
continue;
148179
}
180+
if (append_cyrillic_decomposition(codepoint, out)) {
181+
continue;
182+
}
149183
append_hangul_decomposition(codepoint, out);
150184
}
151185
return out;

webui/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,10 @@ Starts the Gradio web interface (`webui.py`); open **http://127.0.0.1:7860** in
101101
(one model in VRAM at a time; switching models restarts it).
102102
- The UI lets you upload a reference voice, download uninstalled models, enter an HF token / proxy, and so on.
103103
- Backend is auto-detected (as above: GPU if CUDA is present, otherwise CPU); `AUDIOCPP_BACKEND=gpu|cpu` forces it.
104-
In CPU mode, the ggml thread count is set automatically to cores-1 (override with `AUDIOCPP_THREADS=N`), and the
105-
VRAM warning is no longer shown.
104+
In CPU mode, the ggml thread count is set automatically from the **physical** core count — SMT/Hyper-Threading
105+
siblings are not counted, and one core is left free above 4 — so a long run leaves the rest of the machine
106+
usable. Filling every logical CPU is faster (~1.4x in a short CPU TTS run on an 8-core/16-thread 5800H); set
107+
`AUDIOCPP_THREADS=N` if you want that. The VRAM warning is not shown in CPU mode.
106108

107109
> The web interface (7860) is for humans; to use it as an **API for other programs**, start `audiocpp_server`
108110
> directly, or once the WebUI is up, hit the port 8080 it manages directly.

webui/README.zh.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ python3 -m venv venv && ./venv/bin/pip install -r webui/requirements.txt
9292
起/切换底层的 `audiocpp_server`(一次一个模型在显存里,换模型即重启)。
9393
- 界面里可上传参考音色、下载未安装的模型、填 HF token / 代理等。
9494
- 后端自动检测(同上:有 CUDA 用 GPU,否则 CPU);`AUDIOCPP_BACKEND=gpu|cpu` 可强制。
95-
CPU 模式下 ggml 线程数自动设为核数-1(可用 `AUDIOCPP_THREADS=N` 覆盖),且不再显示显存警告。
95+
CPU 模式下 ggml 线程数按**物理核数**自动设置(不计超线程的逻辑核,物理核多于 4 时再留一个核给系统),
96+
这样长任务跑起来机器仍然可用。把逻辑核占满会更快(8 核 16 线程的 5800H 上,一次短文本 CPU TTS 快约 1.4 倍),
97+
想要这个速度就设 `AUDIOCPP_THREADS=N`。CPU 模式下不显示显存警告。
9698

9799
> 网页界面(7860)是给人用的;要给**其它程序**当 API,请直接启动 `audiocpp_server`
98100
> 或让 WebUI 起来后直接打它管理的 8080 端口。

0 commit comments

Comments
 (0)