curl; jq; файл весов или доступ к реестру моделей; nexa-bridge (репозиторий NexaEthos/nexa-bridge на GitHub), если машина с моделью стоит за NAT
curl и jq — чтобы снимать HTTP-коды, время ответа и разбирать JSON.NexaEthos/nexa-bridge. Проброс портов и белый IP не требуются.Чек-лист перед стартом:
ollama --version печатает строку с версиейcurl --version и jq --version отвечают без ошибок.gguf-файлуПример: ollama --version → строка ollama version is <номер>; если команда не найдена — установщик не попал в PATH, перезапустите терминал.
Что сделать: запустить сервер, подключить веса 0.8B-модели и отправить один запрос.
Зачем: миллисекунды можно мерить только на живом процессе, который держит веса в памяти. Всё дальнейшее — обёртки над этим портом, поэтому сначала база.
Как проверить: сервер отвечает на HTTP, модель возвращает осмысленный текст.
ollama serve
Во втором терминале:
ollama pull <тег-модели>
ollama run <тег-модели> "Извлеки город из адреса: Москва, ул. Тверская, 1"
Пример: на экране сначала идёт pulling manifest, затем ответ вида Москва. Если такой задачи в карточке модели нет — сверьтесь с документацией, какие форматы промпта она ожидает.
Вариант на llama.cpp, если у вас файл весов:
llama-server -m ./<файл-модели>.gguf --host 127.0.0.1 --port 8080 -c 2048
curl -s http://localhost:8080/health
Ожидаемый вывод: {"status":"ok"}.
Почему это работает: llama-server загружает веса один раз и отдаёт HTTP-интерфейс, поэтому каждый следующий запрос не тратит время на чтение файла с диска. Именно на этом шаге появляется первая честная цифра — время до ответа без сетевого плеча.
Первый запуск локальной модели вручную (установка, выбор квантования, проверка железа) разобран пошагово в гайде «Как запустить ИИ-модель локально на своём компьютере».

Что сделать: перевести клиента на путь /v1/chat/completions, а если машина с моделью спрятана за NAT — поставить перед ней nexa-bridge.
Зачем: единый формат запроса позволяет мерить локальную и облачную модель одним и тем же кодом и переключать их переменной окружения, не правя приложение.
Как проверить: эндпоинт отдаёт список моделей, а тестовый запрос возвращает JSON с ответом.
export OPENAI_BASE_URL=http://localhost:11434/v1
export OPENAI_API_KEY=ollama
curl -s "$OPENAI_BASE_URL/models" | jq -r '.data[].id'
Ожидаемо: список идентификаторов, среди них <тег-модели>. Ключ ollama — заглушка: локальный сервер не проверяет авторизацию, но клиенты требуют непустое значение.
Пример запроса из Python:
import os, time, requests
base = os.environ["OPENAI_BASE_URL"].rstrip("/")
payload = {
"model": "<тег-модели>",
"messages": [{"role": "user", "content": "Извлеки город: Москва, ул. Тверская, 1"}],
"stream": False,
}
t0 = time.perf_counter()
r = requests.post(f"{base}/chat/completions", json=payload, timeout=30)
print(r.status_code, round((time.perf_counter() - t0) * 1000), "ms")
print(r.json()["choices"][0]["message"]["content"])
Ожидаемый результат: 200 и число миллисекунд, затем текст ответа. Если вместо этого пришёл 404 — вернитесь к шагу 1: имя модели в запросе не совпадает с тем, что реально лежит на сервере.
Если модель стоит на машине за NAT, наружу её выводит nexa-bridge — OpenAI-совместимый прокси на Rust с исходящим туннелем для vLLM, Ollama, LM Studio и llama.cpp. Машина остаётся за NAT, открывать порты и настраивать проброс не нужно: соединение идёт изнутри наружу. Команды установки и конфиг лежат в README репозитория NexaEthos/nexa-bridge — сверьтесь с документацией, в исходном материале их нет.

