MAATRIX / Блог / TabbyAPI и exllama: выжимаем максимум из одной видеокарты

TabbyAPI и exllama: выжимаем максимум из одной видеокарты

MAATRIX

Если у вас одна видеокарта — 24 ГБ, редко больше — и вы хотите поместить туда модель покрупнее, universal-движки вроде vLLM часто оказываются не лучшим выбором: они спроектированы под кластеры и батчинг многих запросов, а не под выжимание последнего гигабайта VRAM на одной карте. TabbyAPI и ExLlama — узкоспециализированная связка именно под этот сценарий: одна карта, один (много) пользователь, максимум модели и контекста в доступную память. Разберём, как это работает на практике и в чём реальная цена такой специализации.

Зачем нужен движок, заточенный под одну карту

Универсальные серверы инференса — vLLM, TGI, отчасти llama.cpp в серверном режиме — оптимизированы под пропускную способность: много параллельных запросов, tensor parallelism между несколькими картами, PagedAttention для эффективного шаринга KV-кэша между сессиями. Это правильные приоритеты для сервиса с десятками одновременных пользователей на серверном GPU-кластере.

У энтузиаста или небольшой команды с одной RTX 3090/4090 или профессиональной картой уровня L4/A4000 задача другая: не throughput на множестве запросов, а максимальная модель и максимальный контекст для одного-двух параллельных чатов на фиксированном объёме VRAM. Здесь выигрывает не универсальность, а узкая специализация:

  • квантизация с дробным числом бит на вес (не только целые Q4/Q8, но и 4.65, 5.0, 6.0 bpw) — точнее подгоняет модель под остаток VRAM после контекста;
  • квантизация самого KV-кэша (не только весов) — при длинном контексте кэш может занимать сравнимый с весами объём памяти;
  • спекулятивное декодирование с маленькой draft-моделью на той же карте — прирост скорости без второй карты;
  • отсутствие накладных расходов на инфраструктуру батчинга, которая на одной карте с одним пользователем просто не нужна.

Именно так позиционируют себя TabbyAPI (сервер) и ExLlamaV2/ExLlamaV3 (движок формата и вычислений) — не как универсальная замена vLLM, а как инструмент для другого сценария.

Что такое TabbyAPI и exllama и как они связаны

ExLlamaV2 (и более новый ExLlamaV3) — это библиотека инференса с собственным форматом квантизации (EXL2 у V2, EXL3 у V3) и CUDA-ядрами, написанными конкретно под потребительские и полупрофессиональные карты NVIDIA (Ampere и новее). Она не занимается ни загрузкой чекпоинтов из произвольных форматов, ни HTTP-сервером — только «сырой» инференс.

TabbyAPI — это OpenAI-совместимый HTTP-сервер поверх ExLlama (проект theroyallab), который берёт на себя всё остальное: приём запросов в формате /v1/chat/completions и /v1/completions, управление загрузкой/выгрузкой моделей, стриминг токенов, API-ключи, шаблоны чата (Jinja2), логирование. Разделение чёткое: exllama решает, как быстро и компактно считать тензоры на одной карте, TabbyAPI — как это отдать наружу привычным API.

Практический смысл связки: вы получаете интерфейс, совместимый с любым клиентом под OpenAI API (SillyTavern, open-webui, LibreChat, curl, собственный код), но под капотом — движок, оптимизированный именно под то железо, которое у вас реально есть, а не под гипотетический кластер из восьми H100.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть ИИ на сервере

Установка и первый запуск на одной карте

Минимальные требования: одна карта NVIDIA с актуальным драйвером и CUDA, Python 3.10-3.11, git.

git clone https://github.com/theroyallab/tabbyAPI --recurse-submodules
cd tabbyAPI

Стартовый скрипт сам создаёт виртуальное окружение и подтягивает нужный вариант зависимостей (CUDA/ROCm) по обнаруженному железу:

./start.sh

При первом запуске он предложит выбрать пресет зависимостей — для одиночной карты NVIDIA обычно подходит вариант с CUDA 12.x. После установки конфигурация живёт в config.yml (создаётся из config_sample.yml):

network:
  host: 0.0.0.0
  port: 5000

model:
  model_dir: models
  model_name: Llama-3.1-70B-Instruct-exl2-4.0bpw
  max_seq_len: 16384
  cache_mode: Q4
  gpu_split_auto: true

logging:
  log_prompt: false
  log_generation_params: false

