Сервер локального инференса · Rust + CUDA

Быстрее llama.cpp
по всем 13 метрикам

Yttri Forge — сервер инференса GGUF-моделей на собственном форке candle и своих CUDA-ядрах. Один бинарь yforge, API OpenAI и Anthropic, окно до 256K токенов.

  • Windows и Linux
  • NVIDIA RTX 30 / 40 / 50
  • API OpenAI и Anthropic
+16 %
декод при контексте 256K
−27 %
время до первого токена
+43 %
декод с MTP-спекуляцией на 32K
−1,4 ГиБ
видеопамяти при окне 256K

RTX 4090 48 ГБ · Qwen3.8-27B Q8_0 · оба движка с окном 256K · сверка 18.09.2026

Замеры

Один GGUF, один харнесс, уникальные промпты

Сравниваем с llama.cpp b10375 на одном стенде: одна модель, один протокол, движки по очереди. Каждый прогон — новый промпт, поэтому кеш промпта не помогает ни одному движку.

Декод, токенов в секунду

Один слот, без спекуляции · больше — лучше

  • yforge
  • llama.cpp b10375
8K
30,8
30,2
+2 %
32K
30,1
29,2
+3 %
128K
27,0
24,5
+10 %
256K
23,4
20,1
+16 %

Префил, токенов в секунду

Один слот, уникальный промпт · больше — лучше

  • yforge
  • llama.cpp b10375
8K
2 179
1 826
+19 %
32K
2 184
2 100
+4 %
128K
1 683
1 677
+0,4 %
256K
1 268
1 233
+3 %

MTP: спекулятивный декод

Токенов в секунду · голова MTP, ширина 2 · больше — лучше

  • yforge без MTP
  • прирост от MTP
  • llama.cpp b10375
8K
36,6
30,5
+22 %
32K
42,8
29,4
+43 %

Прирост — к декоду yforge без MTP: 30,1 и 30,0 т/с. В сборке llama.cpp, с которой сравнивали, MTP нет. Цена — +0,16 ГиБ VRAM на 8K и +0,97 ГиБ на 32K.

Задержка и память

Меньше — лучше

  • yforge
  • llama.cpp b10375
Старт → первый токенот запуска процесса
8,09 с
10,03 с
−1,94 с
Время до первого токенаустановившееся, короткий промпт
0,250 с
0,342 с
−27 %
Видеопамятьокно 256K, пик минус фон
34,4 ГиБ
35,8 ГиБ
−1,39 ГиБ

Четыре клиента одновременно

Декод каждого из четырёх запросов, т/с · промпт — 60 % окна · «пачка» — время всех четырёх

  • yforge
  • llama.cpp b10375
yforge · 8Kпачка 8,9 с
25,5
25,5
25,5
25,5
llama.cpp · 8Kпачка 10,1 с
25,7
12,4
15,4
21,1
yforge · 32Kпачка 30,3 с
24,4
24,4
24,4
24,4
llama.cpp · 32Kпачка 31,7 с
8,7
3,0
4,4
24,4

yforge ведёт слоты одним батчем: все четыре запроса идут с одной скоростью, и пачка завершается раньше — на 12 % при 8K и на 4 % при 32K. У llama.cpp слоты получают время неравномерно: на 32K один запрос идёт на 3,0 т/с, другой — на 24,4.

Как мы меряем

Уникальные промпты

Повтор одного текста попадает в кеш промпта llama.cpp и даёт невозможные 7 781 т/с. Поэтому каждый прогон — новый текст.

Токены из usage

Считаем по usage.completion_tokens, а не по SSE-чанкам: при спекуляции один чанк несёт несколько токенов.

Движки по очереди

Одна карта, чередующиеся пары, никогда одновременно. Одиночный прогон ничего не доказывает.

Одинаковое окно

Оба движка с окном 256K. Малое -c у llama.cpp давало ложный перерасход памяти у нас — это разница конфигураций.

Воспроизвести:python3 scripts/bench/final_verify.pyхарнессы лежат в репозитории сервера, пути настраиваются в шапке скрипта

Почему быстрее

Каждый процент — отдельная правка в ядре

Мы профилируем шаг декода по ядрам, сравниваем с llama.cpp покернельно в nsys и чиним найденное. Вот правки, которые дали отрыв.

01

Split-KV по реальной длине

Декодное внимание делит работу по фактической длине KV, а не по окну пула, и укладывается ровно в одну волну на SM. До правки половина блоков простаивала.

декод 45,8 → 47,2 т/с

02

int8-QK без распаковки

Ключи читаются из int8-страниц как есть, запрос квантуется прямо в ядре, строки staging выровнены против конфликтов банков памяти.

