Голосовой ассистент на RTX 3090: полный гайд по STT, LLM и TTS | AiManual
AiManual Logo Ai / Manual.
28 Дек 2025 Гайд

Как собрать голосового ассистента на одной видеокарте: STT, LLM и TTS на RTX 3090

Пошаговое руководство по сборке локального голосового ассистента на одной видеокарте RTX 3090. Распознавание речи (STT), языковая модель (LLM) и синтез речи (TT

Проблема: Почему облачные ассистенты нас не устраивают?

Сегодня каждый может попросить ChatGPT о чём угодно через браузер или мобильное приложение. Но что, если вы хотите поговорить с ИИ, как с настоящим собеседником — голосом, без задержек, и чтобы ваши разговоры оставались приватными? Облачные решения вроде Alexa или Google Assistant отправляют ваши запросы на серверы компаний, где они анализируются и хранятся.

Ключевые проблемы облачных ассистентов: задержки из-за сети, зависимость от интернета, вопросы приватности данных, невозможность тонкой настройки под свои нужды и, что немаловажно — ежемесячные платежи за API-вызовы к мощным моделям.

Решение — собрать своего ассистента локально. И видеокарта RTX 3090 с её 24 ГБ памяти — идеальный кандидат для этой задачи. Она позволяет запустить всю цепочку: от распознавания вашей речи до генерации осмысленного ответа и его озвучивания — полностью на вашем компьютере.

Решение: Архитектура локального голосового ассистента

Наша система будет состоять из трёх ключевых компонентов, работающих последовательно:

  1. STT (Speech-to-Text): Модель распознавания речи, которая преобразует ваш голос в текст. Мы будем использовать Parakeet от NVIDIA — одну из лучших open-source моделей.
  2. LLM (Large Language Model): Языковая модель, которая понимает запрос и генерирует текстовый ответ. Выбор зависит от ваших задач: от быстрых 7B-моделей до более умных 20B вариантов.
  3. TTS (Text-to-Speech): Модель синтеза речи, которая превращает текстовый ответ ИИ в естественную речь. Kokoro — современная модель с отличным качеством и поддержкой русского языка.
Компонент Модель Примерный объём VRAM Особенности
STT Parakeet RNNT 1.1B 2-3 ГБ Высокая точность, низкая задержка
LLM Qwen2.5-7B-Instruct (Q4_K_M) 5-6 ГБ Хороший баланс скорости и качества
TTS Kokoro 82M 1-2 ГБ Естественное звучание, мультиязычность
Итого 8-11 ГБ Остаётся запас для буферов

Как видите, даже с запасом мы укладываемся в 24 ГБ памяти RTX 3090. Это даёт нам пространство для манёвра: можно выбрать более крупную LLM или оставить память для параллельных задач. Если же вы хотите пойти дальше и использовать две карты, изучите наш гайд по настройке Dual RTX 3090 с NVLink.

Пошаговый план сборки

1 Подготовка системы и установка базовых компонентов

Перед началом убедитесь, что у вас установлены свежие драйверы NVIDIA (версия 535 или выше) и CUDA Toolkit 12.1+. Мы будем использовать Python 3.10+ и виртуальное окружение для изоляции зависимостей.

# Создаём и активируем виртуальное окружение
python -m venv voice_assistant_env
source voice_assistant_env/bin/activate  # Для Windows: voice_assistant_env\Scripts\activate

# Устанавливаем базовые библиотеки
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
pip install transformers>=4.36.0 accelerate>=0.25.0
pip install sounddevice pyaudio wave  # Для работы с аудио
pip install llama-cpp-python --force-reinstall --upgrade --no-cache-dir \
  --verbose \
  -f https://abetlen.github.io/llama-cpp-python/whl/cu121/
💡
Использование llama-cpp-python с поддержкой CUDA критически важно для производительности. Эта сборка позволяет загружать GGUF-модели прямо в VRAM видеокарты, что значительно ускоряет генерацию ответов по сравнению с CPU.

2 Установка и настройка STT (Parakeet)

Parakeet — семейство моделей распознавания речи от NVIDIA, обученных на огромных датасетах. Мы будем использовать относительно компактную версию, которая отлично работает на GPU.

# Установка дополнительных зависимостей для STT
pip install nemo_toolkit['asr']

# Пример кода для распознавания речи с микрофона
import sounddevice as sd
import numpy as np
import torch
import nemo.collections.asr as nemo_asr

# Загружаем модель Parakeet
parakeet_model = nemo_asr.models.ASRModel.from_pretrained("nvidia/parakeet-rnnt-1.1b")
parakeet_model = parakeet_model.to('cuda')
parakeet_model.eval()

def record_audio(duration=5, sample_rate=16000):
    """Запись аудио с микрофона"""
    print("Говорите сейчас...")
    audio = sd.rec(int(duration * sample_rate),
                   samplerate=sample_rate,
                   channels=1,
                   dtype='float32')
    sd.wait()
    return audio.flatten()

def speech_to_text(audio_array, sample_rate=16000):
    """Преобразование аудио в текст"""
    # Подготовка аудио для модели
    audio_tensor = torch.tensor(audio_array).unsqueeze(0).to('cuda')
    
    # Распознавание
    with torch.no_grad():
        transcription = parakeet_model.transcribe(
            audio_tensor,
            batch_size=1,
            return_hypotheses=False
        )[0]
    
    return transcription

# Использование
# audio = record_audio(duration=7)
# text = speech_to_text(audio)
# print(f"Вы сказали: {text}")

3 Выбор и загрузка LLM модели

Это сердце нашего ассистента. Выбор модели зависит от ваших потребностей: скорость ответа vs. качество рассуждений. Для RTX 3090 я рекомендую квантованные GGUF-модели размером 7B-20B параметров.

Модель Размер (GGUF) Качество диалога Скорость (токенов/с) VRAM
Qwen2.5-7B-Instruct Q4_K_M 4.5 GB Отличное 25-40 ~5 GB
Llama-3.2-3B-Instruct Q4_K_M 2.1 GB Хорошее 60-100 ~3 GB
Qwen2.5-14B-Instruct Q4_K_M 8.2 GB Превосходное 15-25 ~9 GB
# Загрузка LLM через llama.cpp
from llama_cpp import Llama

# Инициализация модели (скачайте GGUF файл заранее)
llm = Llama(
    model_path="./models/qwen2.5-7b-instruct-q4_k_m.gguf",
    n_gpu_layers=-1,  # Загрузить все слои на GPU
    n_ctx=4096,       # Контекстное окно
    n_batch=512,      # Размер батча для GPU
    verbose=False
)

def generate_response(prompt, max_tokens=256):
    """Генерация ответа на промпт"""
    # Форматируем промпт в соответствии с шаблоном модели
    formatted_prompt = f"<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n"
    
    # Генерация
    output = llm(
        formatted_prompt,
        max_tokens=max_tokens,
        temperature=0.7,
        top_p=0.9,
        stop=["<|im_end|>"]
    )
    
    return output['choices'][0]['text'].strip()

# Пример использования
# response = generate_response("Объясни, что такое квантовая запутанность простыми словами")

Если вы новичок в теме квантования и форматов GGUF, рекомендую нашу статью «Что такое квантизация GGUF?», где мы подробно разбираем форматы и их влияние на качество.

4 Настройка TTS с Kokoro

Kokoro — современная модель синтеза речи от Facebook, которая поддерживает множество языков (включая русский) и позволяет управлять интонацией. Модель относительно компактна и хорошо работает на GPU.

# Установка TTS библиотеки
pip install TTS
import torch
from TTS.api import TTS

# Инициализация модели Kokoro
# Модель автоматически скачается при первом запуске
tts = TTS(model_name="tts_models/multilingual/multi-dataset/kokoro",
          progress_bar=False,
          gpu=True)

def text_to_speech(text, output_path="output.wav", language="ru"):
    """Преобразование текста в речь"""
    # Генерация аудио
    tts.tts_to_file(
        text=text,
        file_path=output_path,
        speaker=tts.speakers[0],  # Первый доступный голос
        language=language
    )
    print(f"Аудио сохранено в {output_path}")

# Воспроизведение аудио (дополнительно)
import simpleaudio as sa

def play_audio(file_path):
    wave_obj = sa.WaveObject.from_wave_file(file_path)
    play_obj = wave_obj.play()
    play_obj.wait_done()

# Пример использования
# text_to_speech("Привет! Я ваш локальный голосовой помощник.", "greeting.wav")
# play_audio("greeting.wav")

5 Сборка всего пайплайна в единое приложение

Теперь объединим все компоненты в единый цикл «слушай-думай-отвечай». Мы создадим простой скрипт, который будет работать в бесконечном цикле, ожидая голосовые команды.

import time
import threading
from queue import Queue

class VoiceAssistant:
    def __init__(self):
        print("Инициализация компонентов...")
        
        # Инициализация STT
        self.stt_model = self.load_stt_model()
        
        # Инициализация LLM
        self.llm = self.load_llm()
        
        # Инициализация TTS
        self.tts = self.load_tts()
        
        self.is_listening = False
        self.audio_queue = Queue()
        
        print("Ассистент готов к работе!")
    
    def load_stt_model(self):
        """Загрузка модели STT"""
        import nemo.collections.asr as nemo_asr
        model = nemo_asr.models.ASRModel.from_pretrained("nvidia/parakeet-rnnt-1.1b")
        model = model.to('cuda')
        model.eval()
        return model
    
    def load_llm(self):
        """Загрузка LLM модели"""
        from llama_cpp import Llama
        return Llama(
            model_path="./models/qwen2.5-7b-instruct-q4_k_m.gguf",
            n_gpu_layers=-1,
            n_ctx=4096,
            n_batch=512,
            verbose=False
        )
    
    def load_tts(self):
        """Загрузка TTS модели"""
        from TTS.api import TTS
        return TTS(model_name="tts_models/multilingual/multi-dataset/kokoro",
                  gpu=True,
                  progress_bar=False)
    
    def listen_loop(self):
        """Фоновая задача для прослушивания микрофона"""
        import sounddevice as sd
        import numpy as np
        
        sample_rate = 16000
        silence_threshold = 0.01
        silence_duration = 1.0  # секунды тишины для конца фразы
        
        audio_buffer = []
        silent_frames = 0
        
        def audio_callback(indata, frames, time, status):
            nonlocal silent_frames, audio_buffer
            
            volume_norm = np.linalg.norm(indata) * 10
            
            if volume_norm > silence_threshold:
                # Есть речь
                audio_buffer.append(indata.copy())
                silent_frames = 0
            else:
                # Тишина
                silent_frames += frames
                if silent_frames > sample_rate * silence_duration and len(audio_buffer) > 0:
                    # Завершили фразу
                    full_audio = np.concatenate(audio_buffer, axis=0)
                    self.audio_queue.put(full_audio.flatten())
                    audio_buffer = []
                    silent_frames = 0
        
        # Начинаем прослушивание
        with sd.InputStream(callback=audio_callback,
                           channels=1,
                           samplerate=sample_rate,
                           blocksize=1024):
            print("Слушаю... Произнесите команду.")
            while self.is_listening:
                sd.sleep(100)
    
    def process_audio(self, audio_array):
        """Обработка аудио: STT -> LLM -> TTS"""
        # 1. Распознавание речи
        print("Распознаю речь...")
        audio_tensor = torch.tensor(audio_array).unsqueeze(0).to('cuda')
        with torch.no_grad():
            user_text = self.stt_model.transcribe(audio_tensor)[0]
        
        print(f"Вы: {user_text}")
        
        # 2. Генерация ответа
        print("Думаю над ответом...")
        prompt = f"Ты полезный голосовой помощник. Отвечай кратко и по делу. Вопрос: {user_text}"
        response = self.generate_response(prompt)
        
        print(f"Ассистент: {response}")
        
        # 3. Синтез речи
        print("Озвучиваю ответ...")
        temp_file = f"response_{int(time.time())}.wav"
        self.tts.tts_to_file(text=response,
                            file_path=temp_file,
                            language="ru")
        
        # 4. Воспроизведение
        self.play_audio(temp_file)
        
        return response
    
    def generate_response(self, prompt):
        """Генерация ответа через LLM"""
        formatted_prompt = f"<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n"
        output = self.llm(formatted_prompt,
                         max_tokens=150,
                         temperature=0.7,
                         stop=["<|im_end|>"])
        return output['choices'][0]['text'].strip()
    
    def play_audio(self, file_path):
        """Воспроизведение аудио файла"""
        import simpleaudio as sa
        wave_obj = sa.WaveObject.from_wave_file(file_path)
        play_obj = wave_obj.play()
        play_obj.wait_done()
    
    def run(self):
        """Запуск основного цикла ассистента"""
        self.is_listening = True
        
        # Запускаем поток прослушивания
        listen_thread = threading.Thread(target=self.listen_loop)
        listen_thread.daemon = True
        listen_thread.start()
        
        try:
            while True:
                # Обрабатываем аудио из очереди
                if not self.audio_queue.empty():
                    audio_data = self.audio_queue.get()
                    self.process_audio(audio_data)
                time.sleep(0.1)
        except KeyboardInterrupt:
            print("\nЗавершение работы...")
            self.is_listening = False
            listen_thread.join()

# Запуск ассистента
if __name__ == "__main__":
    assistant = VoiceAssistant()
    assistant.run()

Нюансы реализации и частые ошибки

Даже с готовым кодом вы можете столкнуться с проблемами. Вот самые распространённые из них и способы их решения.

Ошибка 1: Нехватка видеопамяти при одновременной загрузке всех моделей

Решение: Используйте контекстные менеджеры для загрузки моделей только когда они нужны, или реализуйте механизм выгрузки/загрузки моделей по требованию. Для более крупных LLM рассмотрите стратегии масштабирования, включая распределение по нескольким GPU.

Ошибка 2: Большая задержка между вопросом и ответом (более 5-10 секунд)

Решение:

  • Используйте более легковесную LLM (например, 3B вместо 7B)
  • Уменьшите параметр max_tokens для генерации более коротких ответов
  • Проверьте, что все модели загружены на GPU (используйте nvidia-smi для мониторинга)
  • Рассмотрите использование специализированных NPU для определённых задач, если они есть в вашей системе

Ошибка 3: Плохое качество распознавания речи в шумной обстановке

Решение:

  • Используйте направленный микрофон
  • Добавьте простой шумоподавитель перед передачей аудио в STT модель
  • Обучите или дообучите STT модель на своих аудиоданных (потребует времени и данных)

Ошибка 4: ИИ "галлюцинирует" или даёт неточные ответы

Решение:

  • Используйте RAG (Retrieval-Augmented Generation) для предоставления модели актуальных данных
  • Реализуйте систему вызова функций (function calling) для точных операций (погода, калькулятор и т.д.)
  • Понизьте температуру (temperature) до 0.3-0.5 для более детерминированных ответов
  • Изучите практический гайд по избеганию ошибок при локальном запуске LLM

Дальнейшее развитие проекта

Ваш базовый голосовой ассистент готов. Но на этом возможности не заканчиваются. Вот что можно добавить для создания по-настоящему мощного помощника:

  • Вызов функций (Function Calling): Научите ассистента выполнять конкретные действия — включать музыку, искать в интернете, управлять умным домом.
  • Контекстная память: Реализуйте хранение истории диалога, чтобы ассистент помнил, о чём вы говорили 10 минут назад.
  • Мультимодальность: Добавьте возможность обрабатывать не только голос, но и изображения с веб-камеры. Для вдохновения изучите подходы к мультимодальному RAG.
  • Голосовая идентификация: Настройте распознавание разных пользователей по голосу для персонализированных ответов.
  • Офлайн-знания: Интегрируйте локальную базу знаний (например, с помощью ChromaDB) для быстрого доступа к вашим документам.

Заключение

Собрать полноценного голосового ассистента на одной видеокарте RTX 3090 не только возможно, но и практично. Вы получаете приватного помощника, который работает без интернета, не отправляет ваши данные на сторонние серверы и может быть настроен под ваши конкретные нужды.

Ключевое преимущество такого подхода — полный контроль над системой. Вы можете менять модели, настраивать параметры, добавлять функциональность без ограничений со стороны облачных провайдеров. И хотя начальная настройка требует времени, в долгосрочной перспективе вы получаете гибкий инструмент, который будет развиваться вместе с вашими потребностями.

Начните с базовой версии из этого гайда, а затем экспериментируйте — пробуйте разные LLM, улучшайте качество распознавания, добавляйте новые возможности. Мир локального ИИ стремительно развивается, и ваш RTX 3090 — отличный полигон для экспериментов.