11.4 · Локальная модель через Ollama + свои evals
Неделя 11 · Продакшен · ⏱ ~4 ч · Практика
Зачем это AI-инженеру
Заголовок раздела «Зачем это AI-инженеру»Заказчики регулярно спрашивают: «а можно, чтобы данные не уходили в облако?», «а можно дешевле на нашем железе?». Правильный ответ — не «да» и не «нет», а «вот цифры на наших evals: локальная модель даёт X% против Y% у облачной, вот латентность, вот что придётся купить». В этой главе получаем такие цифры для ассистента поддержки «Полки» и заодно разбираемся, как устроены квантизация и сервинг моделей — частый вопрос на собеседованиях.
Когда локальная модель вообще имеет смысл
Заголовок раздела «Когда локальная модель вообще имеет смысл»| Довод «за» | Комментарий |
|---|---|
| Приватность | Данные не покидают машину. Для магазина это не абстракция: сообщения покупателей с телефонами, e-mail и номерами заказов (contains_personal_data из 5.3) и снимок базы data/shop.db в проектах 1 и 4 по правилу курса обрабатывает только локальная модель. |
| Нулевая цена токена | Платим за железо и электричество, а не за запрос. Выгодно при большом ровном трафике или фоновых задачах. |
| Офлайн и независимость | Нет 529 от провайдера, нет rate limit, нет изменений модели без предупреждения. Может быть фолбэком (гл. 11.2). |
| Контроль | Фиксированная версия весов; можно дообучить (гл. 11.5). |
| Довод «против» | Комментарий |
|---|---|
| Качество | Модель на 7–8B параметров заметно слабее топовых облачных моделей в рассуждениях, следовании длинным инструкциям, tool calling и работе с длинным контекстом. |
| Скорость на длинном промпте | RAG-промпт на 4–8k токенов на обычном ноутбуке обрабатывается секунды до первого токена. |
| Эксплуатация | Память, обновления, мониторинг, масштабирование — теперь наши. |
Вывод, который важно уметь подтвердить цифрами: локальная модель хороша для узких, простых, частых задач (классификация, извлечение полей, переформулировка запроса, суммаризация короткого текста) и для данных, которые нельзя отправлять наружу; облачная — для сложного рассуждения и агентов.
Сколько памяти нужно: арифметика весов
Заголовок раздела «Сколько памяти нужно: арифметика весов»Если доводы «за» перевешивают, первый практический вопрос — поместится ли модель на нашей машине. Модель — это массив чисел (весов). Размер в памяти ≈ число параметров × байт на параметр:
Сколько байт уходит на параметр, зависит от формата хранения: FP16/BF16 — обычные 16-битные числа, Q8 и Q4 — квантизованные варианты на 8 и 4 бита (что это значит, разберём ниже):
8B параметров × 2 байта (FP16/BF16) ≈ 16 ГБ8B × ~1 байт (Q8) ≈ 8.5 ГБ8B × ~0.5–0.6 (Q4) ≈ 4.5–5 ГБ+ KV-кэш (растёт с длиной контекста) + сам процессKV-кэш — память под промежуточные результаты внимания для каждого токена контекста. Модель 8B с контекстом 4k займёт меньше, чем та же модель с контекстом 32k — разница может быть в гигабайты. Поэтому «влезает модель» ≠ «влезает модель с нашим RAG-промптом».
На Маке с Apple Silicon память общая у CPU и GPU, поэтому модель целиком работает на GPU, если помещается в доступную ей часть RAM; на машине с отдельной видеокартой модель должна поместиться в видеопамять. Ориентир по объёму памяти: 8 ГБ — модели 3–4B в Q4 (дефолт курса qwen3:4b, или явный тег вроде qwen3:4b-instruct-2507-q4_K_M), 7–8B в Q4 — только с закрытым остальным; 16 ГБ — 7–8B в Q4; 32 ГБ и больше — Q8 или модели на 14B и крупнее. Память делят модель, bge-m3, реранкер, браузер и IDE, поэтому считаем с запасом. Если модель не влезает, часть слоёв уходит на CPU, и скорость падает в разы.
Квантизация: Q4, Q8 и что значат буквы
Заголовок раздела «Квантизация: Q4, Q8 и что значат буквы»Именно квантизация позволяет 8B-модели поместиться в 5 ГБ вместо 16. Квантизация — хранение весов с меньшей точностью: вместо 16-битного числа — 8 или 4 бита плюс масштабные коэффициенты на блок весов. Аналогия: хранить цены товаров не как float64, а как небольшие целые числа с одним множителем на весь блок — компактно, но с потерей точности.
Ollama использует формат GGUF (из проекта llama.cpp, на котором Ollama построена). Названия квантизаций:
| Метка | Бит на вес (≈) | Качество | Когда брать |
|---|---|---|---|
fp16 / bf16 |
16 | эталон | исследования, если хватает памяти |
q8_0 |
8 | практически без потерь | если память позволяет |
q5_K_M |
~5.5 | очень близко к q8 | компромисс |
q4_K_M |
~4.5–5 | заметно, но умеренно хуже | частый дефолт в Ollama |
q3, q2 |
3 и ниже | деградация заметна | только когда иначе не влезает |
K — семейство «k-quants» (блочная квантизация с разной точностью для разных частей), M/S/L — вариант размера внутри. Правило практики: большая модель в Q4 обычно лучше маленькой в FP16 при той же памяти. Но потери от квантизации зависят от задачи — сильнее всего страдают точные вещи: арифметика (сумма заказа, тариф доставки по весу), строгий формат, длинный контекст. Именно поэтому их надо мерить на своих evals, а не верить таблицам.
Скорость: prefill и decode
Заголовок раздела «Скорость: prefill и decode»Память решает, запустится ли модель; скорость — можно ли ей пользоваться. Генерация идёт в две фазы:
prefill (обработка промпта) decode (генерация ответа)все 5000 токенов RAG-контекста токен за токеномпараллельно, упирается в последовательно, упираетсявычисления (compute) в пропускную способность памяти ↓ ↓определяет TTFT определяет tokens/sОтсюда сюрприз для RAG: в коротком чате локальная модель кажется быстрой (decode десятки токенов в секунду), а на промпте с 5–8 кусками из kb/ первая буква появляется через несколько секунд. Меряем обе величины: TTFT и tokens/s.
Ollama: что это и как устроена
Заголовок раздела «Ollama: что это и как устроена»Всё это — память, квантизация, prefill и decode — мы будем мерить на Ollama, через которую с первых недель курса запускаем qwen3:4b и bge-m3. Теперь разберём её устройство подробнее. Ollama — локальный сервер моделей: скачивает модели из своего реестра (ollama pull), держит их в памяти, отдаёт HTTP API на 127.0.0.1:11434. Важные для нас факты:
- OpenAI-совместимый endpoint:
base_url="http://localhost:11434/v1",api_keyлюбой (клиенту нужен, Ollama игнорирует). Код на OpenAI SDK или LiteLLM меняет только base_url и имя модели. - Есть и родной API (
/api/chat,/api/generate) с дополнительными опциями, в том числеformatдля structured output по JSON-схеме. - Контекст по умолчанию — 4096 токенов (при памяти меньше 24 ГБ, то есть почти на любом ноутбуке; мы уже встречали это в главе 3.2, гл. 3.2). Длиннее — молча обрезается. Для RAG это ловушка номер один: модель «не видит» часть кусков, и кажется, что она глупая. Увеличивается через
OLLAMA_CONTEXT_LENGTHпри запуске сервера,num_ctxвoptionsродного API или через Modelfile (PARAMETER num_ctx 16384) с созданием своей модели (ollama create). Больше контекст — больше память под KV-кэш. - Модель выгружается из памяти через 5 минут простоя (
keep_alive). Первый запрос после этого включает загрузку — исключаем его из замера латентности (прогреваем). - Модели лежат в
~/.ollama/models.
Иллюстративный пример — тот же код, что для облака:
from openai import OpenAI
local = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama") # key is ignored
resp = local.chat.completions.create( model="<model:tag from ollama list>", messages=[{"role": "system", "content": SYSTEM_RAG}, {"role": "user", "content": build_user_msg(question, chunks)}], temperature=0,)print(resp.choices[0].message.content, resp.usage)Безопасность: и программа, и модель — сторонние
Заголовок раздела «Безопасность: и программа, и модель — сторонние»- Установка и обновление Ollama (мы ставили её в главе 1.5 · Видео Карпатого + ключи API, гл. 1.5) — как любой программы: с официального сайта (на macOS — ещё Homebrew), версия не первого дня. На macOS до первого запуска проверяем подпись:
codesign -dvиspctl -a -vvна.app; на Linux и Windows ставим по официальной инструкции на ollama.com. - Сервер слушает только
127.0.0.1— так по умолчанию, так и оставляем. В интернете регулярно находят тысячи открытых серверов Ollama без аутентификации: любой может гонять модели на чужом железе, а в некоторых версиях находили уязвимости.OLLAMA_HOSTна0.0.0.0не меняем. Из Docker-контейнера (гл. 11.1) ходим наhost.docker.internal:11434. - Модели — только из официального реестра Ollama или с Hugging Face от известных авторов (организации разработчиков модели, известные квантизаторы). GGUF — формат данных, а не исполняемый код, это безопаснее pickle-файлов, но модель от неизвестного автора может быть дообучена на нужное ему поведение (например, вставлять ссылки или игнорировать инструкции). Плюс шаблон чата в Modelfile влияет на поведение.
- Лицензия модели: у open-weight моделей разные лицензии (Apache 2.0, MIT, собственные лицензии с ограничениями). Для учебного проекта это неважно, для заказчика — важно; смотрим карточку модели.
vLLM: как это делают в продакшене
Заголовок раздела «vLLM: как это делают в продакшене»Ollama — инструмент разработчика: один пользователь, один ноутбук, удобство. Когда модель обслуживает много пользователей на GPU-сервере, берут сервинг-движки вроде vLLM. Ключевые идеи (для собеседования):
- Continuous batching — запросы разных пользователей объединяются в общий батч на лету, новый запрос не ждёт окончания чужих. Это резко поднимает throughput.
- PagedAttention — KV-кэш хранится страницами, как виртуальная память в ОС, без фрагментации; влезает больше одновременных запросов.
- OpenAI-совместимый сервер, поддержка квантизованных моделей, распределение по нескольким GPU.
Компромисс: throughput (токенов в секунду на весь сервер) против латентности одного запроса. Ollama оптимизирует удобство одного пользователя, vLLM — пропускную способность.
Честное сравнение с облачной моделью
Заголовок раздела «Честное сравнение с облачной моделью»Теперь понятно, что мерить и на чём запускать; осталось поставить эксперимент так, чтобы его результату можно было верить. Эксперимент должен менять одну переменную — генератор. Всё остальное фиксировано: тот же набор eval/golden.jsonl (гл. 6.3), тот же индекс, retrieval и эмбеддинги (эмбеддинги на другие в этом же эксперименте не переключаем — это второй эксперимент и переиндексация), тот же промпт ответа, temperature=0, тот же LLM-судья (облачный, один для всех строк — иначе сравниваются судьи).
| Столбец | Как мерить |
|---|---|
| Hit rate retrieval | не меняется — контрольная цифра, что ничего не сломано; archived_in_context = 0 |
| Correctness / faithfulness | eval/run.py из гл. 6.4 |
Доля ответов с корректными ссылками [n] на kb/ |
регекс + судья |
Правильные отказы на вопросах без ответа в базе (refusal_ok, false_refusals) |
из гл. 5.3 и гл. 6.4 |
| TTFT, p50/p95 латентности, tokens/s | после прогрева, стриминг для TTFT |
| Стоимость 100 ответов | облако: eval/cost.py (гл. 6.4), формула гл. 11.2; локально: $0 API + примечание о железе |
С учётом шума (гл. 8.1): разница в 2–3 п.п. на 50 вопросах может быть случайной. Пишем число вопросов.
DeepSeek и Ollama: как это сделать у нас
Заголовок раздела «DeepSeek и Ollama: как это сделать у нас»Эта глава и так про Ollama — раздел ниже дополняет теорию тем, что понадобится, когда Ollama становится не экспериментом, а вторым рабочим провайдером рядом с DeepSeek. Факты проверены по docs.ollama.com и api-docs.deepseek.com в ноябре 2026.
Облачная строка сравнения — DeepSeek
Заголовок раздела «Облачная строка сравнения — DeepSeek»В «честном сравнении» из теории облачная модель — DeepSeek (deepseek-flash; deepseek-v4-pro — как более сильная), на наборе eval/golden.jsonl по публичной базе знаний kb/. Вопросы и база выдуманы и публичны, поэтому облаку их отдавать можно. Меняется только base_url и модель. rag-local-q4 и rag-local-q8 — наши локальные модели с увеличенным контекстом в двух квантизациях; их создаём в Практике через Modelfile:
import osfrom openai import OpenAI
cloud = OpenAI(api_key=os.environ["DEEPSEEK_API_KEY"], base_url="https://api.deepseek.com", timeout=60)local = OpenAI(api_key="ollama", base_url="http://localhost:11434/v1") # key is ignored
GENERATORS = { "deepseek-flash": (cloud, "deepseek-flash", {"extra_body": {"thinking": {"type": "disabled"}}}), "local-q4": (local, "rag-local-q4", {"temperature": 0}), "local-q8": (local, "rag-local-q8", {"temperature": 0}),}В раннере из главы 6.4 это три конфига для eval/run.py --config: eval/configs/baseline.json ("answer_model": "deepseek-flash", "answer_thinking": false) и eval/configs/local_q4.json, local_q8.json — копии baseline, в которых отличается только answer_model (rag-local-q4, rag-local-q8).
Тонкость, которая портит сравнение: у DeepSeek режим размышления включён по умолчанию, и в нём temperature молча игнорируется. Либо выключаем размышление у облачной строки (как выше), либо сравниваем «облако с размышлением» отдельной строкой — но не смешиваем. Локальные модели с размышлением (если выбранная его поддерживает) — тоже отдельная строка.
Судья — модель сильнее проверяемых и одна на все строки: deepseek-v4-pro для публичного набора; Claude — если хотим сравнить судей. На данных с личными сведениями (сообщения покупателей, снимок data/shop.db) облачный судья запрещён тем же правилом, что облачный генератор: там судья — код, ручная проверка и самая сильная локальная модель, которая помещается в память, либо оценка на демо-базе data/demo_shop.db.
OpenAI-совместимый endpoint vs родной API Ollama
Заголовок раздела «OpenAI-совместимый endpoint vs родной API Ollama»OpenAI-совместимый слой удобен для переключения, но поддерживает не всё. На ноябрь 2026 в /v1/chat/completions нет tool_choice, logprobs, n, logit_bias, user, а картинки — только base64. Если коду нужно что-то из этого (например, принудительный вызов инструмента), это различие провайдеров, а не баг. Родной API (/api/chat, официальная библиотека ollama) даёт то, чего в совместимом слое нет или неудобно:
options={"num_ctx": 16384}на запрос — контекст без отдельного Modelfile;keep_aliveна запрос (-1— держать модель в памяти,0— выгрузить сразу);- метрики по фазам в наносекундах (
load_duration,prompt_eval_duration,eval_duration,prompt_eval_cached_count) — из них TTFT без сети и tokens/s, см. глава 11.3 (гл. 11.3); - structured output по JSON-схеме через
format(Structured Outputs); в совместимом слое — черезresponse_format.
# illustrative: native Ollama call with a JSON schema (ollama Python library, pinned)from pydantic import BaseModelimport ollama
class StructuredAnswer(BaseModel): # not rag.pipeline.Answer: this is the raw model output answer: str sources: list[int] # chunk numbers [n] found: bool # False -> code returns REFUSAL and offers handoff()
r = ollama.chat(model="rag-local-q4", messages=messages, format=StructuredAnswer.model_json_schema(), options={"num_ctx": 16384, "temperature": 0})ans = StructuredAnswer.model_validate_json(r.message.content) # still validate: the schema guides, code verifiesУ DeepSeek structured output устроен иначе — JSON-режим через response_format (глава 2.2 · Structured output: Pydantic + Instructor, гл. 2.2). Если ответ ассистента структурный, сравниваем одинаковые режимы.
Эмбеддинги — только локально
Заголовок раздела «Эмбеддинги — только локально»У DeepSeek нет эндпоинта эмбеддингов (в API Reference на ноябрь 2026 его нет), поэтому эмбеддинги в этой книге — Ollama (bge-m3 через /api/embed, или /v1/embeddings через OpenAI-клиент, Embeddings) или sentence-transformers. Для этой главы правило из теории остаётся: эмбеддинги в эксперименте не меняем — переключение генератора и переиндексация — два разных эксперимента.
Ollama на Маке: нативно, не в Docker
Заголовок раздела «Ollama на Маке: нативно, не в Docker»На macOS Ollama использует GPU Apple Silicon через Metal (Hardware support), но только при нативном запуске: в Docker Desktop на macOS GPU недоступен — нет проброса GPU (FAQ). Контейнер ollama/ollama на Маке работает на CPU и в разы медленнее. Поэтому на Маке: Ollama — нативное приложение, ассистент поддержки в контейнере ходит к ней через host.docker.internal:11434 (глава 11.1 · Docker: упаковать проект, гл. 11.1). Контейнер Ollama — для Linux-сервера с GPU и для ревьюера портфолио; поддержка видеокарт на Linux и Windows описана на той же странице Hardware support.
Память, кроме весов и KV-кэша из теории:
OLLAMA_NUM_PARALLEL(по умолчанию 1) — сколько запросов модель обрабатывает одновременно; по FAQ требуемая RAM растёт какOLLAMA_NUM_PARALLEL × OLLAMA_CONTEXT_LENGTH. Параллельный прогон evals сnum_ctx=16384и 4 потоками — четыре контекста в памяти.OLLAMA_KV_CACHE_TYPE(по умолчаниюf16) — квантизация KV-кэша; работает при включённом Flash Attention и заметно экономит память на длинном контексте. Это ещё одна переменная эксперимента: меняем — отдельная строка в таблице.OLLAMA_MAX_LOADED_MODELS— сколько моделей одновременно в памяти. Две квантизации одной модели в одном прогоне — две копии весов.
Ollama Cloud — это не локально
Заголовок раздела «Ollama Cloud — это не локально»У Ollama есть облачные модели и облачный API (https://ollama.com/v1, ключ OLLAMA_API_KEY, Cloud). Запрос к облачной модели уходит на серверы, а не остаётся на нашей машине — даже если она вызывается через ту же команду ollama. Для сообщений покупателей и снимка базы это то же самое, что любое облако: нельзя. Проверяем, что в ollama list для приватных задач только локальные модели, и что base_url — localhost.
Сравнение провайдеров по тому, на что опирается эта глава
Заголовок раздела «Сравнение провайдеров по тому, на что опирается эта глава»| Что | Claude | OpenAI | DeepSeek | Ollama (локально) |
|---|---|---|---|---|
| Где обрабатываются данные | облако Anthropic | облако OpenAI | облако, серверы в Китае — никаких личных данных покупателей | своя машина |
| Цена 100 ответов | по прайсу, с кэшем | по прайсу, с кэшем | на порядок дешевле, кэш автоматический, вне пика −50% | $0 API + железо |
| Длина контекста | большая, см. docs модели | большая, см. docs модели | 1M токенов у обеих моделей | 4096 по умолчанию (при памяти меньше 24 ГБ), больше — через num_ctx, ценой памяти |
| Structured output | tool use / structured outputs | response_format с JSON-схемой |
JSON-режим (response_format) |
format с JSON-схемой (родной API), response_format (совместимый) |
| Tool calling | есть, включая принудительный выбор | есть, включая tool_choice |
есть; в режиме размышления reasoning_content надо возвращать в следующих запросах |
только у моделей с поддержкой tools; tool_choice в совместимом слое нет |
| Эмбеддинги | нет своего эндпоинта | есть | нет | есть |
temperature=0 для воспроизводимости |
см. ограничения новых моделей (глава 1.7, гл. 1.7) | работает | игнорируется в режиме размышления | работает |
| Холодный старт | нет | нет | нет (но очередь под нагрузкой) | загрузка модели после keep_alive |
Что брать для практики
Заголовок раздела «Что брать для практики»Генераторы: deepseek-flash (облако) против локальной модели в двух квантизациях; судья — deepseek-v4-pro, один для всех строк; набор — eval/golden.jsonl по публичной kb/. Затем — отдельный маленький прогон «что выбрать для приватных задач», где облако вообще не участвует: только локальные модели на снимке data/shop.db (scripts/snapshot.sh, VACUUM INTO, только чтение) — бот-аналитик проекта 1 с вопросами из questions.md.
Маршрут изучения
Заголовок раздела «Маршрут изучения»- Открываем Ollama и GitHub ollama/ollama → README: установка,
ollama run/pull/list/ps, REST API, раздел про модели и память (≈30 мин). Обращаем внимание на требования к RAM для разных размеров моделей. - В документации Ollama открываем страницу OpenAI compatibility и FAQ → как задать контекст (
num_ctx,OLLAMA_CONTEXT_LENGTH),keep_alive,OLLAMA_HOST, где лежат модели (≈25 мин). Главное — дефолт 4096 токенов контекста. - Открываем библиотеку моделей Ollama → выбираем 2 кандидата по объёму памяти своей машины (8 ГБ — 3–4B; 16 ГБ — 7–8B; 32 ГБ+ — 14B и крупнее) с поддержкой нужных функций (tools — для бота-аналитика проекта 1), смотрим доступные теги квантизации и размер (≈20 мин). Читаем карточку модели: автор, лицензия, длина контекста.
- Квантизация: в репозитории llama.cpp на GitHub находим описание квантизаций GGUF / k-quants; плюс на Hugging Face в docs Hub страница про GGUF (≈20 мин). Цель — понимать, что значат
q4_K_Mиq8_0, не формулы. - Открываем Ollama: OpenAI compatibility → списки поддерживаемых и неподдерживаемых полей для chat completions и embeddings (≈10 мин). Отмечаем всё, что использует наш код.
- Открываем Ollama: Structured Outputs, Embeddings и Tool calling (≈25 мин). Обращаем внимание, что
formatпринимает JSON-схему Pydantic-модели. - Открываем Ollama: Usage и Hardware support → метрики времени (наносекунды) и раздел про свою платформу: Metal на Маке, видеокарты на Linux и Windows (≈10 мин).
- Открываем DeepSeek: Thinking Mode → как выключить размышление и что в этом режиме игнорируется (≈10 мин): это нужно, чтобы облачная строка сравнения была честной.
- Открываем vLLM → обзор / Getting started и раздел, где описаны PagedAttention и continuous batching (≈25 мин). Цель — уметь за 2 минуты объяснить, чем vLLM отличается от Ollama.
На что обратить внимание
Заголовок раздела «На что обратить внимание»- Обрезанный контекст. Самая частая причина «локальная модель отвечает ерундой» в RAG. Проверяем:
usage.prompt_tokensв ответе ≈ ожидаемой длине промпта? Если он ровно упирается в 4096 — обрезка. - Холодный старт в замерах. Первый запрос включает загрузку модели в память. Прогреваем, затем меряем.
- Сравнение разных вещей: поменяли сразу модель и эмбеддинги, или промпт подогнали под локальную — результат не интерпретируется.
- Формат ответа. Маленькие модели хуже держат правила вроде «каждое утверждение со ссылкой [n]» и «если ответа нет в базе знаний — ответь фразой REFUSAL». Это нормально и это результат, а не баг; возможно, нужен упрощённый промпт — но тогда это отдельная строка в таблице.
- Tool calling у локальных моделей поддерживается не всеми и работает менее надёжно. Для бота-аналитика (проект 1) и агента-разборщика заказов (проект 4) это особенно заметно.
- Ноутбук в работе. На 8 ГБ даже 4B-модель в Q4 занимает заметную часть памяти, а 8B в Q4 — больше половины; на 16 ГБ 8B в Q4 с длинным контекстом — около трети. Модель греет ноутбук и сажает батарею. Во время прогона закрываем тяжёлое (браузер с десятками вкладок, IDE с индексацией, Docker), иначе латентность меряет своп, а не модель.
ollama stop <model>после работы. - Неофициальные модели «uncensored-super-v2» от неизвестных авторов — нет.
- «Бесплатно». Токены — да, но в README указываем железо и что на сервере это была бы аренда GPU.
- Ollama в Docker на Маке — без GPU, на CPU. Цифры латентности из контейнера на Маке не сравниваем с нативными.
- Облачная модель через
ollama— не локальная. Для приватных задач проверяем, что модель скачана иbase_url—localhost. - Вывод «локальная хуже — значит, всё в облако» нарушает правило приватности: сообщения с личными данными покупателей и снимок базы всё равно не уходят в облако. Выбор для них — локальная модель или оператор (
handoff), а не DeepSeek. - Неравные условия с DeepSeek: у него размышление включено по умолчанию и
temperatureв этом режиме игнорируется; облачная строка тогда — другая конфигурация, чем кажется. - Код, завязанный на
tool_choice, на Ollama через совместимый слой не заработает: этого поля там нет.
Практика
Заголовок раздела «Практика»- Проверяем Ollama, установленную в главе 1.5:
ollama --version. Если нужно обновить — на macOS с официального сайта или через brew, подпись новой версии (codesign -dv,spctl -a -vv) проверяем до первого запуска; на Linux и Windows — по официальной инструкции. Убеждаемся, что сервер слушает только loopback:lsof -nP -iTCP:11434 -sTCP:LISTEN(macOS, Linux) показывает127.0.0.1. - Выбираем модель из официального реестра по объёму памяти (8 ГБ — 3–4B, по умолчанию
qwen3:4b; 16 ГБ — 7–8B; 32 ГБ+ — 14B и крупнее) и скачиваем в двух квантизациях, если доступны (например,q4_K_Mиq8_0; на 8 ГБ Q8 для 4B ещё влезает, для 8B — нет). Записываем размер на диске (ollama list) и в памяти (ollama ps). - Быстрый тест в терминале:
ollama run <model>и 3 вопроса о правилах «Полки» без контекста («можно ли вернуть вскрытый чай?», «какая гарантия на LAMP-LIGHT-MINI?», «действует ли TEA15 вместе со скидкой?») — смотрим, как модель выдумывает без RAG. - Увеличиваем контекст под RAG-промпт: создаём
Modelfile:FROM <model:tag>PARAMETER num_ctx 16384PARAMETER temperature 0ollama create rag-local-q4 -f Modelfile(иrag-local-q8сFROMвторой квантизации). На 8 ГБ начинаем сnum_ctx 8192: контекст тоже занимает память. Проверяемollama ps— сколько теперь памяти и что модель целиком на GPU. - Подключаем к ассистенту поддержки: в
.envANSWER_PROVIDER=ollama,ANSWER_MODEL_LOCAL=rag-local-q4,OLLAMA_BASE_URL=http://localhost:11434/v1(илиhost.docker.internalиз контейнера). Переключение — переменной окружения, без правки кода. - Проверка обрезки: на 3 вопросах сравниваем ожидаемую длину промпта (подсчитываем токены) и
usage.prompt_tokensот Ollama. - Прогон набора
eval/golden.jsonlпо публичнойkb/:eval/run.pyс конфигамиbaseline.json(deepseek-flash, размышление выключено),local_q4.jsonиlocal_q8.json. Для каждой строки: correctness/faithfulness (судья —deepseek-v4-pro, один для всех строк), доля ответов с корректными ссылками, правильные отказы, p50/p95, стоимость 100 ответов. TTFT и tokens/s меряем отдельным скриптомeval/bench_local.py(прогрев, стриминг, последовательно). Трейсы в Langfuse с тегом модели (гл. 11.3): в наборе только выдуманные вопросы, им туда можно. - Строки в таблице экспериментов (гл. 8.5):
| Эксперимент | Hit rate | Correctness | Ссылки ок | p50 / p95 | Стоимость 100 отв. ||---|---|---|---|---|---|| Облако deepseek-flash | ... | ... | ... | ... | $... (пик/не пик) || Локальная <модель> q4_K_M | ... | ... | ... | ... | $0 API (CPU/GPU ?, RAM ? ГБ) || Локальная <модель> q8_0 | ... | ... | ... | ... | $0 API |
- (Для сравнения) Добавляем строку с Claude или OpenAI на тех же вопросах, если есть ключ: насколько дороже и лучше ли.
- Structured output: переводим ответ локальной модели на
formatс Pydantic-схемойStructuredAnswer(answer,sources,found) через родной API. Сравниваем долю валидных ответов и корректных ссылок со свободным текстом. - Метрики из родного API: для 10 запросов записываем
load_duration,prompt_eval_duration,eval_count/eval_duration. Сходится ли tokens/s с нашим замером через стриминг? - Разбор: 5 вопросов, где локальная ошиблась, а облачная нет. Какой тип ошибки (не нашла в контексте, нарушила формат, выдумала, перепутала «Свет» и «Свет мини», не отказала)? Записываем, можно кодами из 8.4 (гл. 8.4).
- Вывод в 3–5 предложений: где локальная модель достаточно хороша (например, переформулировка запроса
plan_query_ollamaиз гл. 5.1 — можно проверить отдельно черезeval/run_query_eval.py), где нет, и что выбрать для приватных задач. Для них сравниваем только локальных кандидатов (облако там не вариант): бот-аналитик проекта 1 сLLM_PROVIDER=ollamaна снимкеdata/shop.db(только чтение) — 10 вопросов изquestions.mdчерезscripts/run_questions.pyна двух локальных моделях;assert_data_policyне даст случайно подставить облачную. - После работы:
ollama stop <model>.
Готово, когда
Заголовок раздела «Готово, когда»- В таблице экспериментов есть строка (лучше две — разные квантизации) «локальная модель» с качеством, латентностью и стоимостью, посчитанными на том же наборе, что облачная.
- Контекст настроен под RAG-промпт и обрезка проверена по
usage. - Есть письменный вывод: где локальная модель достаточна, а где нет, с цифрами, и что берём для данных покупателей.
Тест для самопроверки
Заголовок раздела «Тест для самопроверки»Вопрос 1
Сколько примерно памяти займут веса модели на 8B параметров в FP16 и в Q4?
FP16: ≈ 8B × 2 байта ≈ 16 ГБ. Q4: ≈ 4.5–5 ГБ. Плюс KV-кэш и накладные расходы.
Вопрос 2
Почему модель, которая «влезает» в память, может не влезть с RAG-промптом на 16k токенов?
KV-кэш растёт с длиной контекста и может занимать гигабайты; память = веса + KV-кэш + процесс.
Вопрос 3
Локальная модель в RAG отвечает так, будто не видит половины кусков базы знаний. Какая самая вероятная причина в Ollama и как её проверить?
Контекст по умолчанию 4096 токенов — промпт обрезан. Проверить usage.prompt_tokens против ожидаемой длины; увеличить num_ctx (Modelfile/OLLAMA_CONTEXT_LENGTH/options).
Вопрос 4
Что обычно лучше при одинаковой памяти?
b. Обычно бóльшая модель с умеренной квантизацией лучше маленькой в полной точности, но проверять надо на своих evals.
Вопрос 5
Чем prefill отличается от decode, и какая фаза определяет TTFT?
Prefill — обработка всего промпта параллельно, decode — генерация токен за токеном. TTFT определяется prefill, tokens/s — decode.
Вопрос 6
Что не так в этом эксперименте: «переключили генератор на локальную модель, эмбеддинги тоже на другие, промпт упростили, судью поставили тоже локальный — качество упало на 20%, значит локальные модели плохие»?
Поменяно четыре переменные сразу, включая судью — результат нельзя приписать генератору. Меняют одну переменную, судья одинаковый для всех строк.
Вопрос 7
Почему не стоит запускать Ollama с OLLAMA_HOST=0.0.0.0 на ноутбуке или сервере?
Сервер без аутентификации станет доступен из сети: чужие смогут пользоваться нашим железом, а уязвимости сервера станут удалённо эксплуатируемыми. Слушать только 127.0.0.1.
Вопрос 8
Почему первый запрос к Ollama после паузы медленный, и как это учесть в замерах?
Через keep_alive (по умолчанию 5 минут) модель выгружается, и запрос включает её загрузку в память. Делать прогревочный запрос и исключать его из статистики.
Вопрос 9
Две ключевые техники vLLM, которые повышают throughput на сервере?
Continuous batching (динамическое объединение запросов в батч) и PagedAttention (страничное хранение KV-кэша без фрагментации).
Вопрос 10
Какие задачи из проектов курса — хорошие кандидаты на локальную модель, а какие — нет?
Хорошие: переформулировка запроса, классификация и маршрутизация вопроса покупателя, извлечение полей из сообщений поставщиков, короткие суммаризации, и всё, где данные нельзя выпускать наружу: сообщения покупателей с телефонами, e-mail и номерами заказов, снимок базы магазина. Плохие: агент-разборщик заказов с многошаговым tool calling, ответы со строгим цитированием длинного контекста.
Вопрос 11
Почему модели нужно брать только из официального реестра или от известных авторов, если GGUF — формат данных, а не код?
Веса определяют поведение: модель от неизвестного автора может быть дообучена на вредное поведение (вставлять ссылки, игнорировать инструкции), а шаблон чата влияет на ответы. Проверенный источник снижает риск подмены.
Вопрос 12
Контейнер ollama/ollama на Маке с Apple Silicon даёт 3 tokens/s против 40 у нативной Ollama. Почему?
В Docker Desktop на macOS нет проброса GPU: контейнер работает на CPU. На Маке Ollama запускают нативно (Metal), а контейнеры ходят к ней через host.docker.internal.
Вопрос 13
Облачная строка сравнения — DeepSeek с параметрами по умолчанию и temperature=0. Что не так с этой строкой?
У DeepSeek размышление включено по умолчанию, и в этом режиме temperature игнорируется: строка — «облако с размышлением», дороже и медленнее, и не та конфигурация, что записана в отчёте. Выключить размышление (thinking: disabled) или сделать две отдельные строки.
Вопрос 14
Почему эмбеддинги в этой книге считает Ollama, а не DeepSeek, и почему в этом эксперименте их всё равно нельзя менять?
У DeepSeek нет эндпоинта эмбеддингов, а у Ollama есть (/api/embed). В эксперименте меняется только генератор: смена эмбеддингов требует переиндексации и меняет retrieval — это второй эксперимент.
Что дальше
Заголовок раздела «Что дальше»Мы увидели, где маленькая модель проигрывает. Следующий вопрос — можно ли её подтянуть дообучением: в главе 11.5 · Файнтюнинг: базовый пример LoRA разберём, когда это оправдано, а когда задачу решает промпт или RAG.