q8-KV быстрее f16: +8,3 % на 127K

03

Страничный KV-пул в int8

Пул выделяется на всё окно при старте, и KV пишется в него сразу, без F16-копии. int8 вдвое экономит память при ошибке round-trip 0,75 %.

окно 128K в 12 ГБ: пул 2 ГБ вместо 4

04

Префикс-кеш без копий

Запись кеша — ссылка на страницы пула: поколение слота и длина. 50 МиБ вместо гигабайтной копии KV, и длинный промпт не считается заново.

повтор 127K-промпта: 138,8 → 2,3 с

05

MTP-спекуляция

Черновые токены предсказывает встроенная MTP-голова модели, основная проверяет их пачкой. Черновику хватает укороченного словаря — ошибку отсеет проверка.

+22 % на 8K, +43 % на 32K

06

Честный батчинг

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

4 × 25,5 т/с против 12,4–25,7

07

CUDA-графы и слитые ядра

Шаг декода захвачен в CUDA-граф. Residual с RMSNorm и подготовка внимания — по одному ядру, отбор top-k — за один проход.

короткий декод 51,6 → 53,9 т/с

08

Честный старт

Порт открывается только после загрузки весов, а страничный пул прогревается при старте. Первый запрос не платит за 8,2 ГиБ аллокаций.

первый запрос: 5,7 → 0,25 с

Цифры из журнала замеров проекта: RTX 3060 12 ГБ с Ornith-1.5-9B и RTX 4090 48 ГБ с Qwen3.8-27B.

Почему Rust

Один бинарь и никакого Python в рантайме

Сервер и движок написаны на Rust, горячие ядра — на CUDA. Всё собирается одной командой cargo в один исполняемый файл.

  • Один исполняемый файл

    yforge.exe на Windows, yforge на Linux. Ни Python, ни pip, ни контейнеров: нужны только драйвер NVIDIA и CUDA-рантайм, модели — обычные GGUF-файлы.

  • Память под контролем

    Буферы CUDA, графы и слоты освобождаются по правилам владения, а не ручным free. Критерий релиза — длинные генерации в четырёх слотах без падений и утечек VRAM.

  • Параллелизм без гонок данных

    Снимки префикс-кеша уходят в фоновый поток, top-k сэмплера считается параллельно в rayon, HTTP — на tokio и axum. Общий буфер без синхронизации не пропустит компилятор.

  • Весь стек в наших руках

    Форк candle, в котором мы сами правим ядра: FlashAttention-2 со страничным split-KV, MMQ на Tensor Cores, Gated DeltaNet, MoE. llama.cpp для нас — эталон сверки, а не зависимость.

  • Флаги как у llama.cpp

    -m, -c, --ngl, --fa, логи в stdout и stderr. Флаг важнее переменной окружения, окружение — env-файла. Переезд с llama-server почти не трогает скрипты.

Клиенты

  • Open WebUI
  • Codex
  • OpenCode
  • OpenAI SDK
  • Anthropic SDK
  • curl

HTTP · Bearer-ключ · порт 18099

yforgeRust · сервер
  • /v1/chat/completions
  • /v1/responses
  • /v1/messages
  • /v1/models

Планировщик слотов · префикс-кеш · сэмплер · горячая смена моделей

один процесс, без IPC

yttri-forgeфорк candle · Rust + CUDA
  • FlashAttention-2
  • страничный split-KV
  • int8-QK
  • MMQ на Tensor Cores
  • Gated DeltaNet
  • MoE
  • MTP
  • CUDA-графы

cudarc · Metal · CPU

  • NVIDIA CUDARTX 30 / 40 / 50 — прод
  • Apple Metalдля разработки
  • CPUрезервный режим

Три API на одном порту

Подключайте агентов и чаты, которые уже работают с OpenAI или Anthropic: достаточно сменить Base URL на http://<хост>:18099/v1.

  • POST /v1/chat/completionsOpenAI Chat Completions

    Стриминг SSE, tools и tool_choice, usage

  • POST /v1/responsesOpenAI Responses

    Input → output, стриминг SSE

  • POST /v1/messagesAnthropic Messages

    Стриминг, tools, заголовок anthropic-version

  • GET /v1/modelsСписок моделей

    Контекст, квант, слоты и действующие пресеты сэмплинга

Запрос к Chat Completions

curl http://localhost:18099/v1/chat/completions \
  -H "Authorization: Bearer my-secret-key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ornith-1.5-9b",
    "stream": true,
    "messages": [{"role": "user", "content": "Привет!"}]
  }'