Модель в формате EXL2 или EXL3 кладётся в models/<имя>/ — это не файл, а каталог с несколькими safetensors-шардами и config.json. Готовые квантизации для популярных моделей публикуют в HuggingFace (ищите теги exl2 или exl3), либо квантуете самостоятельно через скрипты из репозитория exllamav2/exllamav3 — но это требует калибровочного датасета и отдельной карты времени на прогон, отдельная тема.

Запуск сервера:

./start.sh

Проверка, что всё поднялось:

curl http://localhost:5000/v1/models \
  -H "Authorization: Bearer ваш_api_key"

API-ключи и admin-ключ генерируются автоматически при первом запуске и лежат в api_tokens.yml — не забудьте закрыть порт 5000 файрволом или проксировать через nginx с TLS, если сервер смотрит не только в локальную сеть.

Тонкая настройка под ограниченную VRAM

Это тот раздел, ради которого вся связка и существует — набор параметров, которые впрямую конвертируются в «влезла модель или нет» и «сколько токенов в секунду».

Квантизация KV-кэша. Параметр cache_modeFP16, Q8, Q6 или Q4. При длинном контексте (32K и больше) несжатый FP16-кэш может занимать гигабайты сверх самих весов модели. Переход на Q4 для кэша заметно экономит VRAM ценой небольшой деградации точности, обычно менее заметной, чем деградация от квантизации самих весов того же уровня — но это тоже компромисс, а не бесплатный выигрыш.

gpu_split. На одной карте gpu_split_auto: true не даёт выбора — вся модель на одном устройстве. Ручной gpu_split нужен только если у вас две карты и вы распределяете слои вручную (например, 20 и 4 ГБ на две разные по объёму карты) — сценарий на грани темы этой статьи, но опция та же.

max_seq_len. Прямой компромисс между длиной контекста и объёмом, оставшимся под сами веса модели. Если модель едва влезает при 4096 токенах контекста — поднимать его до 16384 без снижения bpw весов или включения квантизации кэша не получится, сервер откажется грузиться или упадёт по OOM при первом длинном запросе.

Спекулятивное декодирование. Ключевая фишка именно для одной карты: маленькая «черновая» модель (draft model) той же архитектуры генерирует несколько токенов вперёд, а основная модель проверяет их за один проход вместо последовательной генерации каждого токена. На одной карте это может ощутимо ускорить генерацию без второй карты — но требует места под вторую, пусть и маленькую, модель одновременно с основной:

draft_model:
  draft_model_dir: models
  draft_model_name: Llama-3.1-8B-Instruct-exl2-4.0bpw
  draft_rope_alpha: 1.0

Прирост скорости от спекулятивного декодирования сильно зависит от того, насколько черновая модель угадывает продолжения основной (похожий домен, похожий стиль) — универсальных цифр здесь нет, проверяйте на своих реальных промптах.

Формат EXL2/EXL3 и честная цена агрессивной квантизации

Главное отличие EXL2 от привычного GGUF — дробная битность на вес. Вместо фиксированных Q4/Q5/Q8 вы задаёте целевое среднее число бит на параметр (например, 4.65 bpw), а квантизатор на калибровочном датасете сам решает, каким слоям и весам оставить больше точности, а какие сжать агрессивнее. Это позволяет точнее попасть в доступный объём VRAM — не «Q4 не влезает, Q5 избыточен», а ровно тот bpw, который умещается с нужным запасом под контекст.

Здесь и находится узкая специализация под одну карту: EXL2/EXL3 в чистом GPU-инференсе на одной карте часто позволяет уместить более крупную модель или больший контекст, чем GGUF того же примерного «уровня качества» — ценой того, что формат не умеет то, что умеет llama.cpp: гибкую офлоад-выгрузку части слоёв на CPU/RAM при нехватке VRAM. Про сам механизм квантизации весов и что именно теряется на разных уровнях сжатия подробно разобрано в статье про квантование модели и что теряется в Q4 — общие принципы (потеря точности в редких токенах, деградация на длинных цепочках рассуждений, разная чувствительность разных типов задач) справедливы и для EXL2/EXL3, хотя конкретные числа bpw не совпадают с GGUF-эквивалентами напрямую.

