8.3 · Эксперимент: модели и реранкер
Неделя 8 · Evals · ⏱ ~4–5 ч · Проект
Зачем это AI-инженеру
Заголовок раздела «Зачем это AI-инженеру»Выбор модели — решение, которое инженер принимает постоянно, и почти никогда ответ не «самая сильная». Задача — найти конфигурацию, которая достаточно хороша при приемлемой цене и задержке. Вопрос «как выбирали модель?» на собеседовании ждёт ответа с цифрами: «маленькая модель с реранкером даёт ту же correctness в пределах шума, в 5 раз дешевле и вдвое быстрее».
Три оси: качество, цена, скорость
Заголовок раздела «Три оси: качество, цена, скорость»В 8.2 мы меняли то, как ассистент ищет. Теперь индекс-победитель фиксируем и меняем две вещи вокруг него: модель, которая пишет ответ, и реранкер между поиском и моделью. Сравнивать конфигурации только по качеству здесь мало: у каждой три числа:
- качество — correctness, faithfulness, hit@k на нашем наборе
eval/golden.jsonl; - цена — стоимость 100 вопросов (только система, без судьи);
- скорость — латентность p50 и p90 от вопроса до готового ответа.
Обычно они тянут в разные стороны. Задача — не максимизировать одно, а найти фронт Парето: конфигурации, у которых нельзя улучшить одну ось, не ухудшив другую. Всё, что хуже какой-то другой конфигурации по всем осям, отбрасывается сразу.
качество ▲ │ ● большая + реранкер │ ● маленькая + реранкер │ ● большая │ ● маленькая │ ✕ большая, длинный промпт (хуже и дороже) └──────────────────────────────────────► ценаСначала требования, потом выбор
Заголовок раздела «Сначала требования, потом выбор»Фронт Парето обычно содержит несколько точек, и выбрать среди них можно только по требованиям. Без требований выбрать нельзя: «лучшая» конфигурация зависит от того, что важно. Записываем их до эксперимента, как продуктовые требования к ассистенту поддержки «Полки»:
- порог качества: correctness не ниже baseline минус разброс; faithfulness ≥ 0,9 (ассистент поддержки не должен выдумывать сроки возврата, тарифы доставки и гарантию);
archived_in_context= 0; - бюджет: например, не дороже $X на 1000 вопросов;
- латентность: p90 не больше N секунд (бот в Telegram, покупатель ждёт).
Правило выбора: самая дешёвая (или самая быстрая) конфигурация, которая проходит порог качества. Это сразу превращает спор «какая модель лучше» в проверку условий.
Как читать внешние рейтинги
Заголовок раздела «Как читать внешние рейтинги»Требования записаны; теперь нужны кандидаты. Прогонять полный eval на десятке моделей дорого, поэтому короткий список составляем по внешним рейтингам.
Artificial Analysis — сравнивает модели по агрегированным бенчмаркам качества, цене за токены и скорости (время до первого токена, токены в секунду) у разных провайдеров. Полезно для короткого списка кандидатов и оценки порядков цен и скорости.
LMArena — рейтинг по попарным слепым голосованиям людей в чате. Отражает, какие ответы людям нравятся в общем разговоре; на него влияют стиль, длина и форматирование.
Ни один из них не отвечает на наш вопрос: как модель работает на нашей задаче (ответ по найденным кускам базы знаний kb/, с цитатами, с отказом, когда ответа нет). Модель, лидирующая в рейтинге, может хуже следовать инструкции «отвечай только по базе знаний» и чаще выдумывать. Рейтинги — для выбора кандидатов, собственный eval — для решения.
Из чего складывается стоимость
Заголовок раздела «Из чего складывается стоимость»Рейтинги дают цену и скорость только порядками: за токены и на чужом железе. Для решения считаем обе оси сами, на своём пайплайне, и для этого надо понимать, из чего они складываются.
cost per question = Σ по вызовам: input_tokens × price_in + output_tokens × price_outДля RAG основной вклад — входные токены: системный промпт плюс k кусков контекста. Поэтому:
- k=10 против k=5 почти удваивает стоимость ответа;
- реранкер, сокращающий 20 кандидатов до 4 лучших, может уменьшить стоимость генерации, даже если сам стоит денег;
- prompt caching снижает стоимость повторяющейся части (системного промпта), но не кусков, которые разные для каждого вопроса;
- переформулировка запроса из 5.1 — ещё один вызов модели, тоже считается.
Цены берём со страниц цен провайдеров на дату эксперимента (они меняются, и у разных моделей одного провайдера отличаются в разы) и записываем дату в журнал. Модели называем точными идентификаторами из документации.
Из чего складывается латентность
Заголовок раздела «Из чего складывается латентность»latency = rewrite + retrieve + rerank + (time to first token + output_tokens / tokens_per_sec)- Маленькие модели обычно быстрее и по времени до первого токена, и по скорости генерации.
- Реранкер-кросс-энкодер на CPU для 20 пар может занимать заметное время; API-реранкер добавляет сетевой вызов.
- Длинный ответ медленнее: выходные токены дорогие и по цене, и по времени.
- Латентность API шумит по времени суток. Меряем варианты вперемешку, а не «утром A, вечером B».
Если продукт стримит ответ (как обычно делают в чатах), для покупателя важнее время до первого токена; для пакетных сценариев — полное время.
Реранкер: что он меняет
Заголовок раздела «Реранкер: что он меняет»Второй фактор эксперимента влияет на все три оси сразу. Как работает реранкер (cross-encoder против bi-encoder), разобрано в 5.2: поиск отбирает много кандидатов, модель-реранкер оценивает каждую пару «вопрос — кусок» и оставляет лучшие. Здесь важно, что он меняет в экспериментальном смысле:
- Поиск: широкий отбор (топ-20/30 гибридом), затем реранкер выбирает топ-k. Растут MRR и hit@3/hit@5 при той же итоговой длине контекста.
- Генерация: модель получает более точный контекст, меньше отвлекающих кусков (например, гарантия «Свет» вместо нужной «Свет мини»). Маленькой модели это обычно помогает сильнее, чем большой, потому что она хуже игнорирует мусор в контексте. Это гипотеза, её и проверяем.
- Цена/скорость: локальный реранкер ничего не стоит в деньгах, но стоит времени; API-реранкер — наоборот.
Локальный кросс-энкодер через Sentence Transformers (иллюстративно):
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3") # multilingual; English ms-marco models are smaller but fail on Russian
def rerank(question: str, chunks: list[dict], top_k: int) -> list[dict]: ranked = reranker.rank(question, [c["text"] for c in chunks]) # sorted by score return [chunks[r["corpus_id"]] for r in ranked[:top_k]]Учитываем язык: многие популярные кросс-энкодеры (cross-encoder/ms-marco-*) обучены на английском, а база знаний и почти все вопросы у нас на русском. Проверяем срез по тегам ru/en из набора.
Дизайн эксперимента: 2×2
Заголовок раздела «Дизайн эксперимента: 2×2»Осталось соединить оба фактора в один эксперимент. Два фактора (модель ответа, реранкер) — четыре конфигурации:
| без реранкера | с реранкером | |
|---|---|---|
| маленькая модель | S | S+R |
| большая модель | L | L+R |
Это лучше, чем две отдельные пары, потому что видно взаимодействие: например, реранкер почти не нужен большой модели, но сильно помогает маленькой. Тогда S+R может оказаться на фронте Парето.
Что фиксировано во всех четырёх: индекс (лучший из 8.2), промпт ответа, судья, набор, k на входе в модель.
Судья не меняется. Если судья из того же семейства, что одна из моделей-кандидатов, учитываем самопредпочтение (из 6.1): проверяем на своей разметке, нет ли перекоса.
Промпт ответа может по-разному подходить разным моделям. Честный вариант — один промпт для всех; если маленькая модель явно ломает формат (например, не ставит ссылки на kb/), это результат, а не повод тайно подкручивать промпт только ей. Отдельная настройка промпта под модель — отдельный эксперимент.
Подсчёт и представление
Заголовок раздела «Подсчёт и представление»Скрипт eval/run.py из 6.4 уже пишет usage и латентность по вопросу, а в summary.json — агрегаты. Для эксперимента нужна сводная таблица:
| Конфиг | hit@5 | correctness | faithfulness | $ / 100 вопросов | p50, с | p90, с |
|---|---|---|---|---|---|---|
| S | (разброс) | |||||
| S+R | ||||||
| L | ||||||
| L+R |
И парные сравнения ключевых пар (L против S+R, S против S+R) по правилу значимости из 8.1. Полезный график — точечная диаграмма «качество против цены» с подписями точек: на ней фронт Парето виден сразу.
Рекомендация
Заголовок раздела «Рекомендация»Таблица и график — материал, а не ответ: тот, кто решает, ждёт одну конфигурацию и её цену. Финальный продукт задачи — рекомендация в 5–8 предложениях, например в форме:
Для продакшена берём S+R: correctness 0,8x (разброс ±0,02), в пределах шума от L+R (расхождения 5/4, p ≈ 1), faithfulness 0,9x, стоимость в N раз ниже ($a против $b на 100 вопросов), p90 латентности x с против y с. Реранкер дал маленькой модели +Z п.п. correctness (расхождения 10/2), большой — в пределах шума. Ограничения: dev-набор 56 вопросов, мелкие различия не различимы; на comparative-вопросах (n=8) L+R выглядит лучше, проверить на расширенном наборе. Финальная цифра на holdout: …
«Расхождения 5/4» — как и в 8.2: 5 вопросов, где выиграла одна конфигурация, и 4, где другая. Здесь же финальную выбранную конфигурацию впервые прогоняем на holdout (один раз) — это та честная цифра, которая пойдёт в README.
DeepSeek и Ollama: как это сделать у нас
Заголовок раздела «DeepSeek и Ollama: как это сделать у нас»На нашем стеке этот эксперимент становится самым интересным в Проекте 3: локальная модель против DeepSeek, с реранкером и без. Вопрос не академический. Набор и kb/ публичные и выдуманные, поэтому их можно отдавать облаку; а вот настоящие сообщения покупателей с телефоном, e-mail или номером заказа contains_personal_data из 5.3 отправляет только локальной модели (или оператору). Эксперимент показывает, сколько качества стоит эта приватность: достаточно ли хорошо локальная модель отвечает такие сообщения или их честнее сразу передавать человеку. Сверено в ноябре 2026 по api-docs.deepseek.com, docs.ollama.com и ollama.com/library; id моделей, цены и теги проверяем перед экспериментом.
Кандидаты по объёму памяти
Заголовок раздела «Кандидаты по объёму памяти»«Маленькую» модель определяет память машины, на которой будет работать локальный ответчик, вместе с bge-m3 и реранкером:
| Роль в дизайне 2×2 | Модель | Комментарий |
|---|---|---|
| S (маленькая) | локальная модель класса 4B в Ollama, по умолчанию курса qwen3:4b (веса ≈2,5 ГБ по странице модели, ноябрь 2026) |
помещается даже в 8 ГБ рядом с эмбеддингами и реранкером; бесплатно; сообщения покупателей не покидают машину. Тег пишем явно: просто qwen3 — это qwen3:8b (≈5,2 ГБ) |
| L (большая) | deepseek-flash, thinking выключен |
дёшево, контекст 1M, на длинном контексте быстрее локальной модели на обычном ноутбуке |
| (опционально) L+ | deepseek-v4-pro или deepseek-flash с thinking |
что даёт ещё более сильная модель или рассуждения и сколько это стоит |
| (для сравнения) | одна модель Claude и/или OpenAI | на тех же публичных вопросах: где DeepSeek на фронте Парето относительно рынка |
По памяти: 8 ГБ — модели 3–4B, и S остаётся qwen3:4b; 16 ГБ — 7–8B, можно добавить qwen3:8b как отдельную точку S′; 32 ГБ и больше — 14B и крупнее. Основной S — qwen3:4b на любой машине, чтобы результат воспроизводился; более крупная локальная модель — отдельный прогон, его латентность не смешиваем с остальными. Перед локальными прогонами закрываем всё лишнее (браузер с десятками вкладок, IDE с индексацией, Docker): модель, реранкер и другие программы делят память, и при нехватке мы получим латентность, которая меряет своп, а не модель.
Конфиги для eval/run.py --config eval/configs/<name>.json отличаются только моделью и реранкером (retrieve_k — новый ключ: сколько кандидатов гибридного поиска уходит в реранкер):
{"index": "index_headings.db", "rewrite": true, "retriever": "hybrid", "retrieve_k": 20, "k": 5, "reranker": "BAAI/bge-reranker-v2-m3", "answer_model": "qwen3:4b", "answer_thinking": false, "answer_prompt": "answer_v2", "judge_model": "deepseek-v4-pro", "judge_prompt": "judge_v2"}Это eval/configs/s_rerank.json; s.json — то же с "reranker": null, l.json и l_rerank.json — с "answer_model": "deepseek-flash". Индекс, промпт и k берём из победителя 8.2. Имя папки прогона eval/runs/<timestamp>_<index>_<retriever>_k<k>/ модель не содержит, поэтому S и L различаем по config.json.
Судья не меняется — и именно здесь самопредпочтение опасно
Заголовок раздела «Судья не меняется — и именно здесь самопредпочтение опасно»Судья во всех конфигурациях — deepseek-v4-pro без thinking (глава 6.4 · Скрипт оценки: retrieval + faithfulness). Локальная 4B-модель судьёй быть не может: она слабее обоих ответчиков. Но судья из семейства DeepSeek сравнивает ответ DeepSeek с ответом qwen — ровно тот случай, где самопредпочтение искажает вывод «S против L». Поэтому:
- вопросы, где S и L разошлись (обычно 10–20), размечаем сами, не глядя на вердикты судьи, и считаем согласие судьи отдельно на этих вопросах;
- (опционально) прогоняем на них же судью другого семейства — Claude или OpenAI (данные публичные).
Если судья систематически оценивает DeepSeek лучше, чем наша разметка, — это результат, его пишут в журнал, а не прячут.
Честное сравнение: контекст, прогрев, очередь
Заголовок раздела «Честное сравнение: контекст, прогрев, очередь»У облачной модели и локальной разные ловушки, и каждая может «подарить» победу не той стороне:
- Контекст. Ollama при видеопамяти меньше 24 ГБ (на Mac — общей памяти) по умолчанию даёт модели 4k токенов. Системный промпт + 5 кусков по 600–800 токенов + вопрос туда не помещаются, хвост молча обрезается, и S проигрывает не из-за ума, а из-за обрезки. Поднимаем контекст (
OLLAMA_CONTEXT_LENGTHпри запуске сервера илиnum_ctxв нативном API; через OpenAI-совместимый эндпоинт размер контекста задать нельзя) до нужного, но при 8–16 ГБ осторожно — например 8k — и проверяемollama ps: CONTEXT и то, что модель целиком на GPU (PROCESSOR). Если не влезает, это тоже результат: «на 8 ГБ S работает только с k=3». - Прогрев. Ollama выгружает модель через 5 минут без запросов (
keep_alive), и первый запрос включает загрузку модели в память. Делаем прогревочный запрос и не учитываем его в латентности. - Очередь. По умолчанию Ollama обрабатывает запросы к модели по одному; параллельный прогон меряет ожидание в очереди. Локальную конфигурацию гоняем последовательно.
- Сеть и часы пик DeepSeek. Латентность DeepSeek включает сеть до серверов в Китае и нагрузку; цена зависит от окна пик/вне пика. Гоняем конфигурации вперемешку и в одном окне цен.
- Квантизация. Теги Ollama по умолчанию — квантизованные веса. Записываем точный тег (
qwen3:4b, а не «qwen 4B»): другой тег — другая модель. - Режим рассуждений.
qwen3в библиотеке Ollama помечена как модель с thinking. Сравниваем S и L в одинаковом режиме (обе без рассуждений,"answer_thinking": false) или делаем режим отдельным фактором; как выключить рассуждения у конкретной модели в Ollama, смотрим в её документации (параметрthink/reasoning_effort, поддержка зависит от модели).
Стоимость: что писать для локальной модели
Заголовок раздела «Стоимость: что писать для локальной модели»У Ollama $0 за токены, но «бесплатно» не значит «без цены». В таблице пишем для S «$0 API» плюс латентность на своём железе с указанием объёма памяти и пометку «модель на машине магазина». На графике «качество против цены» S окажется на нулевой цене; добавляем второй график «качество против p90 латентности» — там картина честнее. Для DeepSeek считаем eval/cost.py из главы 6.4 (кеш-попадания, пик/вне пика). Порядок для L без thinking: ответ ~3500 входных + 300 выходных токенов — около $1,4 на 1000 вопросов в пик и вдвое меньше вне пика (оценка по ценам из 6.4, без кеш-попаданий; сверяем с фактом).
Реранкер и память
Заголовок раздела «Реранкер и память»Реранкер остаётся локальным кросс-энкодером из sentence-transformers, как в 5.2: BAAI/bge-reranker-v2-m3 (RERANK_MODEL). Английский cross-encoder/ms-marco-MiniLM-L6-v2 занимает мало памяти, но для русских вопросов не годится; многоязычные кросс-энкодеры заметно крупнее. На 8 ГБ рядом с Ollama и bge-m3 проверяем, что всё помещается, и меряем время реранкера отдельно. Скачиваем модели только из официальных организаций на Hugging Face и закрепляем ревизию.
Сравнение провайдеров для этой главы
Заголовок раздела «Сравнение провайдеров для этой главы»| Ось сравнения | Claude | OpenAI | DeepSeek | Ollama (qwen3:4b) |
|---|---|---|---|---|
Качество на RAG по kb/ |
сильные модели, проверяем на своём наборе | то же | Flash/Pro — проверяем на своём наборе | заметно слабее; чаще ломает формат и отказы |
| Цена | по странице цен | по странице цен | центы на сотню вопросов; пик и вне пика | $0 API, платим памятью и временем |
| Латентность | сеть + нагрузка | сеть + нагрузка | сеть до Китая + нагрузка | своя машина: прогрев, контекст, очередь, своп |
| Контекст | большой | большой | 1M токенов | 4k по умолчанию при памяти меньше 24 ГБ, поднимать осторожно |
| Приватность | облако | облако | серверы в Китае | всё локально |
| Где на фронте Парето вероятнее | качество | качество | цена/качество | только «приватно и офлайн» |
Последняя строка — гипотеза, а не вывод: эксперимент как раз и проверит, есть ли у локальной 4B-модели место на фронте для публичных вопросов о правилах магазина или её ниша — только сообщения с личными данными покупателей и офлайн.
Маршрут изучения
Заголовок раздела «Маршрут изучения»- Открываем Artificial Analysis → страницу сравнения моделей (≈20 мин). Находим 2–3 кандидата: одну сильную модель и одну-две маленькие/дешёвые у используемого провайдера. Выписываем цену за 1M входных/выходных токенов, время до первого токена, токены в секунду. Сверяем цены со страницей цен самого провайдера.
- Открываем LMArena → лидерборд (≈10 мин). Смотрим, где наши кандидаты. Записываем, почему рейтинг людей в чате ≠ качество ответа по базе знаний.
- Открываем Sentence Transformers: Cross Encoder usage (≈15 мин). Метод
rank, порядок результатов, заметка про масштаб оценок. Смотрим список предобученных кросс-энкодеров на sbert.net, есть ли многоязычные. - Перечитываем в Anthropic: Contextual Retrieval раздел про реранкинг (≈10 мин): какой эффект там получен и какой ценой по латентности.
- В What We Learned from a Year of Building with LLMs находим советы про выбор модели и маленькие модели (≈15 мин).
- Документация цен своего провайдера: страница pricing и раздел про prompt caching (≈10 мин).
- Открываем DeepSeek: Models & Pricing (≈10 мин): цены Flash и Pro с кеш-попаданием и промахом, окна пик/вне пика. Переносим в
eval/prices.jsonс датой. - Открываем ollama.com/library/qwen3 и Ollama: Context length (≈15 мин): размеры и теги модели, сколько она занимает, как контекст зависит от памяти. Выбираем тег по объёму памяти своей машины (8 ГБ — 4B, 16 ГБ — 7–8B, 32 ГБ+ — 14B и крупнее) с запасом на
bge-m3и реранкер. - Открываем Ollama: FAQ → разделы про
keep_aliveи параллельные запросы (≈10 мин): отсюда прогрев и последовательный прогон.
На что обратить внимание
Заголовок раздела «На что обратить внимание»- «Возьмём самую сильную модель» без измерения — дорого и не обязательно лучше на нашей задаче.
- Сравнение моделей с разными промптами — сравнение промптов, а не моделей.
- Цена из памяти или из старой статьи. Только со страницы цен, с датой.
- Средняя латентность вместо p90: покупатель в Telegram-боте запоминает медленные ответы.
- Латентность меряется в разное время суток — шум API выдают за разницу моделей.
- Английский реранкер для русских вопросов может ухудшить выдачу; смотрим срез по языку.
- Маленькая модель ломает формат (нет ссылок
[1], нет фразыREFUSALна unanswerable, лишние обещания возврата). Это часть качества; проверяем assertions из 6.1 (наличие ссылок) иrefusal_ok/false_refusalsотдельными метриками. - Rate limits. Параллельный прогон 4 конфигураций может упереться в лимиты; ретраи искажают латентность. Записываем число ретраев и исключаем время ожидания из латентности или гоняем последовательнее.
- Holdout — один раз, только для финальной конфигурации.
- Локальная модель проиграла из-за обрезанного контекста, а не из-за качества. Проверяем
ollama psиprompt_eval_countдо того, как писать вывод. - Холодный старт и очередь Ollama в латентности. Прогрев, последовательный прогон, первый запрос не учитывать.
- Локальный прогон при нехватке памяти (открыты тяжёлые программы, модель крупнее, чем позволяет RAM) — своп и мусорная латентность. Только с закрытыми лишними программами и моделью по своему объёму памяти.
- Судья DeepSeek сравнивает DeepSeek с qwen. Самопредпочтение искажает именно вывод «S против L»; проверяем согласие на разошедшихся вопросах.
- «$0» у локальной модели на графике цены выглядит как безусловная победа. Добавляем ось латентности и качество.
- Вывод «локальная модель хуже — значит, всё в облако» нарушает правило приватности: сообщения с личными данными покупателей всё равно не уходят в облако. Выбор для них — локальная модель или оператор (
handoff), а не DeepSeek.
Практика
Заголовок раздела «Практика»- В
experiments/EXP-02-models-reranker.mdзаписываем требования (порог качества, бюджет, p90) и гипотезы (например: «реранкер даст маленькой модели больше, чем большой»). - Составляем короткий список по Artificial Analysis и конкретизируем дизайн 2×2 на нашем стеке: S =
qwen3:4b(точный тег в конфиг), L =deepseek-flashбез thinking; режим рассуждений одинаковый у S и L. Точные ID и цены с датой записываем вeval/prices.json. - Добавляем в конфиг
reranker,retrieve_k(сколько кандидатов отбирать до реранкинга, например 20) иk(сколько отдавать модели, например 5). - Прогоняем только поиск с реранкером и без: hit@5, MRR. Смотрим срез по языку вопроса (
ru/en). - Перед локальными прогонами: лишние программы закрыты,
ollama psпоказывает нужный контекст и модель целиком на GPU; прогревочный запрос. - Делаем 4 конфига (
eval/configs/s.json,s_rerank.json,l.json,l_rerank.json) и прогоняем полный eval на dev: локальные конфигурации последовательно, DeepSeek-конфигурации — в одном окне цен; для пар-кандидатов — повторные прогоны или сравнение с известным разбросом. - Считаем $ на 100 вопросов (только система), p50/p90 латентности, отдельно время реранкера.
-
eval/compare.pyдля пар: S vs S+R, L vs L+R, S+R vs L+R. - Строим два точечных графика (matplotlib): «correctness против $ на 100 вопросов» (
experiments/pareto.png, с отмеченным фронтом Парето) и «correctness против p90 латентности». - Читаем вопросы, где S и L разошлись (обычно 10–20): в чём маленькая модель хуже (формат, рассуждение, игнорирование инструкции, архивные правила возврата)? Размечаем их сами, не глядя на вердикты, и считаем согласие судьи
deepseek-v4-proна них отдельно. - Выбираем конфигурацию по своим требованиям, прогоняем её один раз на holdout, записываем цифру.
- Пишем рекомендацию (5–8 предложений, с цифрами и ограничениями) в журнал эксперимента, отдельной строкой — для сообщений с личными данными: локальная модель или оператор.
- (Опционально, 16 ГБ+) S′ =
qwen3:8b(или 14B при 32 ГБ+) отдельным прогоном: сколько качества добавляет локальная модель крупнее. - (Опционально) L+ =
deepseek-v4-proилиdeepseek-flashс thinking: что дают качество и сколько стоят. - (Опционально, для сравнения) Одна модель Claude и/или OpenAI на тех же вопросах (данные публичные); отмечаем их на графике.
Готово, когда
Заголовок раздела «Готово, когда»- Есть таблица 4 конфигураций: качество (с разбросом), стоимость 100 вопросов, p50/p90 латентности.
- Есть рекомендация с цифрами: какую конфигурацию брать в продакшен и почему, с ограничениями.
- Финальная конфигурация проверена на holdout.
Тест для самопроверки
Заголовок раздела «Тест для самопроверки»Вопрос 1
Что такое фронт Парето в контексте выбора конфигурации?
Множество конфигураций, у которых нельзя улучшить одну ось (качество, цена, скорость), не ухудшив другую; всё, что доминируется, отбрасывается.
Вопрос 2
Почему высокий рейтинг модели на LMArena не гарантирует лучший результат в нашем RAG?
LMArena отражает предпочтения людей в общем чате (с влиянием стиля и длины), а не следование инструкции «отвечай только по базе знаний» и точность фактов в нашей предметной области.
Вопрос 3
Какая часть токенов обычно доминирует в стоимости RAG-ответа и почему?
b. Каждый вопрос несёт системный промпт и k кусков базы знаний, это тысячи токенов против сотен на выходе.
Вопрос 4
Как реранкер может снизить стоимость генерации, хотя сам добавляет шаг?
Он позволяет отдавать модели меньше, но более точных кусков (например, 4 вместо 8) при том же или лучшем качестве, и входных токенов становится меньше.
Вопрос 5
Зачем дизайн 2×2, а не две отдельные пары сравнений?
Видно взаимодействие факторов: эффект реранкера может зависеть от модели (сильно помогает маленькой, почти не помогает большой).
Вопрос 6
Маленькая модель на 3 п.п. хуже большой по correctness (расхождения 6/4), в 6 раз дешевле, требования: correctness не ниже baseline−разброс. Что выбирать и что нужно проверить?
Скорее маленькую: 6/4 — шум, разница не установлена, и требования, вероятно, выполнены. Проверить: порог качества с учётом разброса, faithfulness и формат (ссылки, отказы), срезы (не проваливается ли тип вопросов), латентность, и подтвердить на holdout.
Вопрос 7
Что не так в этом сравнении?
L: answer_prompt v3, k=5, judge = LS: answer_prompt v3-short (S путалась в длинном), k=8, judge = LИтог: L лучше на 6 п.п.Изменено несколько переменных: разные промпты и k, поэтому сравниваются не модели. Судья из того же семейства, что L — возможное самопредпочтение. Нет разброса и парного сравнения.
Вопрос 8
Почему латентность разных конфигураций лучше мерить вперемешку?
Латентность API колеблется по времени суток и нагрузке; если гонять A утром, а B вечером, разница сети будет приписана моделям.
Вопрос 9
Почему решение принимается по требованиям, сформулированным до эксперимента?
Иначе требования подгоняются под понравившийся результат; заранее заданные пороги превращают выбор в проверку условий.
Вопрос 10
Зачем финальную конфигурацию прогонять на holdout и почему только один раз?
Dev использовался для выбора, поэтому его цифра оптимистична; holdout даёт честную оценку. Повторные прогоны с подстройкой превратили бы holdout во второй dev.
Вопрос 11
Локальная qwen3:4b проиграла deepseek-flash 25 п.п. correctness, причём почти все провалы на вопросах с длинными кусками (таблицы тарифов доставки). Что проверить, прежде чем писать вывод?
Не обрезан ли контекст: при памяти меньше 24 ГБ Ollama по умолчанию даёт 4k токенов, длинные куски молча отбрасываются. Посмотреть ollama ps (CONTEXT) и prompt_eval_count, поднять контекст (осторожно) или уменьшить k и перепрогнать.
Вопрос 12
Почему судья deepseek-v4-pro требует дополнительной проверки именно в сравнении «локальная модель против deepseek-flash»?
Судья из семейства DeepSeek может выше оценивать ответы своего семейства, и это искажает ровно сравниваемую пару. Нужна своя разметка разошедшихся вопросов (и, по желанию, судья другого семейства на них).
Вопрос 13
Почему для локальной модели на машине с 8 ГБ мерить латентность через ThreadPoolExecutor(max_workers=6) и сразу после простоя — ошибка? Назвать две причины.
Ollama по умолчанию обрабатывает запросы к модели по одному, поэтому параллельные запросы меряют ожидание в очереди; после 5 минут простоя модель выгружена, и первый запрос включает её загрузку. Плюс на 8 ГБ параллельность увеличивает память и может вызвать своп.
Что дальше
Заголовок раздела «Что дальше»Цифры показали, сколько ошибок осталось. В 8.4 · Ручной разбор ошибок выясняем, почему они происходят, и исправляем самую частую причину с измеренным эффектом.