Перейти к содержимому

8.3 · Эксперимент: модели и реранкер

Неделя 8 · Evals · ⏱ ~4–5 ч · Проект

Выбор модели — решение, которое инженер принимает постоянно, и почти никогда ответ не «самая сильная». Задача — найти конфигурацию, которая достаточно хороша при приемлемой цене и задержке. Вопрос «как выбирали модель?» на собеседовании ждёт ответа с цифрами: «маленькая модель с реранкером даёт ту же 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 из набора.

Осталось соединить оба фактора в один эксперимент. Два фактора (модель ответа, реранкер) — четыре конфигурации:

без реранкера с реранкером
маленькая модель 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.

На нашем стеке этот эксперимент становится самым интересным в Проекте 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 лучше, чем наша разметка, — это результат, его пишут в журнал, а не прячут.

Честное сравнение: контекст, прогрев, очередь

Заголовок раздела «Честное сравнение: контекст, прогрев, очередь»

У облачной модели и локальной разные ловушки, и каждая может «подарить» победу не той стороне:

  1. Контекст. 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».
  2. Прогрев. Ollama выгружает модель через 5 минут без запросов (keep_alive), и первый запрос включает загрузку модели в память. Делаем прогревочный запрос и не учитываем его в латентности.
  3. Очередь. По умолчанию Ollama обрабатывает запросы к модели по одному; параллельный прогон меряет ожидание в очереди. Локальную конфигурацию гоняем последовательно.
  4. Сеть и часы пик DeepSeek. Латентность DeepSeek включает сеть до серверов в Китае и нагрузку; цена зависит от окна пик/вне пика. Гоняем конфигурации вперемешку и в одном окне цен.
  5. Квантизация. Теги Ollama по умолчанию — квантизованные веса. Записываем точный тег (qwen3:4b, а не «qwen 4B»): другой тег — другая модель.
  6. Режим рассуждений. 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-модели место на фронте для публичных вопросов о правилах магазина или её ниша — только сообщения с личными данными покупателей и офлайн.

  1. Открываем Artificial Analysis → страницу сравнения моделей (≈20 мин). Находим 2–3 кандидата: одну сильную модель и одну-две маленькие/дешёвые у используемого провайдера. Выписываем цену за 1M входных/выходных токенов, время до первого токена, токены в секунду. Сверяем цены со страницей цен самого провайдера.
  2. Открываем LMArena → лидерборд (≈10 мин). Смотрим, где наши кандидаты. Записываем, почему рейтинг людей в чате ≠ качество ответа по базе знаний.
  3. Открываем Sentence Transformers: Cross Encoder usage (≈15 мин). Метод rank, порядок результатов, заметка про масштаб оценок. Смотрим список предобученных кросс-энкодеров на sbert.net, есть ли многоязычные.
  4. Перечитываем в Anthropic: Contextual Retrieval раздел про реранкинг (≈10 мин): какой эффект там получен и какой ценой по латентности.
  5. В What We Learned from a Year of Building with LLMs находим советы про выбор модели и маленькие модели (≈15 мин).
  6. Документация цен своего провайдера: страница pricing и раздел про prompt caching (≈10 мин).
  7. Открываем DeepSeek: Models & Pricing (≈10 мин): цены Flash и Pro с кеш-попаданием и промахом, окна пик/вне пика. Переносим в eval/prices.json с датой.
  8. Открываем ollama.com/library/qwen3 и Ollama: Context length (≈15 мин): размеры и теги модели, сколько она занимает, как контекст зависит от памяти. Выбираем тег по объёму памяти своей машины (8 ГБ — 4B, 16 ГБ — 7–8B, 32 ГБ+ — 14B и крупнее) с запасом на bge-m3 и реранкер.
  9. Открываем 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-ответа и почему?

  • a) выходные, ответы длинные
  • b) входные, из-за кусков контекста
  • c) токены эмбеддингов
  • d) токены судьи.

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 = L
S: 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 · Ручной разбор ошибок выясняем, почему они происходят, и исправляем самую частую причину с измеренным эффектом.