Долгое время я прекрасно обходился без использования технологий искусственного интеллекта. Одни задачи можно было реализовать без всякого ИИ, а для других или готовых моделей не было или это были какие-то коммерческие облачные API.

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

Казалось бы, есть и локально запускаемые аналоги ChatGPT или сервисов генерации изображений. Есть библиотеки типа llama.cpp - бери и используй! Но если бы было всё так просто, то не было бы этой статьи.

Для тех, кто не может ждать, можете посмотреть, чего теперь можно добиться относительно быстро:

Всё началось с идеи создания своего локального голосового помощника, который можно встроить в умный дом. Да, я в курсе, что есть множество готовых проектов, но меня в них всегда что-то не устраивало, а главное мне было интересно самому разработать софт, напечатать на 3D-принтере корпуса и собрать свои устройства для взаимодействия с виртуальным ассистентом.

Дальше я расскажу, с какими проблемами я столкнулся.

Сложность интеграции

Какие-то решения написаны на C++, какие-то на python или даже C# и JavaScript. Что-то придётся собирать руками, а для чего-то есть готовые пакеты под популярные дистрибутивы linux. У каких-то решений есть готовое API, а для других придётся писать C-биндинги и реализовывать API самому. Какие-то приложения собираются просто, а с другими приходится повозиться.

Нельзя сказать, что это не решаемые проблемы, но это не то, чем я хотел заниматься. Я горел идеей скорее захватить звук с микрофона, преобразовать его в текст, потом отправить в модель аналогичную ChatGPT и озвучить результат, занимаясь дальше накручиванием бизнес-логики.

Было бы хорошо, если бы все компоненты можно было запустить в Docker и не возиться с этим всем.

Непригодность для запуска на конечных устройствах

Я встречал много комментариев, где люди успешно запускали llama.cpp и whisper на mac mini и использовали это как готовое решение. Кто-то даже на Raspberry Pi умудряется запускать 7b модели.

Мне это решение категорически не подходило.

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

Да и вообще, интерфейсов взаимодействия множество. Например, на кухне это может быть умная колонка, встроенная где-нибудь в потолке, с которой взаимодействуют исключительно голосом, в гостиной это уже может быть устройство с экраном, способное не только на голосовые команды, но и на визуализацию результатов. Вне дома это может быть текстовый интерфейс, например Telegram-бот.

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

Видится, что лучше запускать все тяжелые задачи на домашнем сервере или даже нескольких серверах, или на худой конец на облачных ресурсах (но это обойдётся вам очень дорого).

Низкая производительность и ограниченность

Конечно можно запустить легкие 7b модели и на raspberry pi, но взаимодействовать с таким устройством будет сплошное мучение. На обработку вашего вопроса будут уходить минуты, а качество ответов будет весьма посредственным.

Для серьёзных моделей нужны устройства с большим количеством CPU, оперативной памяти и видеокартой, способной вместить модель целиком в память (например, Nvidia 3090 или 4090). С хорошим оборудованием можно будет получать более качественные результаты и быстрее.

Это ещё один аргумент для использования центрального сервера.

Отсутствие стандартизированного API

Продукты в области ИИ очень активно развиваются и постоянно появляется что-то новое, решающее задачу более качественно. Кому-то для распознавания голоса (ASR) нравится whisper, потому что он поддерживает около 100 языков и умеет автоматически определять язык, а кому-то Vosk, потому что он это делает качественнее и быстрее, хоть и для меньшего числа языков. Все эти решения имеют разные API и переход с одного на другое неизбежно будет приводить к переписыванию клиентского кода приложения. При этом вам повезло, если вы используете только распознавание текста, а если у вас ещё и отдельные интеграции для синтеза речи, шумоподавления, генерации текста и изображений — это будет сложнее.

Хотелось бы по принципу облачных сервисов реализовать один раз API и париться только подменяя реализации.

Итоги:

Для себя я отметил следующие пожелания к системам на базе ИИ:

  • Возможность запуска в Docker

  • Стандартизированное API, позволяющее без переписывания клиентского кода подменять реализации

  • Модульность

  • Простота использования

Так появился проект VoiceDock, призванный сделать ИИ доступнее для разработчиков конечных приложений.

VoiceDock API

В качестве протокола был выбран gRPC из-за его производительности, поддержки стриминга, простоты описания спецификации и большему числу генераторов клиентского кода для разных языков программирования.

В данный момент API решает следующие задачи:

SttAPI
// Speech-to-text service.
service SttAPI {
  // Converts speech to text.
  rpc SpeechToText(stream SpeechToTextRequest) returns (stream SpeechToTextResponse);
  // Returns available language packs.
  rpc GetLanguagePacks(GetLanguagePacksRequest) returns (GetLanguagePacksResponse);
  // Downloads selected language pack.
  rpc DownloadLanguagePack(DownloadLanguagePackRequest) returns (DownloadLanguagePackResponse);
}

AichatAPI
// Speech-to-text service.
service AichatAPI {
  // Generate response text by prompt.
  rpc Generate(GenerateRequest) returns (stream GenerateResponse);
  // Returns available ai chat models.
  rpc GetModels(GetModelsRequest) returns (GetModelsResponse);
  // Downloads selected ai model.
  rpc DownloadModel(DownloadModelRequest) returns (DownloadModelResponse);
}

TtsAPI
// Text-to-speech service.
service TtsAPI {
  // Converts text to speech.
  rpc TextToSpeech(TextToSpeechRequest) returns (stream TextToSpeechResponse);
  // Returns available voices.
  rpc GetVoices(GetVoicesRequest) returns (GetVoicesResponse);
  // Downloads selected voice.
  rpc DownloadVoice(DownloadVoiceRequest) returns (DownloadVoiceResponse);
}

Помимо методов, непосредственно решающих задачу, везде присутствуют следующие методы:

  • для получения списка моделей;

  • определение, какие из них уже загружены, какие из них можно загрузить по сети;

  • для запуска скачивания выбранной модели.

Пример описания модели AI Chat
// AI Chat model info.
message Model {
  // Model name
  string name = 1;
  // Is downloaded
  bool downloaded = 2;
  // Can be downloaded
  bool downloadable = 3;
  // License text to accept
  string license = 4;
}

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

Методы распознавания и синтеза речи работают с потоком аудиоданных. Метод распознавания принимает стрим аудиоданных. А метод синтеза возвращает стрим аудиоданных.

Я долго думал, как представить этот поток аудиоданных, чтобы реализация на клиентской стороне оставалась максимально простой, и остановился на следующей структуре, где данные представлены в виде сырого PCM (int16 little endian):

// Container for raw PCM audio.
message AudioContainer {
  // 16 bit raw PCM (is the format of 16 bits integers little endian)
  bytes data = 1;
  // Sample rate of audio data
  int32 sample_rate = 2;
  // Audio channels
  int32 channels = 3;
}

API - это хорошо, но толку от него мало, если нет реализации. Об этом далее.

VoiceDock Apps

На данный момент есть 3 готовых для использования реализации:

  • STT Whisper – реализует API распознавания речи

  • AI Chat llama – позволяет запускать llama подобные модели в ggml-формате

  • TTS Piper – реализует API синтеза речи

STT Whisper

Построен на базе whisper.cpp (Быстрая С++ реализации проекта Whisper от Open AI). Распознаёт текст на ~99 языках, может автоматически определять язык.

Запускается как на CPU:

docker run --rm \
  -v "$(pwd)/config:/data/config" \
  -v "$(pwd)/dataset:/data/dataset" \
  -p 9999:9999 \
  ghcr.io/voicedock/sttwhisper:latest sttwhisper

Так и на GPU:

docker run --rm \
  -v "$(pwd)/config:/data/config" \
  -v "$(pwd)/dataset:/data/dataset" \
  -p 9999:9999 \
  ghcr.io/voicedock/sttwhisper:gpu sttwhisper

Перед запуском нужно создать директории под конфигурацию и модели:

mkdir dataset
mkdir config

А также создать файл конфигурации «config/sttwhisper.json».

Либо скачать пример:

curl -o config/sttwhisper.json https://raw.githubusercontent.com/voicedock/sttwhisper/main/config/sttwhisper.json

Либо настроить самому:

[
  {
    "name": "model_name",
    "languages": ["ru", "en"],
    "download_url": "download_url",
    "license": "license text to accept"
  }
]

Если «download_url» не пустой, то модель можно будет скачивать по API. Остальное, думаю, понятно.

Модель можно загрузить предварительно и положить по пути «dataset/{model_name}/model.bin».

Модели принимает в формате ggml (скачать можно тут).

После запуска можно делать gRPC запросы по порту 9999.

AI chat llama

Сделана на базе популярного проекта llama.cpp от того же автора, что и whisper.cpp. На Hugging Face вы можете найти множество моделей уже сконвертированных в ggml формат. Рейтинг самых лучших моделей можно посмотреть тут и потом найти их ggml-версии. Ggml формат поддерживает разные методы квантования и обычно в описании репозитория написано, чем они отличаются. Вариант квантования «q4_1», вероятно, подойдёт всем.

Запустить можно как на CPU:

docker run --rm \
  -v "$(pwd)/config:/data/config" \
  -v "$(pwd)/dataset:/data/dataset" \
  -p 9999:9999 \
  ghcr.io/voicedock/aichatllama:latest aichatllama

Так и на GPU:

docker run --rm \
  -v "$(pwd)/config:/data/config" \
  -v "$(pwd)/dataset:/data/dataset" \
  -e LLAMA_GPU_LAYERS=2 \
  --runtime=nvidia --gpus all \
  -p 9999:9999 \
  ghcr.io/voicedock/aichatllama:gpu aichatllama

Обратите внимание на параметр LLAMA_GPU_LAYERS, который говорит сколько слоёв модели нужно загрузить в память GPU. Чем больше, тем быстрее, но и требует больше памяти видеокарты.

Перед запуском также нужно создать директории под конфигурацию и модели:

mkdir dataset
mkdir config

И создать файл конфигурации «config/aichatllama.json».

Либо скачать пример:

curl -o config/aichatllama.json https://raw.githubusercontent.com/voicedock/aichatllama/main/config/aichatllama.json

Либо настроить самому:

[
  {
    "name": "model_name",
    "download_url": "download_url",
    "license": "license text to accept"
  }
]

Модель можно загрузить предварительно и положить по пути «dataset/{model_name}/model.bin».

После запуска можно делать gRPC запросы по порту 9999.

TTS Piper

Решение основано на проекте Piper и позволяет достаточно качественно и быстро синтезировать голос (демки можно послушать тут). К сожалению, на момент создания Piper не предоставлял shared library (сейчас, насколько мне известно, предоставляет), поэтому пришлось поработать напильником, но, думаю, скоро переделаю по-нормальному.

TTS Piper поддерживает 26 языков. В принципе, можно и самостоятельно добавить поддержку нового языка.

Запускается только на CPU, но работает оно быстро и без GPU:

docker run --rm \
  -v "$(pwd)/config:/data/config" \
  -v "$(pwd)/dataset:/data/dataset" \
  -p 9999:9999 \
  ghcr.io/voicedock/ttspiper:latest ttspiper

Как обычно перед запуском нужно создать директории под конфигурацию и модели:

mkdir dataset
mkdir config

И создать файл конфигурации «config/ttspiper.json».

Либо скачать пример:

curl -o config/ttspiper.json https://raw.githubusercontent.com/voicedock/ttspiper/main/config/ttspiper.json

Либо настроить самому:

[
  {
    "lang": "lang_code",
    "speaker": "speaker_name",
    "download_url": "download_url",
    "license": "license text to accept"
  }
]

Модель можно загрузить предварительно и распаковать по пути «dataset/{lang}/{speaker}/».

После запуска можно делать gRPC запросы по порту 9999.

Заключение

Пройдя этот путь, уже не так сложно начать строить свой проект голосового ассистента. Наверняка, и у вас есть куча своих идей, которыми вы горите: может это будет web-сервис для сокращения текста, а может telegram-бот для расшифровки лекций или аудио сообщений. И я буду рад, если кому-то, кроме меня, пригодятся наработки проекта VoiceDock.

Всю информацию я буду собирать на сайте VoiceDock.app. Вы можете использовать как готовые приложения, так и делать собственные, реализуя gRPC спецификацию API.

В любом случае, это проект с открытым исходным кодом с максимально свободной лицензией, позволяющей делать не только продукты для личного, но и для коммерческого использования (только уточняйте лицензии на используемые модели).

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

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

Комментарии (17)


  1. rads
    01.08.2023 11:30
    +1

    Спасибо, что поделились наработками! Думаю, хороший старт для многих проектов :)


  1. ValentinaIgorevna
    01.08.2023 11:30
    +1

    От меня тоже спасибо! Хочу разобраться


  1. shadrap
    01.08.2023 11:30
    +1

    Спасибо , рад был увидеть что есть С++ реализация виспера.

    ТОлько я так и не понял в итоге на чем вы это хозяйство запускать хотите ?


    1. liderman Автор
      01.08.2023 11:30
      +2

      Сейчас у меня домашний сервер, с 128гб ОЗУ, cpu 24 потока и видеокартой Nvidia 3090.

      С большими моделями Whisper проблем нет, piper прекрасно и на CPU работает, а llama 30b модели целиком влезают в видео память Nvidia 3090.

      Думаю дальше и 75b модели попробовать, но грузить уже не все слои в видео память.

      Но на текущий момент есть 30b, модели, которые опережают ChatGPT 3.5, а в каких-то задачах лучше ChatGPT 4. Так что думаю бытовых видеокарт будет достаточно, для качественного продукта.

      Видео в начале статьи без ускорения, но это не предел скорости. Есть ещё над чем поработать в плане оптимизации, но я спешил поделиться с сообществом текущими наработками.


      1. shadrap
        01.08.2023 11:30

        Не слабое железо) Так в цель в итоге - просто голосовой броузинг? Или все же управление домом? У меня викуна 7 крутится на i7\16 , да не быстрая , я думал это из-за собственных "претренингов" , но смысла ее интеграции в систему управления домом я так и не прочувствовал ..


        1. liderman Автор
          01.08.2023 11:30
          +1

          Конечно хочется не просто получать ответы на вопросы. То что на видео, это демо которого я хотел достичь за вечер, но не смог достичь по причинам описанным в статье. Хочется полноценное управление умным домом (тут не уверен, что стану писать с нуля и возможно интегрирую с Home Assistant), хочется сделать законченные устройства в "железе", пустить нейросеть в интернет (она же прекрасно умеет обобщать и сокращать результаты из нескольких источников). В общем хочется сделать "Вау", какой-то новый user experience.

          Задача не простая и не быстрая, но мне это интересно, это моё хобби и я получаю от этого невероятный кайф


          1. shadrap
            01.08.2023 11:30
            +1

            За полностью локальный ассистент голосую руками и ногами. Я уже упомянул - на поиграться есть викуна 7 и я с ней поигрался. ЧТо дальше - не понятно... Вот есть у меня система управления домом - состоит из почти 150 устройств, управляется с приложения под андроид. Это автомат , дополнительная автоматизация ему нужна ? Просто голосовые команды -не интересно . Интересно было бы парсить видео потоки с камер в "интеллектуальном" режиме , а не глупым мошен детектором... Больше что-то ничего не приходит умного...


  1. liderman Автор
    01.08.2023 11:30
    +1

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

    Сейчас я нахожусь в туристической базе, и тут есть какое-то подобие умного дома, и прикол в том, что пропал WiFi и я ничего не могу сделать))) С ресепшен связаться тоже не могу, т.к. мобильная сеть тут ловит только в определенных местах.


    1. le2
      01.08.2023 11:30
      +2

      локальный ассистент нужен многим. Судя по комментариям, многие как и я не хотят доверять свои сИкреты Алисе.


      1. Orbit67
        01.08.2023 11:30

        Всё верно, открывая свои сикретики Алисе, вы открываете их феисбе.


    1. visirok
      01.08.2023 11:30
      +1

      Вы решаете очень нужную задачу. Рад, что не пропустил Вашу статью. Постараюсь в ближайшее время разобраться с Вашим проектом поподробнее.


    1. ofixone
      01.08.2023 11:30

      Таки помоему сейчас умные дома онинтируют на свой собственный протокол (или частоту, не шарю точно), чтобы она работала не через инет, а просто через роутер. Видимо там не оч настроили :)


      1. liderman Автор
        01.08.2023 11:30
        +1

        В умном доме может использоваться масса протоколов, как опенсорсных так и проприетарных, причём одновременно, но статья не совсем про это.


    1. AlexanderS
      01.08.2023 11:30

      Мне иногда кажется, что я решаю никому не нужную задачу, пытаясь сделать качественный голосовой ассистент работающий локально, но выпуская эту статью я очередной раз убедился в его актуальности

      Задача как минимуму нужна вам. А то, что она делается изначально масштабируемой в массы — это просто великолепно! Поверьте — ещё не перевелись люди, ценящие автономность. Ваша разработка интересна как минимум пользователям таких вещей как Nextcloud и Home Assistant.


  1. Priz01
    01.08.2023 11:30

    Решил почитать эту статью чисто из интереса, понял процентов 50(а может меньше), но этого хватило для примерного представления. Сейчас для меня это всё "выглядит" удивительно и многообещающе, так держать!


  1. mihmig
    01.08.2023 11:30

    После запуска можно делать gRPC запросы по порту 9999.

    А есть пример на python?
    Например послать на распознавание звуковой файл созданный тем же Whisper?


    1. liderman Автор
      01.08.2023 11:30
      +1

      Я не большой спец по python, но для всех языков программирования путь примерно один:

      1) Копируете спеки https://github.com/voicedock/voicedock-specs/tree/main/proto

      2) Запускаете кодогенерацию клиента (как сделать это для python написано тут: https://grpc.io/docs/languages/python/basics/#client)

      3) Импортируете клиент и инстанцируете его передав IP:port

      Всё. Теперь можно дёргать методы, что указаны в спеке.

      Для отладки можно установить BloomRPC, добавить в импорты папку proto, и открыть файл API "*_api.proto". Теперь прям в интерфейсе можно делать запросы.

      Открыть Wav-файл в python можно, например, через "scipy" и прочитать из него сырые данные. Главное, чтобы они были в формате "16-bit integer PCM" (1 канал) и их как массив байт отправить с gRPC запросом.

      Думаю лучше подскажут люди с опытом разработки на python.

      P.S. если не хотите мучаться с чтением WAV, то можете предварительно сконвертировать файл raw pcm 16le с помощью ffmpeg примерно так:

      ffmpeg -i input.flv -f s16le -acodec pcm_s16le output.raw

      Итоговый файл будет достаточно прочитать и отправить как массив байт.