CosyVoice3 — одна из лучших open source TTS моделей прямо сейчас, особенно для русского языка. Но у неё есть проблема: LLM-часть на PyTorch работает медленно на слабых GPU вроде T4. RTF (real-time factor) около 1.17 — это значит синтез одной секунды аудио занимает больше секунды реального времени.
Я решил это исправить, добавив поддержку llama.cpp через llama-cpp-python. Результат: RTF упал до ~0.45, то есть ускорение примерно в 2.6x.
В этой статье расскажу как это работает, почему это нетривиально, и как попробовать самому.
Почему CosyVoice LLM — не обычная LLM
CosyVoice3 использует LLM (на базе Qwen2.5-0.5B) для генерации речевых токенов. Но это не стандартная языковая модель — у неё две головы и два набора эмбеддингов:
-
Текстовые эмбеддинги (
embed_tokens) — стандартный словарь Qwen, ~151936 токенов -
Речевые эмбеддинги (
speech_embedding) — 6561 токен для аудио -
Речевая голова (
llm_decoder) — отдельный выходной слой для речевых токенов
llama.cpp не знает об этой архитектуре. Он ожидает стандартную модель с одним словарём и одной lm_head.
Конвертер: объединяем всё в одну обычную LLM
Чтобы скормить модель llama.cpp, нужно привести её к стандартному виду. Идея простая: слить оба эмбеддинга в один большой словарь.
# Конкатенируем текстовые и речевые эмбеддингиinput_emb = torch.cat([txt_emb, spk_in], dim=0) # Теперь vocab_size = 151936 + 6561+200 = ~158700# Собираем единую lm_headfull_output = torch.zeros((input_emb.shape[0], input_emb.shape[1]))full_output[:151936, :] = txt_emb # текстовая частьfull_output[151936:, :] = spk_out # речевая часть
После этого обновляем config.json (новый vocab_size, tie_word_embeddings: false) и расширяем vocab.json, добавляя токены вида <|s_0|>, <|s_1|>, … для речевых токенов.
Теперь convert_hf_to_gguf.py из llama.cpp работает без изменений и видит просто большой Qwen2.5.
Готовые сконвертированные модели опубликованы на HuggingFace: Ferraronp/CosyVoice3-qwen2.5-0.5b-speech-gguf
Доступные варианты квантизации: Q2_K, Q3_K_S, Q3_K_M, Q3_K_L, Q4_0, Q4_1, Q4_K_S, Q4_K_M, Q5_0, Q5_1, Q5_K_S, Q5_K_M, Q6_K, Q8_0, BF16, F16, F32.
Конвертер (Colab-ноутбук): Ferraronp/CosyVoice-gguf-converter
Инференс: как не дать модели генерировать текст вместо речи
После конвертации возникает другая проблема. Модель теперь видит огромный словарь из текстовых и речевых токенов. Ничто не мешает ей на выходе генерировать обычный текст — ведь с её точки зрения это просто токены с маленькими индексами.
Решение: маскировка логитов при сэмплинге. Перед выбором следующего токена берём сырые логиты из llama.cpp, ставим -inf на все позиции кроме речевых токенов и EOS:
logits = np.array(self.llm_gguf.scores[logit_pos], dtype=np.float32)# Разрешаем только речевые токены [151936, 151936+6561) и EOSvalid = np.full(n_vocab, False)valid[speech_offset : speech_offset + base_speech_size] = Truevalid[eos_token_id] = Truelogits[~valid] = -np.inf# Дальше стандартный softmax + top-k/top-p сэмплингprobs = np.exp((logits - logits[valid].max()) / temperature)probs /= probs.sum()next_token = np.random.choice(n_vocab, p=probs)
Встроенный sample() из llama-cpp-python тоже работает, но иногда может вернуть текстовый токен — в этом случае код автоматически переключается на маскировку.
А как насчёт TRT-LLM?
Существует форк FastCosyVoice с поддержкой TensorRT-LLM. Это мощный инструмент, но он требует Docker и конвертацию модели под конкретную GPU. Бенчмарки там только на RTX 3090 и RTX 5060ti. На слабых картах это либо не заведётся, либо потребует часов настройки.
llama-cpp-python бэкенд устанавливается одной командой:
pip install llama-cpp-python
Никакого Docker, никакого Triton, никакой конвертации под GPU. Работает на слабых GPU, работает на CPU. Если у вас нет топовой карты — это ваш вариант.
Результаты
Тестировалось на NVIDIA T4, fp16:
|
Бэкенд |
Среднее RTF |
|---|---|
|
PyTorch fp16 (оригинал) |
~1.17 |
|
llama-cpp-python F16 GGUF |
~0.45 |
RTF < 1.0 означает что синтез идёт быстрее реального времени — можно использовать в стриминге без накопления лага.
Как попробовать
Установка:
pip install llama-cpp-python
Использование:
from cosyvoice.cli.cosyvoice import AutoModelcosyvoice = AutoModel( model_dir='pretrained_models/Fun-CosyVoice3-0.5B', load_llama_cpp=True, gguf_model_path='/path/to/cosyvoice_llm_f16.gguf')# Все стандартные методы работают как обычноfor output in cosyvoice.inference_zero_shot(text, prompt_text, prompt_wav): audio = output['tts_speech']
Скачать готовую GGUF модель: HuggingFace
Форк с llama-cpp-python бэкендом (для быстрого старта): Ferraronp/CosyVoice
PR в оригинальный репозиторий: FunAudioLLM/CosyVoice#1872
ссылка на оригинал статьи https://habr.com/ru/articles/1027934/