Локальный LLM на сервере клиента: Ollama vs llama.cpp, выигрыш в 3 раза
К нам обратилась торговая компания на 28 рабочих мест из ЦАО — продают промышленную химию, есть юрист, два менеджера-аналитика и финдир, который привык в ChatGPT кидать договоры с контрагентами. Им запретил это делать новый аудитор по 152-ФЗ — слишком жирные персональные данные летят к американцам. Мы за пять рабочих дней собрали им свой собственный «ChatGPT» на сервере в офисе, и в процессе переключения с Ollama на llama.cpp разогнали его в 3,1 раза. Этот кейс — пошаговый отчёт о том, как мы это сделали и где наступали на грабли.
С чем пришёл клиент и почему запретили облачный AI
Финансовый директор два года жил в мире ChatGPT Plus за 20 долларов в месяц. Бросал туда договоры, проекты писем контрагентам, варианты скриптов для 1С. Юрист увидел это случайно через плечо, скрутил в трубочку и пошёл к гендиру с распечаткой 152-ФЗ. Договор содержал ФИО, паспорта учредителей контрагента, банковские реквизиты — это полноценные персональные данные третьих лиц, которые мы не имеем права передавать в иностранную систему без отдельного согласия каждого субъекта.
Дальше последовал короткий разговор и решение: «AI нам нужен, но только свой». Аудитор по 152-ФЗ сказал чёткое: «Если вы можете показать мне сервер в этом здании, на котором обрабатываются эти запросы, и доказать, что наружу из него ничего не уходит — я подпишу заключение». Финдир позвонил мне.
До этого момента я уже разворачивал локальные LLM трижды: для юрфирмы 35 РМ, для медцентра в районе Курского вокзала и для проектного бюро на Чистых Прудах. Поэтому план работ нарисовал в первой же встрече: одна неделя, GPU-станция в их же серверной, веб-интерфейс на внутреннем порту, никакого outbound в интернет с этой машины.
Подбор железа: почему RTX 4090, а не A100 и не «облако»
Любая статья про локальные LLM в интернете начинается с фразы «вам понадобится H100 за восемь миллионов рублей». Это враньё для энтерпрайза. Малому и среднему бизнесу нужен баланс «модель толковая — железо в смете — окупаемость год».
Что я взял у этого клиента
Платформа — Supermicro SYS-740GP-TNRT в корпусе 4U. CPU — один Xeon Gold 6354 (18 ядер, 3,0 GHz). Память — 128 GB DDR4 ECC. NVMe — Samsung PM9A3 1.92 TB под систему и веса моделей. GPU — Nvidia GeForce RTX 4090 24 GB (Founders Edition). Блок питания — 1600 W титанового класса. Корпус позволяет поставить вторую такую же карту, если масштабируемся.
Сумма: 612 000 ₽ за платформу с памятью и дисками + 268 000 ₽ за GPU + 18 000 ₽ за ИБП APC Smart-UPS 1500 + НДС. Итого около 950 тысяч под ключ с настройкой. Окупаемость по чистой замене подписки на ChatGPT/Claude/GigaChat для команды из 14 человек — около 11 месяцев.
Почему не A100 и не RTX 6000 Ada
Скажу прямо: A100 80 GB сейчас на вторичке стоит 1,4-1,8 миллиона. RTX 6000 Ada 48 GB — 750-900 тысяч. Они нужны, если вам надо в одну карту запихнуть Llama 70B или Qwen 72B без квантования, либо обслуживать сотни параллельных запросов. У клиента из этого кейса в обед одновременно сидят максимум четыре человека с LLM — 4090 хватает с двукратным запасом по памяти.
Почему не облако GPU вроде Yandex DataSphere
Аргумент простой: цель проекта — увести данные из чужих рук. Yandex DataSphere — это юридически тот же «облачный API», просто в РФ. Аудитор сказал бы: «А кто гарантирует, что Yandex не сделает резервную копию модели вашего запроса для отладки?» Свой железный сервер закрывает этот вопрос за один аргумент.
Первая итерация: Ollama, 19 токенов в секунду и разочарование
Первые три дня я сделал «как все»: Ubuntu Server 24.04, драйвер Nvidia 555, Docker, контейнер с Ollama, веб-интерфейс Open WebUI. Десять команд, два часа работы, всё взлетает. Финдир заходит в браузер по адресу http://llm.local:8080, видит знакомый чат и улыбается.
Но улыбка длилась три минуты. На запросе «вычитай этот договор и предложи правки в раздел 5» Ollama выдал поток ответа со скоростью 19 токенов в секунду. На двухэкранном договоре генерация ответа заняла 1 минуту 47 секунд. Финдир сказал то, что говорят все клиенты в такой ситуации: «А ChatGPT отвечает за 8 секунд».
Что внутри Ollama притормаживает
Ollama — это обёртка над llama.cpp, написанная на Go, с менеджером моделей и сетевым API. По умолчанию она запускает движок с консервативными параметрами:
- Не использует флаг
--flash-attn— а на RTX 4090 это +30% к скорости. - Контекст в Ollama по умолчанию 2048 токенов, и при загрузке длинного промпта она пересчитывает с нуля.
- Layers offloaded to GPU считается автоматически и часто ставит
ngl=999, но при этом резервирует слишком мало под KV-cache. - Параллелизм запросов через
OLLAMA_NUM_PARALLEL=1по умолчанию — то есть второй пользователь ждёт, пока закончится первый.
Я докрутил Ollama насколько можно: переменные окружения, кастомный Modelfile, увеличил контекст до 8192. Получил 26 ток/сек. Лучше, но всё равно не торт.
Переход на llama.cpp напрямую: 58 токенов в секунду
На третий день вечером я снёс Ollama и поставил голый llama.cpp. Скомпилировал из исходников с CUDA-поддержкой, чтобы не тащить чужие сборки и точно знать, какие флаги активны.
# Ставим зависимости
apt-get install -y build-essential cmake git pkg-config \
libcurl4-openssl-dev nvidia-cuda-toolkit
# Клонируем и собираем с CUDA
git clone https://github.com/ggerganov/llama.cpp.git /opt/llama.cpp
cd /opt/llama.cpp
cmake -B build -DGGML_CUDA=ON -DLLAMA_CURL=ON
cmake --build build --config Release -j 18
# Проверяем, что собралось
./build/bin/llama-cli --version
./build/bin/llama-server --help | head -40
Дальше — модель. Я взял Qwen 2.5 32B Instruct в кванте Q4_K_M от unsloth, на тот момент это был лучший русскоязычный open-source вариант для технических и юридических задач. Размер файла — 19,8 GB, отлично укладывается в 24 GB видеопамяти RTX 4090 вместе с KV-cache.
# Скачиваем веса прямо из HuggingFace через llama-cli
mkdir -p /var/lib/llm/models
cd /var/lib/llm/models
# Q4_K_M от unsloth — 19,8 GB
wget https://huggingface.co/unsloth/Qwen2.5-32B-Instruct-GGUF/resolve/main/Qwen2.5-32B-Instruct-Q4_K_M.gguf
# Контрольная сумма обязательно
sha256sum Qwen2.5-32B-Instruct-Q4_K_M.gguf
Теперь самое интересное — запуск llama-server с правильными параметрами. Это та формула, которая мне дала 58 ток/сек:
/opt/llama.cpp/build/bin/llama-server \
--model /var/lib/llm/models/Qwen2.5-32B-Instruct-Q4_K_M.gguf \
--host 0.0.0.0 \
--port 8081 \
--ctx-size 16384 \
--n-gpu-layers 65 \
--batch-size 2048 \
--ubatch-size 512 \
--flash-attn \
--cache-type-k q8_0 \
--cache-type-v q8_0 \
--threads 12 \
--threads-batch 18 \
--parallel 4 \
--cont-batching \
--metrics \
--log-format json \
--api-key 7c4a0f...очень_длинный_ключ
Что делает каждый флаг
--n-gpu-layers 65 — выгрузить все 65 слоёв Qwen 2.5 32B в видеопамять, включая embedding. Если поставить 64 — один слой останется на CPU и скорость упадёт втрое.
--flash-attn — Flash Attention v2, экономит память KV-cache и ускоряет генерацию на 25-35% на Ada Lovelace.
--cache-type-k q8_0 --cache-type-v q8_0 — квантование KV-cache в 8 бит. Сама модель в Q4, кэш в Q8 — оптимум по памяти и точности. С таким кэшем у меня в 24 GB лезет контекст до 24K токенов с запасом.
--parallel 4 и --cont-batching — четыре одновременных запроса батчатся непрерывно, без ожидания. Это критично для офиса с несколькими пользователями.
После старта проверяю замер:
# Тест скорости
curl -X POST http://127.0.0.1:8081/v1/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer 7c4a0f..." \
-d '{
"model": "qwen-32b",
"prompt": "Перепиши пункт договора простым языком: ...",
"max_tokens": 800,
"temperature": 0.3
}' | jq '.usage, .timings'
Ответ: predicted_per_second: 58.4. Тот самый трёхкратный выигрыш по сравнению с Ollama на той же модели. Прогон договора, который раньше ждал 1:47, теперь идёт 34 секунды. Финдир прислал в Telegram три «огонь»-эмодзи и попросил «то же самое для бухгалтера».
Обвязка вокруг llama.cpp: веб-интерфейс, авторизация, журнал
Чистый llama-server даёт OpenAI-совместимый HTTP API, но людям нужен интерфейс. Я поставил перед ним Open WebUI в Docker, чтобы юристу и финдиру было привычно, как ChatGPT.
# Docker Compose файл /etc/llm/docker-compose.yml
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: webui
network_mode: host
environment:
- OPENAI_API_BASE_URL=http://127.0.0.1:8081/v1
- OPENAI_API_KEY=7c4a0f...
- WEBUI_AUTH=true
- DEFAULT_MODELS=qwen-32b
- DEFAULT_USER_ROLE=user
- ENABLE_SIGNUP=false
- ENABLE_OPENAI_API=true
- ENABLE_OLLAMA_API=false
- WEBUI_NAME=ITfresh AI
- WEBUI_URL=https://llm.client-corp.local
volumes:
- /var/lib/open-webui:/app/backend/data
restart: always
Авторизация через Active Directory
У клиента уже был AD на Windows Server 2022, который мы развернули им же годом раньше. Прикручиваю LDAP-аутентификацию в Open WebUI:
# Дополнительные переменные в docker-compose
- ENABLE_LDAP=true
- LDAP_SERVER_LABEL=corp
- LDAP_SERVER_HOST=dc01.corp.client.local
- LDAP_SERVER_PORT=389
- LDAP_USE_TLS=false
- LDAP_APP_DN=CN=svc-llm,OU=ServiceAccounts,DC=corp,DC=client,DC=local
- LDAP_APP_PASSWORD=...
- LDAP_SEARCH_BASE=OU=Sotrudniki,DC=corp,DC=client,DC=local
- LDAP_SEARCH_FILTER=(&(sAMAccountName={username})(memberOf=CN=GG_AI_Users,OU=Groups,DC=corp,DC=client,DC=local))
Доступ получают только участники группы GG_AI_Users — её наполняет администратор клиента, мы туда не лезем. Это позволяет финдиру в любой момент отозвать у уволенного сотрудника доступ к корпоративной LLM той же командой, которой он отзывает доступ к 1С.
Аудит и журналирование
Аудитор просил «доказательство, что наружу ничего не уходит». На уровне сети я закрыл outbound на этом сервере iptables-правилом — наружу можно только в локальную сеть и в зеркало пакетов Ubuntu, всё остальное ловит DROP.
# /etc/iptables/rules.v4 — выписка
-A OUTPUT -d 192.168.0.0/16 -j ACCEPT
-A OUTPUT -d 10.0.0.0/8 -j ACCEPT
-A OUTPUT -d 91.189.91.0/24 -p tcp --dport 80 -j ACCEPT
-A OUTPUT -d 91.189.91.0/24 -p tcp --dport 443 -j ACCEPT
-A OUTPUT -p udp --dport 53 -d 192.168.20.10 -j ACCEPT
-A OUTPUT -p udp --dport 53 -d 192.168.20.11 -j ACCEPT
-A OUTPUT -p udp --dport 123 -j ACCEPT
-A OUTPUT -j REJECT --reject-with icmp-host-prohibited
На уровне приложения llama-server пишет логи в JSON-формате в /var/log/llm/, плюс Open WebUI хранит все диалоги в SQLite. Я настроил Filebeat → Wazuh-аггрегатор у клиента, чтобы каждый запрос с user-id попадал в централизованный журнал. Если когда-то всплывёт вопрос «а что такого попадало в LLM», можно будет показать.
Тонкости квантования: почему Q4_K_M, а не Q5 или Q8
Эта тема всех путает. Объясню как клиенту: квантование — это сжатие весов модели с потерей точности. Чем сильнее жмёшь, тем меньше места и быстрее работает, но дурнеет ответ.
Что я тестировал
На том же сервере я погонял три кванта Qwen 2.5 32B на 30 одинаковых запросах:
- Q8_0 — 34,8 GB. Не лезет в 24 GB, часть на CPU, скорость 11 ток/сек. Качество ответа эталонное, но сервер ползает. Не подходит.
- Q5_K_M — 23,3 GB. Впритык влезает в видеопамять, но кэшу остаётся 0,7 GB и контекст обрезается до 4K. Скорость 41 ток/сек. Подходит, если нужны короткие диалоги.
- Q4_K_M — 19,8 GB. Остаётся 4,2 GB под KV-cache, контекст 16K, скорость 58 ток/сек. На моих тестах разница в качестве с Q8 — 1-2% по бенчмаркам и не заметна на реальных задачах перевода/правки/SQL.
- Q3_K_S — 14,4 GB. Скорость 71 ток/сек, но качество заметно проседает на длинных рассуждениях. Не пускаю в прод.
Вывод по моему опыту: для офиса 30-50 человек на одной RTX 4090 — Q4_K_M это золотое сечение. Если у вас две карты или RTX 6000 Ada — берите Q5 или Q6. Если денег хватило только на 4070 Ti Super 16 GB — придётся жить на Q3 или брать модель меньше: Qwen 2.5 14B Q5_K_M туда лезет.
Imatrix-квантованные веса от unsloth
Я принципиально использую кванты от unsloth, а не «обычные» от bartowski или TheBloke. У unsloth кванты сделаны через imatrix-калибровку на лучшем датасете, и в результате Q4_K_M от unsloth даёт качество как Q5 от других сборщиков. Реальная разница на юридических текстах — два-три балла из ста по моему слепому A/B-тесту с финдиром.
Сравнение скорости: цифры со стенда
Честные замеры на сервере клиента, RTX 4090, Qwen 2.5 32B, один и тот же промпт-договор на 4500 токенов входа и 800 токенов выхода:
Ollama 0.5.4, Q4_K_M, дефолт 19 ток/сек, ответ 58 сек
Ollama 0.5.4, Q4_K_M, тюнинг env-vars 26 ток/сек, ответ 41 сек
llama.cpp build 4127, Q4_K_M, голый 48 ток/сек, ответ 22 сек
llama.cpp build 4127, Q4_K_M, +flash-attn 58 ток/сек, ответ 18 сек
llama.cpp build 4127, Q4_K_M, +cache q8_0 58 ток/сек, ответ 18 сек, контекст 16K
vLLM 0.6.4, AWQ, GPTQ 52 ток/сек, ответ 20 сек, +1 час настройки
vLLM я тоже пробовал, но он капризнее, требует AWQ/GPTQ-квантованные веса (которых нет от unsloth) и в моём окружении дал на 10% медленнее. Для серверной фермы из четырёх GPU vLLM объективно лучше за счёт PagedAttention, но для одного GPU — llama.cpp проще и быстрее.
Что мы получили в итоге
Через пять рабочих дней у клиента стоит сервер в их же серверной (этаж минус один, в районе Курского вокзала), который доступен по адресу https://llm.client-corp.local через корпоративный SSL-сертификат от внутреннего CA. Заходят 14 сотрудников с компьютеров в домене, авторизуются доменными учётками, никакой регистрации.
Аудитор приехал через две недели с проверкой — мы показали серверную, диаграмму потоков данных, выписку iptables-правил, журнал из Wazuh за прошедшие 14 дней. Подписал заключение «обработка персональных данных не вышла за периметр».
Финансово: клиент платил за ChatGPT Plus + GigaChat + ещё API-доступы суммарно около 92 000 ₽ в месяц. Теперь — 0 ₽ переменных расходов, плюс электричество на сервер ~1 800 ₽/мес. Окупаемость железа — 11 месяцев. Дальше каждый месяц чистая экономия.
По производительности: 14 пользователей пользуются комфортно, в пиковые часы (10:00-12:00 и 14:00-17:00) одновременно сидят 4-5 человек, очередь не возникает за счёт --parallel 4 --cont-batching. Если станет тесно — у меня в плане докупить вторую RTX 4090 за 270 тысяч и перейти на tensor parallelism через vLLM.
Грабли, на которые я наступил
Свежий драйвер Nvidia 565 ронял CUDA
Первый день я поставил самую свежую версию драйвера на тот момент — 565. Через десять минут работы llama-server вылетал с ошибкой CUDA error: out of memory при заведомо свободной памяти. Откатился на 555.42 — стабильно как часы. Урок: на проде не ставлю последний драйвер, беру предыдущий major-релиз.
Open WebUI пытался ходить во внешний интернет
По умолчанию Open WebUI пингует api.openai.com и hub HuggingFace для проверки обновлений. У меня iptables всё резал, и интерфейс открывался 40 секунд. Решение: переменная окружения HF_HUB_OFFLINE=1 и WEBUI_AUTH_DOMAIN=disable.
Длинные контексты — KV-cache в нативном FP16 не вмещался
Без флага --cache-type-k q8_0 при контексте 16K карта забивалась под пробку и рестартила процесс. Квантование KV-cache в Q8 уменьшает занимаемую память почти вдвое без видимой потери качества — это главный трюк для длинных диалогов.
Финдир добавил в группу всех бухгалтеров — пошли тормоза
На вторую неделю клиент сам добавил в GG_AI_Users всю бухгалтерию (восемь человек) и два менеджера. Утром пришло восемь параллельных запросов, llama-server поставил их в очередь по 4. Я докрутил --parallel 8, но скорость на одного просела до 22 ток/сек. Договорились с клиентом, что когда народу станет 25+ — берём вторую карту.
FAQ: что чаще всего спрашивают клиенты
Сколько стоит развернуть локальный LLM для офиса 30 человек?
Если у заказчика уже есть подходящий сервер с GPU — около 80-120 тысяч рублей за неделю инженерных работ под ключ. Если железо нужно покупать с нуля, основная статья — видеокарта: одна RTX 4090 24 GB обходится в 240-280 тысяч рублей, плюс серверная платформа на Xeon с 64 GB RAM и NVMe — ещё 350-400 тысяч. Итого комплект под Qwen 2.5 32B Q4 — 600-700 тысяч рублей разово, против 90-120 тысяч в месяц на API OpenAI/Anthropic при сопоставимой нагрузке.
Какую модель выбрать: Qwen, Llama, GPT-OSS или что-то ещё?
На начало мая 2026 для русскоязычных задач я ставлю Qwen 2.5 32B Instruct в кванте Q4_K_M — лучший баланс размера, скорости и качества. На английских технических задачах вровень идёт Llama 3.3 70B, но она не лезет в 24 GB видеопамяти и требует CPU-offload, что роняет скорость в 4-5 раз. GPT-OSS 20B от OpenAI хорошая, но у неё слабее русский. Qwen 3 ещё сырой, ждём стабильных весов в кванте.
Чем llama.cpp лучше Ollama, если Ollama проще?
Ollama — это обёртка над llama.cpp с сетевым API и удобным менеджером моделей, но она по умолчанию использует консервативные параметры запуска и теряет на скорости. На RTX 4090 у клиента у меня llama.cpp выдавал 58 токенов/сек, Ollama — 19. Разница в три раза при той же модели и тех же весах. Если у вас Mac Studio для прототипа — Ollama нормально, на проде с правильно подобранными параметрами всегда llama.cpp напрямую.
Безопасно ли держать LLM на своём сервере вместо облачного API?
Это вообще главная причина, по которой клиенты к нам приходят. Юристам и бухгалтерам нельзя кидать в OpenAI/GigaChat договоры с персональными данными контрагентов — это утечка по 152-ФЗ. Локальный LLM работает без интернета, веса лежат на NVMe сервера, ни один токен не покидает корпоративную сеть. Аудитор приходит — показываем сетевую диаграмму с blocked outbound на 443 у GPU-ноды, и вопросов нет.
Какая нагрузка реально вытягивается одной RTX 4090 на офис?
На Qwen 2.5 32B Q4_K_M с llama.cpp одна RTX 4090 спокойно держит 4-6 одновременных диалогов с временем ответа до 8 секунд на сложный запрос. Для офиса 28-35 человек, где LLM используют 12-15 сотрудников эпизодически в течение дня, этого хватает с большим запасом. Если нужны 20+ параллельных запросов в реальном времени или Llama 70B — берите две карты в SLI или RTX 6000 Ada с 48 GB.
Итог
Локальный LLM в 2026 году — не научная фантастика и не «нужен дата-центр». Это одна неделя работы инженера, одна RTX 4090, грамотные параметры llama.cpp и веб-интерфейс на доменной авторизации. Малому бизнесу это закрывает требования 152-ФЗ и убирает 90 000 ₽/мес подписок на чужие облака. Главное — не запускать через Ollama в дефолте, а собрать llama.cpp из исходников и подобрать кванты под свою карту.
Похожая задача в вашей компании?
Расскажите, что у вас сейчас — пришлю план работ и оценку в течение рабочего дня.
Написать в Telegram или +7 903 729-62-41
Семёнов Е.С., руководитель ITfresh