75 картинок ablation: как Reddit-критика заставила меня переосмыслить FLUX-LoRA пайплайн

от автора

TL;DR. Запустил pinock.io — бесплатную ленту AI-генерации животных в стиле советских спичечных коробков. Под капотом FLUX.2-klein + кастомная LoRA + двухпроходный «sandwich»-пайплайн. Получил детальный технический комментарий на r/StableDiffusion с двумя конкретными претензиями. Прогнал ablation: 5 вариантов × 5 категорий × 3 сида = 75 картинок. Нашёл дыры в собственном пайплайне — в том числе кириллицу в выходе LoRA (training-set leakage) и полный коллапс LoRA при scale=2.0. Текущий sandwich оказался патчем поверх плохо обученной LoRA. Пересобираю датасет на 1500 примеров и ухожу в single-pass. В статье — все картинки, цифры, и почему оба «правильных» совета критика на текущей модели не сработали.

Master comparison grid, seed=42 — 5 variants × 5 animals

Master comparison grid, seed=42 — 5 variants × 5 animals

Master comparison grid, seed=42. Строки — варианты пайплайна (A–E), столбцы — категории животных. Подробный разбор ниже.


Что такое pinock.io

Открыл pinock.io пару недель назад. Идея проще пареной репы:

  • Заходишь на сайт — видишь сетку AI-картинок животных в стиле винтажного советского плаката.

  • Каждые 30 секунд выходит новая.

  • Можно лайкать, скачивать, шарить, искать («cat», «owl», «fox»…), генерить свои («peacock»).

  • Бесплатно, без регистрации, без вотермарки.

К моменту этой статьи в базе ~6700 картинок. Пользовательский поиск и кастомные промпты прыгают очередь и подсказывают системе, чего хотят люди — это сигнал спроса.

Стек:

  • Frontend: статика (vanilla JS), Caddy

  • Backend: FastAPI + SQLite (WAL) на дешёвой Ubuntu-машине у знакомого

  • FLUX worker: один RTX 3090 на vast.ai (~$0.20/час), запросы через SSH-туннель

  • Caption worker: Qwen2.5-VL-7B INT4 на вторичной машине

  • Real-ESRGAN для апскейла топовых картинок (Hall of Fame)

  • Stripe для оплаты edit-токенов (Nano Banana 2)

Total стоимость генерации одной картинки — около $0.01.


Архитектура «two-pass sandwich»

Это центральный технический выбор, который и оказался под огнём. Текущий пайплайн на одну картинку:

prompt = "cat"   │   ├─ Pass 1: FLUX.2-klein + matchbox LoRA (rank=32, alpha=64, scale=2.0)   │             text2image, 28 шагов   │             → output_b1 (стилизованная, но с кривой анатомией)   │   └─ Pass 2: FLUX.2-klein без LoRA                 img2img от output_b1, strength=0.9, 28 шагов                 → output_b (финальная)

Почему так делалось. На датасете ~300 картинок при scale=1.0 LoRA даёт еле заметный эффект, при scale=2.0 — стиль появляется, но анатомия ломается (лишние конечности, сросшиеся головы). Я подобрал эмпирический «костыль»: pass-2 берёт сломанный pass-1 как init и при strength=0.9 практически перерисовывает картинку с нуля, оставляя только «отпечаток стиля». Получается узнаваемое животное в matchbox-эстетике.

Звучит как трюк. И это трюк — но я был уверен, что он оптимальный.


Reddit-критика, которая меня заставила сесть

Запостил пост в r/StableDiffusion. Получил длинный, технически точный комментарий от u/DelinquentTuna. Если коротко, три претензии:

1. Зачем выкручивать LoRA до scale=2.0 — это намеренно «жарит» модель, и потом ты сам же затираешь её в pass-2 при strength=0.9? То есть ты выбрасываешь ~90% результата LoRA.

2. FLUX.2-klein умеет нативный edit/style-transfer. Я (критик) у себя на 4080 16GB прогнал твои картинки через встроенный pipeline и получил выход в 4 раза больше (~1024×1024) за 9 секунд с более когерентным стилем. Используй edit-фичу вместо самопального i2i.

3. ~300 примеров — слишком мало для matchbox-эстетики (halftone, ограниченная палитра, литографические текстуры). Нужно 5× датасета и нормальная разметка.

И отдельный пункт про дефолтную сортировку — он первым делом увидел свежие картинки (мусор), а не лучшие.

Все три замечания технически обоснованы, и все три заслуживают честного ответа. Сел делать ablation.


Ablation: 5 вариантов × 5 животных × 3 сида = 75 картинок

