История бага с хардкодом шагов в FLUX: как мы откопали и починили | AiManual
AiManual Logo Ai / Manual.
07 Июл 2026 Гайд

Как я случайно захардкодил ограничение шагов генерации в FLUX: история бага и его исправления

Разработчик случайно зафиксировал num_inference_steps=28 в коде инференса FLUX, думая, что это предел. Раскапываем баг, исправляем и делаем выводы о самоогранич

Бывает, сидишь ночью, доделываешь фичу, тестишь — всё летает. Деплоишь в прод. А через неделю прилетает баг-репорт: «генерация тормозит, картинки мыльные, почему нельзя увеличить качество?». Открываешь код, смотришь — и чувствуешь, как щека начинает дёргаться. В хардкоде сидит num_inference_steps=28.

Я хочу рассказать, как я сам себе прострелил ногу, неправильно прочитав документацию FLUX, и что из этого вышло. Это история про самоограничение, про то, как мы, разработчики, любим «защищать» систему от самих себя, и про то, чем это может обернуться.

Как я выстрелил себе в ногу: предыстория

Всё началось с того, что мы решили добавить в наш сервис генерацию изображений на базе FLUX. Модель тогда была свежая — FLUX.2 в Diffusers только вышла, и мы, как прогрессивная команда, сразу вкатили. Я полез в документацию к пайплайну. Там чёрным по белому написано: num_inference_steps: int, optional, default 28. И где-то в примерах — «обычно 28 шагов даёт хороший баланс скорости и качества».

И тут я, видимо, прочитал это как «максимально допустимое количество шагов — 28, больше нельзя». Сам себе внушил: «FLUX — это диффузионка с каким-то продвинутым scheduler, у неё есть потолок, иначе модель расходится». Бред? Абсолютный. Но в тот момент логика была: «Зачем давать пользователю опцию менять шаги, если от этого только проблемы? Давайте зафиксируем на дефолте».

Так в коде появилась строчка:

# Фиксируем шаги, чтобы не навредить себе
images = pipe(
    prompt,
    num_inference_steps=28,  # МАКСИМУМ! (как я думал)
    guidance_scale=3.5
).images

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

Момент истины: баг-репорт и первые подозрения

Через пару недель нашёл тикет от пользователя: «Почему картинки такие мыльные? В Stable Diffusion XL с 50 шагами получается детализированнее». Я полез проверять. Действительно, есть некоторая размытость, особенно на сложных промптах. Но я же помню — 28 шагов, это же стандарт? А потом другой пользователь спросил: «А можно ли ускорить генерацию, уменьшив шаги до 8? Мне нужно быстро прототипировать».

И тут я осознал: я заблокировал возможность менять шаги. Хуже того — я даже не знаю, можно ли их менять вообще. Пошёл перечитывать документацию. И понял, что я идиот. num_inference_steps — это просто параметр, который можно ставить от 1 до разумных 100 и больше. Никакого потолка. Ограничение было только в моей голове.

Важно: В Diffusers для FLUX по умолчанию используется FlowMatchEulerDiscreteScheduler. Он поддерживает переменное количество шагов без проблем. Никаких ограничений на 28 нет. Это я сам себе придумал лимит.

Параллельно вспомнилась история с кривым расчётом key_gdiff в llama.cpp, когда неправильная интерпретация параметра ломала генерацию. Там тоже всё начиналось с «а зачем менять?» — но потом выяснилось, что баг прятался в другом месте. У нас было похоже: мы не давали пользователю гибкости, потому что боялись, что он что-то сломает.

Копание в исходниках FLUX: что на самом деле происходит

Чтобы убедиться, что увеличение шагов безопасно, я залез в код FLUX pipeline. Оказалось, scheduler FlowMatchEulerDiscreteScheduler на каждом шаге просто сэмплирует траекторию потока. Больше шагов — плавнее переход, меньше шумов. Никакого «срыва» модели. Наоборот, для сложных промптов рекомендуется 50-60 шагов, а для быстрого превью хватает 4-8.

Кстати, в Step 3.5 Flash была похожая ситуация: модель могла галлюцинировать при tool calls, если неправильно настроить количество шагов. Там проблема была в стабильности, но решение тоже лежало через настройку параметров, а не через хардкод.

Я решил поэкспериментировать: запустил один и тот же промпт с 8, 28, 50 и 100 шагами. Разница — колоссальная. 8 — шумно, 28 — приемлемо, 50 — детализированно, 100 — почти фотореалистично, но время генерации растёт линейно. И никакого «срыва».

💡
Вывод для себя: Не экстраполируй дефолтные значения на максимальные. Default 28 — это баланс, а не предел. Разработчики Diffusers выбрали 28 как золотую середину, но они же дали возможность менять.

Деплой с граблями: как мы откатывали фикс