Ключи именованные: API_KEYS='[{"key": "…", "name": "codex"}]' — удобно раздавать клиентам и отзывать по одному.

Модели

На чём проверено

Семейства Qwen 3.5, 3.6 и 3.8, Ornith и Gemma 4 в формате GGUF — от 4B до MoE 35B-A3B. Модель меняется без перезапуска: POST /admin/switch находит файлы в MODELS_DIR.

МодельТипКвантыСтендЧто проверено
Qwen3.8-27Bсверка 13/13
Гибрид DeltaNet
Q8_0UD-IQ2_XXS
RTX 4090 48 ГБ, RTX 3060 12 ГБ Финальная сверка с llama.cpp, окно 256K, MTP
Ornith-1.5-9Bпрод
Гибрид DeltaNet
Q4_K_MQ6_KMTP Q8_0
RTX 3060 12 ГБ Прод-профиль с окном 128K, пары с llama.cpp
Qwen3.6-35B-A3B
MoE, 3B активных
UD-IQ2_XXSUD-Q2_K_XL
RTX 3060 12 ГБ Эксперты в pinned RAM, горячие — в VRAM, окно 131K
Qwen3.6-27B
Гибрид DeltaNet
UD-Q2_K_XLIQ2_XXS
RTX 3060 12 ГБ Первая целевая модель: четыре слота, три API
Qwen3.5-9B
Гибрид DeltaNet
Q4_K_M
RTX 3060 12 ГБ Префикс-кеш с точками ветвления для агентов
Qwen3.5-4B
Гибрид DeltaNet, мультимодальная
Q4_K_MQ6_KQ8_0
RTX 3060 12 ГБ Текст, изображения и видео, MTP, матрица против llama.cpp
Gemma 4 E4B-it
Трансформер
Q8_0Q4_K_M
RTX 3060 12 ГБ Пресеты сэмплинга семейства, длинный префил чанками

Установка

Сборка и запуск на Windows и Linux

Единственный артефакт — исполняемый файл yforge. Первая сборка CUDA-ядер занимает около 25 минут, повторные — секунды из кэша.

  1. Инструменты

    Драйвер NVIDIA с поддержкой CUDA 12.4+, CUDA Toolkit 13.2 с архива NVIDIA, Visual Studio 2022 Build Tools и Rust с MSVC-тулчейном.

    winget install --id Git.Git -e
    winget install --id Rustlang.Rustup -e
    winget install --id Microsoft.VisualStudio.2022.BuildTools -e `
      --override "--wait --quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"
  2. Исходники

    Движок и сервер кладутся рядом: сервер подключает движок относительным путём ../yttri-forge.

    cd D:\Projects
    git clone https://github.com/askidmobile/yttri-forge.git
    git clone https://github.com/askidmobile/qwen36-server.git
  3. Сборка

    CUDA_COMPUTE_CAP под вашу карту: 86 — RTX 30xx, 89 — RTX 40xx, 120 — RTX 50xx. Результат — target\release\yforge.exe.

    call "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\Tools\VsDevCmd.bat" -arch=x64
    set CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.2
    set CUDA_COMPUTE_CAP=86
    cd qwen36-server
    cargo build --release --features cuda --bin yforge
  4. Запуск

    Рабочий профиль для карты на 12 ГБ: окно 128K, int8-пул, CUDA-графы, один слот.

    target\release\yforge.exe --model D:\Models\Ornith-1.5-9B-Q4_K_M.gguf ^
      --api-key my-secret-key --ctx 131072 --slots 1 ^
      --kv-pool q8 --cuda-graphs 1

    Процесс завершается молча? Бинарь линкуется с cudart 12.4 — поставьте CUDA\v12.4\bin первым в PATH.

  5. Как служба

    Процесс, запущенный из ssh-сессии, умирает вместе с ней — регистрируйте задачу планировщика с запуском при старте системы.

    rem run-yforge.bat
    set "PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\bin;%PATH%"
    cd /d D:\Projects\qwen36-server
    target\release\yforge.exe --env .env 1>>D:\Projects\logs\server.log 2>&1
    
    rem регистрация и запуск задачи
    schtasks /create /tn yforge /tr "cmd /c D:\Projects\run-yforge.bat" /sc onstart /ru SYSTEM
    schtasks /run /tn yforge

macOS с Metal и чистый CPU — сборки для разработки: cargo build --release --features metal или без фич. Флаги --cuda-graphs, --kv-pool и MTP относятся к CUDA-пути.

Не верьте на слово — соберите и сравните

Харнессы сравнения лежат в репозитории: final_verify.py поднимает оба движка с окном 256K и снимает те же 13 метрик.