Что сделать: снять 20 замеров на реальном промпте вашего сценария и посчитать p50 и p95.
Зачем: «отвечает за миллисекунды» — это не свойство модели, а результат замера на вашем железе, вашей длине контекста и вашем промпте. Одно число без методики ничего не доказывает.
Как проверить: у вас есть min/median/max по 20 запросам и понятно, укладывается ли p95 в бюджет задачи.
for i in $(seq 1 20); do
curl -s -o /dev/null -w "%{time_starttransfer}\n" \
"$OPENAI_BASE_URL/chat/completions" \
-H "Content-Type: application/json" \
-d '{"model":"<тег-модели>","stream":true,"messages":[{"role":"user","content":"Извлеки город: Москва, ул. Тверская, 1"}]}'
done | sort -n | awk '{a[NR]=$1} END {print "min="a[1], "median="a[int((NR+1)/2)], "max="a[NR]}'
Пример вывода: min=0.061 median=0.078 max=0.193 — секунды; ваши числа будут другими, важно соотношение median и max. Флаг "stream":true обязателен: при false метрика time_starttransfer фиксируется только после генерации всего ответа и показывает не время до первого токена, а полное время.
| Сценарий | Что мерить | Условие приёмки |
|---|---|---|
| Маршрутизация и классификация запроса | p95 на коротком промпте | p95 укладывается в бюджет сценария, метка верная на вашей выборке |
| Извлечение полей (адрес, сумма, дата) | p50 и доля корректных разборов | качество проверено на 20–30 своих примерах |
| Автодополнение в редакторе | время до первого токена | задержка не ощущается при наборе текста |
| Длинная генерация | полное время ответа | модель не обрывает ответ на вашем лимите контекста |
Кому такая модель нужна по итогам замера: тем сценариям, где p95 укладывается в бюджет и где задача — короткая нормализация или извлечение, а не длинное рассуждение. Если узкое место — не скорость, а приватность, полный селф-хост большой модели не обязателен: в разборе про PII-фильтр используется self-hosted guardrails-llm-filter от Cloud.ru (Apache-2.0) как прокси перед облачной моделью, в режиме enforce он заменяет персональные данные плейсхолдерами, в detect — только пишет в журнал. Что вообще есть на рынке моделей под другие задачи — в подборке «Лучшие нейросети и ИИ-модели 2026».
Проблема. Модель не влезает в память при запуске
Error: model requires more system memory (3.1 GiB) than is available (2.0 GiB)llama-server ... -c 1024), взять более компактное квантование из карточки модели и проверить, что память не занята прошлым процессом: ollama ps, затем при необходимости остановить лишнюю модель.Проблема. Клиент не видит сервер
curl: (7) Failed to connect to localhost port 11434 after 0 ms: Couldn't connect to server8080 у llama-server).ollama serve) и сверить переменную OPENAI_BASE_URL с фактическим портом: curl -s http://localhost:11434/api/tags | jq.Проблема. Запрос приходит, но модель не находится
{"error":{"message":"model \"<тег-модели>\" not found, try pulling it first","type":"api_error"}}curl -s http://localhost:11434/api/tags | jq -r '.models[].name') и подставить точный в "model", либо скачать веса: ollama pull <тег-модели>.Модель на 0.8B нужна там, где замер p95 на вашем промпте укладывается в бюджет задачи, а качество проверено на собственной выборке, — остальным достаточно облака с PII-фильтром. Следующий шаг: прогнать 20–30 реальных примеров через /v1/chat/completions и зафиксировать в тикете медиану и p95 вместе с версией весов.
Jeff 0.8B — это сколько параметров и хватит ли ноутбука?
0.8B — примерно 0.8 млрд параметров, это заметно меньше, чем 27B-модель Qwen 3.8 весом 17 ГБ, которая работает на ноутбуке. Точные требования к памяти и размер файла сверьте с карточкой модели.
Чем маленькая модель лучше облачной?
Она отвечает локально, без сетевого плеча до провайдера, поэтому время ответа зависит только от вашего железа и длины промпта. Но это преимущество нужно подтвердить замером на своём сценарии, а не переносить на свою задачу чужие цифры.
Нужно ли открывать порты, если машина с моделью за NAT?
Нет. nexa-bridge — прокси на Rust с исходящим туннелем: соединение устанавливается изнутри, проброс портов и белый IP не требуются. Порядок запуска — в README репозитория.
Что делать, если качество 0.8B не хватает, а данные нельзя отдавать в облако?
Вариант из практики — оставить облачную модель, но поставить перед ней PII-фильтр: guardrails-llm-filter в режиме enforce маскирует персональные данные плейсхолдерами. Учтите риск из того же разбора: при сбое маскирования запрос может уйти без обработки, поэтому такой сбой надо отслеживать как инцидент.