Ты собрал аудиопаплайн для Gemma-4. И он не работает
Картина знакомая. Скачал свежую мультимодальную Gemma-4, которая в теории должна и слушать, и говорить. Поставил VAD для детекта речи. Подключил модель через llama.cpp. Нацепил TTS на выход. Запускаешь – и получаешь либо тишину, либо дикие лаги, либо падение со словами "CUDA out of memory". Знакомо? Это не ты криворукий. Это стандартный ад, который ждет всех, кто пытается заставить аудио работать в локальных LLM без правильного стека.
Проблема в том, что большинство гайдов до сих пор предлагают CPU-only пайплайны. Аудио обрабатывается в одном потоке, модель тупит на CPU, а TTS ждет своей очереди. Результат – задержка в 5-10 секунд, что для голосового ассистента смертельно. Нам нужен другой подход.
Актуальность на 08.04.2026: Речь идет о Gemma-4 14B, последней на этой дате мультимодальной версии от Google. llama.cpp поддерживает GPU inference через CUDA 12.4 и Vulkan 1.3. Unsloth Studio обновлен до версии 2026.1, а LiteRT LM – это новая легковесная библиотека, специально заточенная под реальное время.
Почему стандартный пайплайн VAD-LLM-TTS ломается?
Давай разберем по косточкам. Классическая цепочка: Voice Activity Detection (VAD) → Транскрипция/понимание через LLM → Синтез речи (TTS). На бумаге все просто. В реальности:
- VAD работает в отдельном процессе и кидает куски аудио в очередь. Если LLM медленная, очередь переполняется.
- Gemma-4 на CPU – это 2-3 токена в секунду для 14B модели. Для ответа из 50 слов ждать полминуты.
- TTS, особенно нейронный, тоже требует GPU. Без него синтез тормозит весь поток.
- Самое мерзкое – бутылочное горло в передаче данных между процессами. Аудиобуферы, текстовые строки, управляющие сигналы – все это создает лаг.
Решение – перевести всю цепочку на GPU, максимально распараллелить и использовать инструменты, которые не просто работают, а созданы для реального времени. Как в нашем гайде про убийство задержки в голосовом AI.
1Собираем правильный инструментарий
Забудь про чистый Ollama и стандартные биндинги. Нам нужны специализированные инструменты, которые умеют жать каждую миллисекунду.
| Инструмент | Зачем нужен | Версия на 08.04.2026 |
|---|---|---|
| llama.cpp с GPU | Инференс Gemma-4 на GPU с квантованием. Поддержка непрерывного батчинга. | Commit с CUDA 12.4 |
| Unsloth Studio | Ускорение инференса в 2-5 раз за счет кастомных ядер и оптимизаций памяти. | 2026.1 |
| LiteRT LM | Легковесная обертка для запуска моделей с приоритетом на низкую задержку. | v0.4.2 |
| Silero VAD | Детект речи с минимальной задержкой. Может работать на GPU. | v4.1 |
| KittenTTS или AnyTTS | Синтез речи с поддержкой потокового вывода. | KittenTTS 2.3, AnyTTS 1.8 |
Первое, что делаем – собираем llama.cpp с поддержкой CUDA. Не ту версию, что в репозиториях (там часто старая), а свежую с GitHub.
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make LLAMA_CUDA=1 -j$(nproc)2Квантуем Gemma-4 для скорости, а не только для экономии памяти
Берем оригинальную Gemma-4 14B (не инструктивную, а базовую мультимодальную) и квантуем в формат Q4_K_M. Почему не Q8 или тем более FP16? Потому что нам нужен баланс между скоростью и качеством. Q4_K_M на GPU дает ускорение в 3-4 раза с минимальной потерей точности для аудиозадач.
# Конвертируем Hugging Face модель в GGUF
python3 convert-hf-to-gguf.py /path/to/gemma-4-14b --outtype q4_k_m
# Квантуем через llama.cpp
./quantize ./models/gemma-4-14b.gguf ./models/gemma-4-14b-q4_k_m.gguf q4_k_mВот здесь многие ошибаются – квантуют в самый агрессивный формат Q2_K, а потом удивляются, что модель перестала понимать контекст аудио. Для мультимодальных моделей, которые работают с аудиоэмбеддингами, лучше не опускаться ниже Q4.
3Интегрируем Unsloth Studio и LiteRT LM
Теперь магия. Устанавливаем Unsloth Studio – он умеет патчить llama.cpp для использования более эффективных ядер матричного умножения.
pip install unsloth-studio==2026.1
pip install litert-lm==0.4.2Создаем простой скрипт инициализации, который загружает модель один раз и держит ее в памяти GPU, готовую к непрерывному приему запросов. Это ключевое отличие от обычного llama.cpp, который для каждого запроса перезагружает контекст.
from litert_lm import LiteRTModel
import torch
model = LiteRTModel(
model_path="./models/gemma-4-14b-q4_k_m.gguf",
max_batch_size=4, # Обрабатываем до 4 аудиосегментов параллельно
continuous_batching=True, # Непрерывный батчинг – жизнь без ожидания
gpu_layers=99, # Все слои на GPU
)
# Вместо стандартного generate используем потоковый вариант
def process_audio_transcript(transcript):
stream = model.generate(
transcript,
max_tokens=150,
stream=True,
temperature=0.7,
)
for token in stream:
yield token # Отдаем токены по мере генерации, не дожидаясь концаЭтот подход сокращает задержку между окончанием речи пользователя и началом ответа модели с 10+ секунд до 1-2 секунд. Как в статье про SAM-Audio, где мы экономили VRAM, здесь мы экономим время.
4Собираем VAD в реальном времени, а не для галочки
Берем Silero VAD, но не стандартную версию, а ту, что умеет работать на GPU через ONNX Runtime с CUDA execution provider. Зачем? Чтобы детект речи не съедал CPU, который нам нужен для управления потоками.
import onnxruntime as ort
import numpy as np
# Создаем сессию ONNX с CUDA
providers = [
('CUDAExecutionProvider', {
'device_id': 0,
'arena_extend_strategy': 'kNextPowerOfTwo',
'gpu_mem_limit': 2 * 1024 * 1024 * 1024, # 2 GB лимит
'cudnn_conv_algo_search': 'EXHAUSTIVE',
'do_copy_in_default_stream': True,
}),
'CPUExecutionProvider',
]
session = ort.InferenceSession('silero_vad.onnx', providers=providers)
# Функция для обработки аудиобуфера
def detect_speech(audio_chunk):
# audio_chunk: numpy array, 16 kHz mono
input_data = {
session.get_inputs()[0].name: audio_chunk.astype(np.float32)
}
out = session.run(None, input_data)
is_speech = out[0].item() > 0.5 # Порог можно настраивать
return is_speechЭтот VAD мы интегрируем в отдельный поток, который постоянно слушает микрофон или аудиопоток (например, из WebRTC). Как только детектируется конец речи, сегмент аудио сразу отправляется в ASR (если нужна транскрипция) или прямо в Gemma-4, если она умеет работать с аудио напрямую (через аудиоэмбеддинги).
5Подключаем TTS, который не тормозит
Здесь два варианта. Если хочешь максимальное качество и есть лишние 4-6 ГБ VRAM, ставим KittenTTS сервер, как в нашем гайде по развертыванию на Windows. Если ресурсы ограничены, берем AnyTTS – он легче и умеет работать с разными бэкендами.
# Пример с AnyTTS через его API
import requests
import json
def synthesize_speech(text, voice="ru_RU-ai_ru-ekaterina"):
# AnyTTS работает как отдельный сервис
response = requests.post(
'http://localhost:8001/synthesize',
json={
'text': text,
'voice': voice,
'stream': True # Ключевой параметр – начинаем отдавать аудио сразу
},
stream=True
)
for chunk in response.iter_content(chunk_size=1024):
yield chunk # Потоковое аудио, можно сразу в аудиовыходВажно: TTS сервер должен быть на том же GPU или отдельной карте. Не пытайся запускать и LLM, и TTS на одном слабом GPU – будут троттлинг и артефакты. Лучше распределить: LLM на основную карту, TTS на вторую (если есть) или на CPU, но с легкой моделью.
Собираем пазл: архитектура всего пайплайна
Теперь нужно связать все компоненты в одну систему. Рисуем архитектуру в голове:
- Входной аудиопоток (микрофон, WebRTC, файл) идет в VAD модуль на GPU.
- Обнаруженные сегменты речи буферизуются и отправляются в ASR (если используется) или прямо в Gemma-4. Для ASR можно взять SLAY-ASR – легковесную модель.
- Gemma-4 через LiteRT LM получает текст (или аудиоэмбеддинги) и генерирует ответ потоково.
- Каждый сгенерированный токен сразу отправляется в TTS сервер с флагом stream=True.
- TTS начинает синтез еще до завершения генерации полного ответа. Это создает эффект почти мгновенного ответа.
Реализуем это на Python с asyncio. Главное – правильно настроить очереди и семафоры, чтобы не перегрузить память.
import asyncio
from queue import Queue
from threading import Thread
# Очереди для межпоточного обмена
audio_queue = Queue(maxsize=10) # Сегменты аудио
text_queue = Queue(maxsize=10) # Транскрипты/запросы
tts_queue = Queue(maxsize=10) # Токены для TTS
async def pipeline():
# Запускаем все компоненты в отдельных потоках/процессах
vad_thread = Thread(target=run_vad, args=(audio_queue,))
llm_thread = Thread(target=run_llm, args=(text_queue, tts_queue,))
tts_thread = Thread(target=run_tts, args=(tts_queue,))
vad_thread.start()
llm_thread.start()
tts_thread.start()
# Главный цикл управления
while True:
await asyncio.sleep(0.001) # Не блокируем event loop
# Контролируем заполненность очередей, при необходимости сбрасываем старые данные
if audio_queue.qsize() > 8:
audio_queue.get() # Выкидываем самый старый сегмент, чтобы не копилось
Предупреждение: Не используй обычные списки или глобальные переменные для обмена данными между потоками. Только потокобезопасные очереди (Queue) или asyncio.Queue. Иначе будут race conditions и падения.
Типичные ошибки и как их избежать
Даже с правильными инструментами можно наступить на грабли. Вот список самых частых проблем:
- "CUDA out of memory" после запуска TTS. Происходит, когда LLM и TTS борются за память. Решение: ограничь память для каждого процесса через
CUDA_VISIBLE_DEVICESилиtorch.cuda.set_per_process_memory_fraction(). Либо используй техники экономии VRAM. - Задержка между предложениями в TTS. TTS ждет точки, чтобы начать синтез. Включи флаг
stream=Trueи отправляй текст кусками по 2-3 слова. Да, синтез будет менее естественным, но зато без пауз. - VAD ловит шумы как речь. Настрой порог детекции (threshold) под твое окружение. В тихой комнате можно поставить 0.3, в шумном офисе – 0.7. Лучше сделать калибровку в начале сессии.
- Gemma-4 теряет контекст в длинном диалоге. Убедись, что используешь непрерывный батчинг и не сбрасываешь контекст между запросами. В LiteRT LM есть параметр
keep_alive.
Если нужен готовый пример такого пайплайна в действии, посмотри на проект из статьи про голосовых NPC в Unity. Там похожая архитектура, но для игр.
Что в итоге? Аудио работает, но требует тонкой настройки
Полноценный аудиопаплайн для Gemma-4 на GPU – это не пять строк кода. Это сборка из специфичных инструментов, каждый из которых решает свою задачу с минимальной задержкой. Основные выводы:
- Не используй CPU для инференса LLM в реальном времени. Только GPU с квантованием Q4_K_M или выше.
- Бери llama.cpp с поддержкой CUDA/Vulkan, патчи его через Unsloth Studio и управляй через LiteRT LM для низкой задержки.
- VAD и TTS тоже должны работать на GPU, либо быть максимально легковесными.
- Потоковая обработка от начала до конца – ключ к отсутствию лагов. Никакого ожидания полного предложения.
И последнее. Не гонись за самым большим контекстом. Для голосового диалога достаточно 4096 токенов. Увеличивая контекст до 32k, ты добавляешь задержку на attention слоях. Gemma-4 и так умная, дай ей только самое необходимое. Как в том гайде про настройку для перевода игр, где мы резали контекст для скорости.
Теперь у тебя есть план. Собирай, тестируй, настраивай под свое железо. А когда заработает – сделай так, чтобы TTS говорил с эмоциями. Но это уже другая история.