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. Строки — варианты пайплайна (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 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 (single-pass + scale=1.0 + style-промпт) теоретически верный, но на текущей LoRA производит хуже результат, чем sandwich.
-
Совет критика #2 (edit-фичи) недотягивает по стилю при умеренных strength и снова уйдёт в leakage при высоких.
-
Совет критика #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 ~5/сутки → при ~1500 картинок/сутки =
2/сутки. Итого <$0.01 за картинку при текущем масштабе.
Что забираю из этой истории
-
«Эмпирически работает» — не значит «оптимально». Sandwich я подобрал по принципу «попробовал — анатомия не ломается — оставил». Я не задавал вопрос «а почему вообще пришлось крутить scale до 2.0». Reddit-критик задал.
-
Ablation должен быть в day-one. Сделать 5 вариантов × 3 сида = 15 минут на чужом GPU, и я бы не запостил пост с самопальным sandwich как «решением».
-
Чужая критика — самый дешёвый источник истины. Месяц назад я бы 30 раз подумал, постить ли. Стоило поста на Reddit и одного длинного комментария от незнакомца, который провёл свою параллельную работу на 4080.
-
Training-set leakage — это не теоретическая страшилка. В моём случае она проявилась как литеральные кириллические буквы в выводе. Если бы я проверял только sandwich-результат (где они скрываются), я бы их не увидел.
Открытые вопросы / куда смотреть дальше
-
Возможно ли получить «sandwich-качество» в один проход на v2 LoRA без leakage. Подозреваю да, но это надо проверить тем же ablation.
-
Стоит ли пробовать FLUX.2-edit явно (через diffusers.FluxEditPipeline) вместо моей самопальной img2img-реализации.
-
Имеет ли смысл вообще держать LoRA на FLUX.2-klein, или для эстетики типа matchbox правильнее обучить под Schnell или другой fast-вариант.
Ссылки
-
pinock.io — https://pinock.io
-
Reddit-нить с критикой: https://www.reddit.com/r/StableDiffusion/comments/1t0pcac/comment/ojf7vn1/
-
Master-гриды (полное разрешение) — приложу в комменты к статье
Если интересно посмотреть на v2 LoRA результаты (планирую обучение в ближайшие дни) — заходите через неделю, опубликую update.
P.S. Огромная благодарность пользователю u/DelinquentTuna на r/StableDiffusion. Это лучшее техническое ревью, которое я получал за год. Тебе одному я обязан пятью экспериментами, переосмыслением пайплайна и осознанием того, что training-set leakage — реальная штука, а не байка.
Автор: yukakust