Честный вопрос, который стоит перед каждым, кто загружает 70B в 24 ГБ через агрессивный EXL2 на 2.4-3 bpw: действительно ли сильно сжатая крупная модель выигрывает по качеству у менее сжатой модели поменьше (например, 32-34B на 5-6 bpw), которая занимает примерно тот же объём VRAM? Однозначного ответа нет — это зависит от конкретной задачи, домена, языка. Разбор уровней квантизации и того, как их выбирать под конкретный сценарий (чат, код, агенты), — в статье Q4, Q5, Q8: что выбрать. Единственный надёжный способ ответить на этот вопрос для своей задачи — прогнать оба варианта через один и тот же набор реальных промптов и сравнить ответы вручную, а не полагаться на общие рассуждения о параметрах модели. Методика такого сравнения (набор тестовых вопросов, оценка до/после) описана в статье как тестировать качество ответов своей модели.

Для кого эта связка и когда она не нужна

TabbyAPI и ExLlama — не универсальный выбор «на все случаи», а инструмент под конкретный профиль пользователя:

ПрофильПодходит TabbyAPI/ExLlamaЛучше universal-движок
Энтузиаст с одной картой 16-24 ГБ, 1-2 параллельных чатаДа — максимум модели/контекста на карту
Небольшая команда, локальный чат-ассистент без высокой конкурентностиДаЕсли нужен приоритетный SLA на много пользователей одновременно — vLLM/TGI
Продакшн-сервис с десятками одновременных запросовНет — нет эффективного батчинга под нагрузкуvLLM с PagedAttention и continuous batching
Кластер из нескольких карт с tensor parallelismЧастично — ExLlama поддерживает split между картами, но не заточен под это так, как vLLMvLLM/TGI
Нужна CPU-выгрузка части слоёв при нехватке VRAMНет — только GPUllama.cpp с офлоадом

Типичный кейс, где связка раскрывается полностью: одна карта (RTX 3090/4090, или в аренде — карта уровня L4/A4000), локальный или командный чат-бот, ролплей/творческие сценарии (сообщество ExLlama исторически активно именно там) или личный кодовый ассистент, где важна не пропускная способность, а качество ответа на конкретной карте здесь и сейчас, без переплаты за второй GPU. Если вы подбираете саму карту под инференс — общие ориентиры по VRAM под разные размеры моделей есть в статье выбор GPU-сервера для инференса LLM.

Практический план для одной карты

  1. Определите реальный бюджет VRAM: полный объём карты минус 1-2 ГБ на систему и минус объём под контекст (оцените по max_seq_len и cache_mode).
  2. Выберите 2-3 кандидата: разные модели на разном bpw, которые примерно укладываются в этот бюджет (крупная агрессивно сжатая vs средняя умеренно сжатая).
  3. Прогоните одинаковый набор из 15-20 реальных для вашей задачи промптов через каждый вариант — вручную сравните ответы, не полагайтесь на perplexity квантизатора как единственный критерий.
  4. Если разница в качестве между вариантами для вашей задачи невелика — берите тот, что даёт больше свободного контекста или скорости, а не тот, что «крупнее по паспорту».
  5. Включите cache_mode: Q4/Q6 и, если задача однотипная (одна модель для похожих запросов), протестируйте спекулятивное декодирование с draft-моделью — почти бесплатный прирост скорости при наличии свободных нескольких гигабайт VRAM.
  6. Зафиксируйте выбранную конфигурацию в config.yml и держите отдельную резервную копию с параметрами — при обновлении exllama/TabbyAPI поведение квантизации иногда меняется между версиями.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть ИИ на сервере

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Чем EXL2 принципиально отличается от GGUF?

Дробной битностью на вес (calibration-based, а не фиксированные уровни) и тем, что рассчитан только на чистый GPU-инференс без CPU-офлоада — за счёт этого ограничения он часто эффективнее на одной карте, но менее гибок при нехватке VRAM.

Можно ли запустить TabbyAPI без GPU, только на CPU?

Формально экспериментальная поддержка существует в части сборок exllama, но смысла в этом почти нет — вся специализация связки построена вокруг GPU-ядер, на CPU производительность будет заметно хуже, чем у llama.cpp, изначально спроектированного под такой режим.

EXL2 и EXL3 — это одно и то же?

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

Нужно ли квантовать модель самому или искать готовую?

Для популярных моделей почти всегда проще найти готовую EXL2/EXL3-квантизацию на HuggingFace под нужный bpw — самостоятельное квантование оправдано только для редких/дообученных моделей и требует времени на калибровочный прогон.

Заменяет ли TabbyAPI vLLM для продакшн-сервиса?

Нет — если у вас растущая нагрузка с десятками параллельных пользователей, континуус-батчинг vLLM даст лучшую суммарную пропускную способность; TabbyAPI выигрывает именно в сценарии «одна карта, один-два одновременных пользователя, максимум модели».

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →