✏️ LLM's Backends (монстр-текст)
Как встроить LLM в сервис?
Можно просто поднять на сервере контейнер, внутри запустить Python-процесс, загрузить модель через transformers и обращаться к ней из приложения.
Для одного прототипа — нормально. Но дальше появляются вопросы.
А если эта LLM нужна сразу нескольким сервисам? Каждый сервис будет отдельно грузить свою копию модели? Или все будут ходить по HTTP в один контейнер? А если запросов станет много? Как управлять доступом? Как обновлять модель? Как распределять несколько моделей между несколькими GPU?
В итоге приходим к довольно стандартному для system design решению — LLM как отдельный микросервис.
Сервисы не знают, где физически лежит модель и как она запущена. Они просто делают запрос:
Service
↓ HTTP
LLM API
↓
Model
У нас обслуживание этого слоя упрощает GPUStack.
Один раз настраиваем GPUStack и подключаем к нему GPU-серверы, а дальше можем централизованно разворачивать модели и обращаться к ним через единый OpenAI-compatible API.
Получается примерно так:
Service
↓ API
GPUStack
↓
Worker
↓
Container
↓
Inference Backend
↓
Model
↓
CUDA
↓
GPU
Но что вообще происходит внутри контейнера?
В Jupyter Notebook мы обычно делаем что-то в духе:
model = AutoModel.from_pretrained(...)
Библиотека создаёт модель, загружает её веса в RAM/VRAM, после чего мы можем запускать inference.
В production эту работу выполняет inference backend.
Inference backend — это программа, внутри процесса которой загружается и исполняется модель. Он принимает запросы, токенизирует текст, управляет памятью и KV-cache, объединяет запросы в batch, запускает вычисления на GPU и возвращает результат.
При этом одна и та же модель может работать через разные backend'ы.
Например Qwen можно запустить через vLLM, llama.cpp или SGLang.
Почему?
Потому что модель определяет, что считать, а backend — как это эффективно посчитать и обслужить запросы (и запомнить историю запросов, например)
Backend'ы отличаются поддерживаемыми архитектурами и форматами моделей, работой с VRAM, batching, KV-cache, quantization, parallelism и оптимизациями инференса.
Из тех, с которыми сталкивался:
vLLM — быстрый inference и высокий throughput. Хорошо подходит для production-нагрузки и множества параллельных запросов. При этом любит активно кушать доступную VRAM под модель и KV-cache.
llama.cpp — более гибкий вариант, особенно для квантованных GGUF-моделей. Может работать на GPU, CPU или одновременно использовать и то, и другое. Хороший выбор, когда железо ограничено и важнее экономия ресурсов, но имхо вообще не могу придумать кейсы, где скорость не важна. Если только для пэтпроект дома, на старенькой 3060 ti.
SGLang — лично пока не пробовал. По назначению это скорее конкурент vLLM: backend для быстрого production inference LLM и VLM.
В итоге схема такая (еще раз, но конкретнее):
Приложение
↓
GPUStack
↓
Worker
↓
Docker-контейнер
↓
vLLM / llama.cpp / SGLang
↓
Qwen / Llama / DeepSeek / ...
↓
GPU
И вот в этом для меня главное удобство GPUStack: прикладному сервису уже не нужно знать, на какой машине, на какой GPU и через какой backend сейчас работает модель.
Для приложения LLM превращается в обычный внешний сервис:
model + API key + endpoint
А вся работа с GPU, контейнерами и inference backend'ами остаётся внутри инфраструктурного слоя.
Как встроить LLM в сервис?
Можно просто поднять на сервере контейнер, внутри запустить Python-процесс, загрузить модель через transformers и обращаться к ней из приложения.
Для одного прототипа — нормально. Но дальше появляются вопросы.
А если эта LLM нужна сразу нескольким сервисам? Каждый сервис будет отдельно грузить свою копию модели? Или все будут ходить по HTTP в один контейнер? А если запросов станет много? Как управлять доступом? Как обновлять модель? Как распределять несколько моделей между несколькими GPU?
В итоге приходим к довольно стандартному для system design решению — LLM как отдельный микросервис.
Сервисы не знают, где физически лежит модель и как она запущена. Они просто делают запрос:
Service
↓ HTTP
LLM API
↓
Model
У нас обслуживание этого слоя упрощает GPUStack.
Один раз настраиваем GPUStack и подключаем к нему GPU-серверы, а дальше можем централизованно разворачивать модели и обращаться к ним через единый OpenAI-compatible API.
Получается примерно так:
Service
↓ API
GPUStack
↓
Worker
↓
Container
↓
Inference Backend
↓
Model
↓
CUDA
↓
GPU
Но что вообще происходит внутри контейнера?
В Jupyter Notebook мы обычно делаем что-то в духе:
model = AutoModel.from_pretrained(...)
Библиотека создаёт модель, загружает её веса в RAM/VRAM, после чего мы можем запускать inference.
В production эту работу выполняет inference backend.
Inference backend — это программа, внутри процесса которой загружается и исполняется модель. Он принимает запросы, токенизирует текст, управляет памятью и KV-cache, объединяет запросы в batch, запускает вычисления на GPU и возвращает результат.
При этом одна и та же модель может работать через разные backend'ы.
Например Qwen можно запустить через vLLM, llama.cpp или SGLang.
Почему?
Потому что модель определяет, что считать, а backend — как это эффективно посчитать и обслужить запросы (и запомнить историю запросов, например)
Backend'ы отличаются поддерживаемыми архитектурами и форматами моделей, работой с VRAM, batching, KV-cache, quantization, parallelism и оптимизациями инференса.
Из тех, с которыми сталкивался:
vLLM — быстрый inference и высокий throughput. Хорошо подходит для production-нагрузки и множества параллельных запросов. При этом любит активно кушать доступную VRAM под модель и KV-cache.
llama.cpp — более гибкий вариант, особенно для квантованных GGUF-моделей. Может работать на GPU, CPU или одновременно использовать и то, и другое. Хороший выбор, когда железо ограничено и важнее экономия ресурсов, но имхо вообще не могу придумать кейсы, где скорость не важна. Если только для пэтпроект дома, на старенькой 3060 ti.
SGLang — лично пока не пробовал. По назначению это скорее конкурент vLLM: backend для быстрого production inference LLM и VLM.
В итоге схема такая (еще раз, но конкретнее):
Приложение
↓
GPUStack
↓
Worker
↓
Docker-контейнер
↓
vLLM / llama.cpp / SGLang
↓
Qwen / Llama / DeepSeek / ...
↓
GPU
И вот в этом для меня главное удобство GPUStack: прикладному сервису уже не нужно знать, на какой машине, на какой GPU и через какой backend сейчас работает модель.
Для приложения LLM превращается в обычный внешний сервис:
model + API key + endpoint
А вся работа с GPU, контейнерами и inference backend'ами остаётся внутри инфраструктурного слоя.