Почему LLM выдаёт разные результаты при каждом запуске

Пять одинаковых запросов — пять разных ответов: один запуск ответил «не Python», четыре сказали «Python». Закрепили seed — и батч перевернул токен при temp=0.

HUMANавтор @msvram7h7 мин чтения
один промпт × 5 · temp 0.7 · без seed · Qwen3-4B Q4_K_M ОДИН ПРОМПТ“The most popular programminglanguage in the world is” запуск 1 · “ Python. Python is widely used in data…” запуск 2 · “not Python, Java, or C++. It’s actually…” запуск 3 · “ Python. But there are other languages…” запуск 4 · “ Python. But why? Python is a high-level…” запуск 5 · “ Python. Python has a lot of applications…”

Рис. 1 — пять запусков, пять ответов, одно самоопровержение. Запуск 2 (тусклый) — та квитанция: тот же файл, та же машина, тот же промпт — противоположное начало.

Почему LLM отвечает по-разному при каждом запуске?

seed — сохранённый бросок костей. Каждое решение сэмплинга стартует с одного числа; задайте его — и вся перетасовка повторится. Пропустите — и движок вытягивает свежее число на каждый запрос.
temperature — насколько дико модель делает ставки. Каждый следующий токен — аукцион: у модели список вероятностей, temperature меняет его форму, один токен выигрывает розыгрыш. 0.7 — обычные кости; 0 значит «всегда бери фаворита» (жадный режим).
top_p — второй, более строгий фильтр. Он подрезает список кандидатов до броска. Кости он сужает, а не фиксирует.

У модели никогда нет «готового» ответа. Для каждой токен-позиции она держит список вероятностей, temperature меняет его форму, и один токен выигрывает розыгрыш. Победитель дописывается, и следующий аукцион открывается с другим списком — ведь текст, который она продолжает, уже изменился. Одно раннее расхождение растёт снежным комом: запуск 2 сказал «not» там, где запуски 1, 3, 4 и 5 сказали «Python», — и от этого единственного символа пять ответов живут в разных вселенных. Пять запусков — пять независимых розыгрышей, а не один ответ, напечатанный пять раз.

Как сделать так, чтобы ответы LLM повторялись в точности?

Дайте броску имя. Мы повторили идентичный запрос трижды с seed: 42 при temperature 0.7 — заведомо недетерминированной настройке — и получили 3/3 идентичных до байта ответа (скорость генерации гуляла 6.18 → 6.74 → 7.63 tok/s; слова не гуляли вовсе). Seed фиксирует кости, даже пока их бросают.

curl http://localhost:8080/v1/chat/completions -d '{
  "model": "qwen3:4b",
  "messages": [{"role": "user", "content": "Explain GGUF quantization"}],
  "temperature": 0.7,
  "seed": 42
}'

проверка: тот же промпт + тот же seed → ответ байт в байт, 3/3 на стенде (temp 0.7, не только жадный режим) · det-results-a.json

Та же ручка есть везде, где вы уже работаете: options.seed в /api/generate у ollama, seed в OpenAI-совместимом чатовом API, --seed в командной строке llama.cpp. Раннер ollama — это llama-server в пиджаке, так что квитанция под капотом та же — seed доходит до того же сэмплера.

Почему temperature 0 всё равно не детерминированность?

Потому что жадное декодирование убирает кости. Оно не убирает округление. Внутри сети счёт каждого токена — тысячи сложений, а сложение чисел с плавающей точкой не ассоциативно: сложите длинный столбец слева направо, потом кусками — те же числа, возможно другая последняя цифра. Когда два верхних кандидата сидят достаточно близко, округление и есть решение. А порядок суммирования зависит от того, сколько работы батч делает за раз.

Поэтому мы зафиксировали всё — temp=0, seed=42, тот же сервер, тот же промпт — и изменили ровно один флаг: ширину микробатча.

запрос-ubtempseedначало ответапротив -ub 512
1–2512042“ Python. It is used in many…”базовая линия, 2/2 идентичны
18192042“ Python. It’s used in many…”ПЕРЕВЕРНУЛОСЬ — один токен
2–38192042“ Python. It is used in many…”вернулось к базе, 2/2 базовой линии

Кость с грузиком вживую: каждая ручка говорила «детерминированно», а форма батча всё равно сдвинула один токен — и только на первом запросе после старта.

Прочитайте третью строку дважды. Переворот случился однажды, на первом запросе холодного сервера, а два следующих запроса с теми же настройками вернули базовый текст. Состояние тёплого сервера — кэш промптов, раскладка слотов — встаёт в один ряд с порядком суммирования: тоже то, от чего зависит «детерминированный» ответ. Переворот мы поймали; притворяться, что поймали виновника, не будем. Кости загружены. Рука остаётся скрытой.

Во что обходится разброс от запуска к запуску?

Счёт, оплаченный пять раз. Мы просили «hello» ровно в трёх словах, пять раз, свободный seed, бюджет 256 токенов. Модель ответила 0 из 5 — каждый запуск потратил весь бюджет на размышления, прежде чем лимит его оборвал, и сами размышления гуляли: 884 · 954 · 986 · 915 · 885 символов на один и тот же запрос.

символов размышлений · “Say hello in exactly three words” × 5 · лимит 256 токенов, съеден ×5 884запуск 1 954запуск 2 986запуск 3 915запуск 4 885запуск 5

Рис. 2 — разброс и есть суть: разница в 102 символа на фиксированный запрос. Счёт за размышления из октябрьского поста про LFM3, пять раз подряд.

Мы просили «hello» в трёх словах — получили от 884 до 986 символов душевных исканий и ни одного «hello». Модель нерешительна не в ответе — она нерешительна во всём, что до ответа, и вы платите за это независимо от того, пришёл ответ или нет. Любой запрос «точного ответа» — это бюджет: ограничивайте размышления (think: false или собственный выключатель модели), иначе лимит съест вас первым.

Список исправлений

симптомчто происходитисправление
разный ответ на каждом запускенет seed; temp > 0 меняет список при каждом розыгрышефиксируйте seed; ответ повторится байт в байт
разные ответы при temp=0форма батча / состояние тёплого сервера изменили порядок суммированиятот же -ub/-b, та же сборка движка, прогрейте перед измерением
ответ так и не приходитразмышления съели токен-бюджет (здесь 0/5)ограничьте или отключите размышления; смотрите completion_tokens, а не вайбы
квитанция · запуск 2 отрицает запуски 1, 3, 4, 5 (тот же промпт, та же машина, без seed)
run 1: “ Python. Python is widely used in data science, web…”
run 2: “not Python, Java, or C++. It’s actually a language that’s
       not even a progr…”
run 3: “ Python.  But there are other languages that are…”
run 4: “ Python. But why? Python is a high-level, interpreted…”
run 5: “ Python. Python has a lot of applications in web…”

Четыре запуска сказали Python. Один начал с оспаривания самой постановки вопроса. Те же веса, та же машина, та же строка на входе. Один из пяти ошибается, считая себя детерминированной машиной; остальным четырём просто повезло.

последняя проверка 2026-11-11

Qwen3-4B Q4_K_M · 2,497,280,256 B · sha256 7485fe6f…df5 · llama-server 0.9.0-dev (b12110) · одна свежая VM Arch, 4 потока — CPU-бенч, никаких заявлений про VRAM