Тестировал на проде (RTX 3090 + FLUX.2-klein + matchbox LoRA, тот же стек что в production). Запустил два tmux-скрипта на ~30 минут суммарно и собрал результаты в гриды через PIL.

Варианты:

Код

Описание

Параметры

A

Pure FLUX (без LoRA, голый промпт)

baseline

B

LoRA t2i pass-1 snapshot (то, что выдаёт LoRA до затирания)

lora_scale=2.0, prompt=“cat”

C

Текущий прод-sandwich

lora=2.0, pass2_strength=0.9

D

Single-pass со style-промптом (совет критика #1)

lora=1.0, prompt=“cat, matchbox poster style, 1960s Soviet, woodcut, halftone, limited red-black palette”

E

Edit-style: pure FLUX → img2img со style-промптом (совет критика #2)

init=A, lora=1.0, strength=0.5

Категории: cat, fox, owl, lion, wolf. Сиды: 42, 1337, 80085 (выбраны до прогонов; в трёх повторах хочется поймать seed-зависимость).


Результаты

Master grid, seed=1337

Master comparison grid, seed=1337

Master comparison grid, seed=1337

Master grid, seed=80085

Master comparison grid, seed=80085

Master comparison grid, seed=80085

Разбор по строкам

Вариант A — pure FLUX

Базовая модель без LoRA. Реалистичные иллюстрации/фотографии. Никакого matchbox. Ожидаемо. Это нужно как baseline, чтобы понимать, что добавляет LoRA.

Вариант B — LoRA при scale=2.0, голый промпт (snapshot)

Полный коллапс. На каждом сиде все 5 категорий выглядят почти одинаково — текстурный шум одного оттенка:

  • seed=42: красно-оранжевые волнистые полосы

  • seed=1337: зелёный «лесной шум»

  • seed=80085: золотая нашлёпка

Анатомия отсутствует. Это и есть «правда» о LoRA при scale=2.0: модель не генерирует животных. Она генерирует текстуру плаката, потому что я её перекормил весом во время инференса. И именно поэтому я придумал sandwich — потому что наблюдал ровно эту катастрофу и захотел её спрятать за pass-2.

Критик увидел это сразу. Я нет.

Вариант C — текущий прод-sandwich

Адекватный результат. Узнаваемые животные с явно matchbox-эстетикой: woodcut-линии, halftone-фон, ограниченная палитра, иногда W. Morris-подобные цветочные узоры на фоне. На всех 3 сидах стабильно даёт картинки, которые узнаваемы и стилизованы.

Технически это работает так: pass-2 на strength=0.9 берёт сломанный pass-1 (B), добавляет 90% шума и перерисовывает заново. От пасса-1 остаётся только низкочастотный сигнал — общая композиция и цветовой профиль. Это «впрыскивает» стиль без того, чтобы анатомия ломалась.

Вариант D — single-pass со style-промптом, scale=1.0 (совет критика #1)

Катастрофа другого типа. На seed=42 на части картинок видна кириллица в подписи: «СТАДИНАМ» или похожий гибберишный текст. На seed=1337 все 5 категорий схлопываются в почти идентичные «красные силуэты на чёрном». На seed=80085 — снова все 5 одинаковы, на этот раз красный animal-силуэт на белом фоне.

Что произошло. Тренировочный датасет (~300 примеров) включал советские плакаты с кириллицей и красным доминантным фоном. При scale=1.0 + длинный «правильный» style-промпт LoRA начинает вспоминать целые плакаты из training set, а не транспонировать стиль. То есть training-set leakage в чистом виде.

Это самое интересное наблюдение во всей серии. Совет критика  «используй scale=1.0 + проперный style-промпт» теоретически правильный — но на этой LoRA он только выявляет, насколько она переобучена на конкретные обучающие примеры.

Вариант E — edit-style refinement (совет критика #2)

Стиль почти не виден. На strength=0.5 + lora=1.0 LoRA не пробивает FLUX-prior. Картинки выглядят как A с лёгким иллюстративным фильтром: чуть больше насыщенности, чуть проще линии, но не matchbox.

Чтобы стиль начал проступать, нужен strength≥0.7 — а тогда мы возвращаемся в логику i2i sandwich. И снова получим ту же кириллицу/коллапс, только через img2img.


Главный вывод

Текущий sandwich © выигрывает в этой пятёрке — но это патч поверх плохо обученной LoRA, а не правильное решение.

Все три подхода (B raw, D single-pass-styled, E edit-style) показали одну и ту же проблему: LoRA при scale=1.0 пытается воспроизвести training-set целиком, а не переносить стиль. Sandwich работает именно потому что pass-2 при strength=0.9 «сжигает» эту память до низкочастотного остатка.

Это значит:

  1. Совет критика #1 (single-pass + scale=1.0 + style-промпт) теоретически верный, но на текущей LoRA производит хуже результат, чем sandwich.

  2. Совет критика #2 (edit-фичи) недотягивает по стилю при умеренных strength и снова уйдёт в leakage при высоких.

  3. Совет критика #3 (нужен 5× датасет, чище разметка) — единственный истинный фикс. И именно его я и не делал.


Что планирую (и почему это правильный путь)

1. Пересобрать датасет до 1500 картинок.

  • Никакой кириллицы вообще (либо отдельный токен «soviet-text» если он нужен).

  • Жёсткие фильтры: halftone present, limited palette (≤5 цветов), flat geometry.

  • Captioning через Qwen2.5-VL с шаблоном «matchbox poster of a {category}, {dominant colors}, {composition}, woodcut linework».

2. Переобучить LoRA на rank 32 + attention+MLP, не только attention. Текущая LoRA — только на attention-блоках, что недостаточно для переноса композиционных признаков (woodcut, halftone). MLP даст больше «места» для стиля.

3. После v2 LoRA — повторить тот же ablation. Если на v2 single-pass styled (D) даст узнаваемые животных без кириллицы — sandwich удаляется навсегда, и:

  • Время генерации с ~30 сек падает до ~10-15 сек

  • Размер можно поднять с 512 до 1024 (на 3090 хватит)

  • VAE round-trip между проходами уходит — pass-1 больше не сохраняется в JPEG

4. Параллельно — Hall of Fame default sort = Liked. Этот фикс уже задеплоен. Первый экран теперь — лучшие, а не свежие.


Промежуточные технические находки, попавшие в side-quests

FastAPI + SQLite + cursor-пагинация в поиске. Изначально search-эндпоинт жёстко ограничивал выдачу 60 картинок — 581 кошку нельзя было долистать. Добавил ?cursor=<id> (фильтр id < cursor, ORDER BY id DESC), на пагинационных запросах не триггерится автогенерация (чтобы не флудить очередь). Frontend подцепил тот же IntersectionObserver, что для основной ленты.

Auto-prompt variety. Для авто-генерации (не пользовательский ввод) добавил три пула — adjectives (proud, fierce, sleepy…), actions (running, perched, watching…), scenes (in winter forest, at sunset…) — и распределение 55/20/15/10: 55% — голое название категории, 20% — adj+animal, 15% — animal+action, 10% — animal+scene. Раньше все «cat» выглядели одинаково, теперь больше разнообразия.

Реальная стоимость. vast.ai 3090 ~0.20/час → ~5/сутки → при ~1500 картинок/сутки = 0.003/картинка по GPU. Плюс backend/storage ~2/сутки. Итого <$0.01 за картинку при текущем масштабе.


Что забираю из этой истории

  1. «Эмпирически работает» — не значит «оптимально». Sandwich я подобрал по принципу «попробовал — анатомия не ломается — оставил». Я не задавал вопрос «а почему вообще пришлось крутить scale до 2.0». Reddit-критик задал.

  2. Ablation должен быть в day-one. Сделать 5 вариантов × 3 сида = 15 минут на чужом GPU, и я бы не запостил пост с самопальным sandwich как «решением».

  3. Чужая критика — самый дешёвый источник истины. Месяц назад я бы 30 раз подумал, постить ли. Стоило поста на Reddit и одного длинного комментария от незнакомца, который провёл свою параллельную работу на 4080.

  4. Training-set leakage — это не теоретическая страшилка. В моём случае она проявилась как литеральные кириллические буквы в выводе. Если бы я проверял только sandwich-результат (где они скрываются), я бы их не увидел.


Открытые вопросы / куда смотреть дальше

  • Возможно ли получить «sandwich-качество» в один проход на v2 LoRA без leakage. Подозреваю да, но это надо проверить тем же ablation.

  • Стоит ли пробовать FLUX.2-edit явно (через diffusers.FluxEditPipeline) вместо моей самопальной img2img-реализации.

  • Имеет ли смысл вообще держать LoRA на FLUX.2-klein, или для эстетики типа matchbox правильнее обучить под Schnell или другой fast-вариант.


Ссылки

Если интересно посмотреть на v2 LoRA результаты (планирую обучение в ближайшие дни) — заходите через неделю, опубликую update.


P.S. Огромная благодарность пользователю u/DelinquentTuna на r/StableDiffusion. Это лучшее техническое ревью, которое я получал за год. Тебе одному я обязан пятью экспериментами, переосмыслением пайплайна и осознанием того, что training-set leakage — реальная штука, а не байка.

ссылка на оригинал статьи https://habr.com/ru/articles/1031338/