Исправление заняло пять минут. Я вынес количество шагов в переменную окружения FLUX_INFERENCE_STEPS с дефолтом 28. Потом добавил опцию в API. Протестировал — всё работает. Залил в прод.

Но на этом история не кончилась. Через день после деплоя я заметил, что некоторые пользователи начали ставить 100 шагов, и сервак лёг под нагрузкой. Пришлось добавить лимит сверху (максимум 100) и квоты. И вот тут мы снова задумались: а не перестраховываемся ли мы опять?

Но это уже другая история. Главное — первоначальный баг был именно в самоограничении. Как в баге с преждевременным EOS в GLM-4.5-Air, где модель обрывала генерацию раньше времени, потому что разработчики так настроили токенайзер. Только у нас наоборот — мы искусственно обрывали генерацию, не давая модели дойти до лучшего качества.

Почему мы так делаем? Психология самоограничения

Я задумался: а почему я, опытный инженер, вдруг решил, что 28 — это максимум? Ведь буквально везде пишут про adjustable steps. Ответ: страх. Страх, что если дать пользователю слишком много свободы, он сломает систему. Страх, что модель поведёт себя непредсказуемо. Но в итоге мы ломаем собственную гибкость.

Вспомнилась статья про Mistral 4 в GGUF, который врал о контексте в 1 млн токенов. Там тоже разработчики приняли заявленное «1 млн» как факт, а на деле модель работала только с 32k. Ошибка интерпретации документации — наша общая проблема.

Совет: Если в документации написано «default: 28», это не значит «recommended: 28» или «max: 28». Default — это просто значение по умолчанию. Всегда проверяйте, можно ли его менять, и если да — делайте параметры настраиваемыми.

Пошаговый план исправления (чтобы вы не наступили на те же грабли)

1 Аудит кода на хардкод

Найдите все вхождения num_inference_steps, max_length, max_tokens и других «магических чисел». Проверьте, не зашиты ли они константами.

2 Вынесите в конфиг

Используйте переменные окружения, yaml-конфиги или хотя бы константы с комментарием «можно менять». Для Python: NUM_STEPS = int(os.getenv("FLUX_STEPS", 28))

3 Протестируйте с разными значениями

Проверьте, что модель не ломается при минимальных (1-4) и максимальных (100+) шагах. Снимите метрики качества (FID, CLIP score) для разных значений. Убедитесь, что нет побочных эффектов.

4 Добавьте документацию для пользователей

Объясните, что больше шагов = лучше качество, но дольше; меньше шагов = быстрее, но шумнее. Дайте рекомендации (например, 8-12 для превью, 28-50 для продакшна, 100+ для премиум).

Чему я научился на этой ошибке

Во-первых, не читать документацию по диагонали. Во-вторых, всегда подозревать себя в самоограничении. Если вы думаете «это значение нельзя менять» — проверьте, так ли это на самом деле. Скорее всего, можно.

В процессе разбора этого бага я наткнулся на статью про настройку MiMo-2.5 для кодинга. Там проблема была в зацикливании модели, и её решили корректировкой параметров генерации, а не хардкодом. Общий паттерн: сначала «защищаем» систему, потом страдаем от негибкости.

Ещё один пример — баг с зацикливанием GLM-4.7-Flash в LM Studio. Там тоже пришлось копаться в настройках, а не верить дефолтам. Вывод: дефолты — это не догма.

Что пошло не так в моей архитектуре: взгляд DevOps

Как Senior DevOps, я должен был предусмотреть гибкость на уровне инфраструктуры. Параметры инференса — это часть конфигурации сервиса, они должны быть изменяемы без пересборки. Мы это исправили, но осадочек остался.

Если вам интересны похожие кейсы, почитайте сравнение Qwen3.5 для генерации баг-репортов. Там тоже были неожиданные результаты из-за разных настроек генерации. Или как Gemini 2.5 Pro починил 9-летний баг в far2l — отличная демонстрация того, как свежий взгляд помогает найти глупую ошибку.

И, конечно, ловушка Langfuse — ещё один пример, когда дефолтное поведение SDK может навредить. Не доверяйте умолчаниям.

Вместо заключения: парадокс контроля

Чем больше мы контролируем параметры, тем менее гибкой становится система. Я перестал бояться давать пользователям ручки настройки. Да, они могут ошибиться. Но лучше они ошибутся, чем мы решим за них, что им нужно.

Сейчас в нашем сервисе можно выбирать количество шагов от 8 до 100. И — о чудо — никто не сломал генерацию. Наоборот, пользователи рады, что могут ускорить или улучшить качество под свою задачу.

Так что, если вы тоже хардкодите дефолты — остановитесь. Выпустите параметр наружу. Модель вас не укусит. А если укусит — ну, тогда хотя бы честно.

Подписаться на канал