Эта картинка рисовалась на моём Mac 384 минуты 55 секунд.

Приложение не зависло. Metal не упал. Рендер честно дошёл до 48-го шага и выдал портрет размером 2048×2048. Просто за время ожидания я успел поработать, поесть, поспать и вернуться к той же девушке в кафе. Она всё ещё рисовалась.
Так я понял, что главная задача моей новой локальной студии — не добавить ещё одну кнопку Generate. Нужно было сделать тяжёлую модель предсказуемой для живого человека и не превратить Mac с 32 ГБ памяти в дорогую грелку с пляжным мячом.
Это продолжение моего странного эксперимента: Mac + Swift + MLX = локальный inference без Python-процесса и облачного API. В первой статье я рассказывал, как три ночи чинил картинки, которые на самом деле не были сломаны. Во второй — как сам придумал главное ограничение своего приложения, поверил в него и даже написал для него тултип.
Теперь эксперимент добрался до Ideogram 4.

Зачем ещё один генератор
После Typhoonminigen мне не хотелось делать тот же интерфейс вокруг модели потяжелее. В Ideogram 4 меня заинтересовали две вещи: типографика и композиция.


Модель умеет генерировать изображение сразу на холсте до 2048 пикселей по каждой стороне, работать со структурированными caption и принимать приблизительное расположение объектов через bounding boxes. Из этого получаются вывески, плакаты, character sheet, схемы и сцены, в которых можно заранее попросить: персонаж слева, машина справа, заголовок сверху.
Под капотом это тоже не обычный «один трансформер и один сэмплер». Официальный качественный путь использует asymmetric dual-CFG — два transformer-компонента с собственными файлами весов:
conditional-трансформер получает текстовое conditioning и латент изображения;
unconditional-трансформер получает тот же латент, но нулевое текстовое conditioning;
их направления смешиваются с guidance и обновляют латент.
Unconditional здесь — не negative prompt. Это отдельная ветка модели без пользовательского отрицательного текста.
Я перенёс пайплайн в Swift, опираясь на mflux как основной MIT-референс и на официальный ideogram-oss как математический oracle. UI, orchestration и inference работают в одном приложении через MLX-Swift и Metal. Во время рендера Python-процесс не запускается. Но называть проект «написанным полностью с нуля» было бы нечестно: в нём есть явно атрибутированные компоненты под лицензиями MIT и Apache 2.0, а Python используется в проверочных oracle-скриптах разработки.
Моя цель была не победить ComfyUI или доказать, что Swift быстрее Python. Я хотел проверить другое: можно ли собрать Ideogram 4 как обычное macOS-приложение с очередью, Gallery, воспроизводимыми рецептами генерации и контролем памяти — без отдельного Python runtime.
Шесть часов. И это не было ошибкой
Ideogram 4 поддерживает нативную генерацию до 2048×2048 без обязательного апскейла. В квадрате это четыре мегапикселя. Разумеется, первым делом мне захотелось проверить потолок. Раз кнопка есть — надо нажать.
Эксперимент проходил на Mac mini с базовым Apple M4, 10-ядерным CPU, 10-ядерным GPU и 32 ГБ unified memory. Использовался официальный FP8-checkpoint Ideogram 4. Штатным путём той development-сборки был полный dual-CFG, но старый формат Gallery не сохранял transformer mode для каждого запуска. Три запуска были сделаны 27–28 июня, ещё до финального релиза 1.1.
Это важно: сохранились Gallery-записи и скриншоты. Вместе они подтверждают промпт, seed, размер, пресет, число шагов, guidance 7.0 и показанное приложением время. Старый формат не сохранял Render Speed, transformer mode и частоту Live Preview. Точную версию macOS на момент запусков я тоже не зафиксировал. Поэтому ниже — не «лабораторный бенчмарк», а три наблюдения из реальной разработки.
Промпт и размер оставались одинаковыми, но seed различался. Следовательно, сравнивать качество портретов нельзя. Сравнивать можно только сохранённое приложением время render path — не полное время с ожиданием в очереди, подготовкой Magic Prompt и записью результата. Каждый режим запускался один раз — n=1.
Пресет |
Шаги |
Seed |
Время, мин:с |
Примерно на шаг |
|---|---|---|---|---|
Turbo |
12 |
3434324324324 |
92:49 |
7:44 |
Default |
20 |
604856127754352017 |
156:31 |
7:50 |
Quality |
48 |
34983479379457439 |
384:55 |
8:01 |


В этой серии время росло почти вместе с числом шагов. Но сами официальные пресеты отличаются не только длиной: у них разные параметры noise schedule и финальные участки guidance. Поэтому утверждать «48 шагов ровно в четыре раза тяжелее 12» нельзя. Можно сказать лишь: на моей машине в этих трёх запусках получилось именно так.
Я пробовал кэширование, разные схемы загрузки и освобождения весов, режимы скорости и варианты квантизации. Где-то удавалось откусить проценты. Чуда не произошло: шесть часов не превратились в один.




Практическое решение оказалось продуктовым. Я вынес три официальных пресета Ideogram в основной выбор: Turbo 12, Default 20 и Quality 48. Custom позволяет идти дальше, вплоть до 100 шагов, но кнопка существует не потому, что её обязательно нужно нажимать.
Для масштаба приведу ещё два отдельных запуска Turbo 12 / Normal: 11 минут 39 секунд при 1152×768 и 13 минут 45 секунд при 1024×1024. Это были разные сцены и seed, поэтому две точки нельзя считать диапазоном производительности или тестом влияния разрешения. Но как ориентиры они куда полезнее шестичасового портрета.


32 ГБ: почему Mac вообще не умер
Полный FP8-набор основных файлов занимает около 27,5 GB в десятичных единицах, то есть примерно 25,6 GiB на диске: text encoder, conditional- и unconditional-трансформеры, VAE и tokenizer. Напрямую вычитать этот объём из 32 ГБ unified memory нельзя. Файлы загружаются поэтапно, а к их представлению в памяти добавляются активации, conditioning, латент, промежуточные направления, буферы VAE, кеш MLX и сама macOS, которая тоже почему-то хочет жить.
Секрет в том, что все тяжёлые компоненты не находятся в unified memory одновременно.
prompt ↓ tokenizer → фиксированный Qwen3-VL-8B → conditioning → encoder освобождается ↓ latent → conditional DiT → выгрузка → unconditional DiT → выгрузка ↑ ↓ └────────────────────────── N шагов ───────────────────────────┘ ↓ VAE decode ↓ PNG
Это упрощённый text-to-image путь. В img2img VAE encoder работает до денойзинга, а decoder может использоваться во время шагов для live preview и внутренних проверок технической заглушки. Полноразмерный финальный decode всё равно выполняется после трансформеров.
32 ГБ здесь скорее не квартира, куда я чудом поселил весь checkpoint вместе с мебелью, а маленькая гримёрка. Сначала туда входит Qwen3‑VL, читает caption и уходит. Затем на каждом шаге по очереди заходят два трансформера: один с текстом, второй без него. После денойзинга они освобождаются перед полноразмерным финальным VAE decode; небольшие веса VAE при этом могли загружаться раньше для encode или preview.
И тут появляется лучший сюжетный поворот: VAE весит на диске всего около 160Mb, но при финальном декоде 2048×2048 он становится главным источником пика. Watchdog использует консервативную оценку примерно 24,3Gb для footprint всего render-процесса на этой стадии — это не изолированный размер весов или активаций VAE. Самый маленький актёр привёз самую большую декорацию.

Приложение ограничивает кеш MLX, проверяет прогноз памяти до старта и следит за footprint процесса — объёмом реально занятой им памяти — между шагами. Но это защита, а не математическая гарантия для любой фоновой нагрузки. Отмену можно проверить до и после финального VAE decode. Нажатие Stop во время синхронного MLX-вызова не прерывает его посередине: приложение может лишь дождаться завершения и отбросить результат.
Есть экспериментальный режим Conditional only. Он может работать без файла весов unconditional-трансформера и пропускает проход этой ветви, поэтому экономит диск и время. Однако пик RAM не обязан уменьшиться вдвое: в dual-CFG трансформеры и так загружаются по одному, а главным пиком на 2K может стать финальный декод. Картинка также меняется — это не более дешёвый эквивалент официального dual-CFG.


JSON как интерфейс к модели
У Ideogram 4 есть ещё одна необычная особенность: модель обучалась на структурированных описаниях в формате caption. Обычный текст она тоже принимает, но официальная схема позволяет отдельно описать сцену, стиль, фон, элементы и их координаты.
Упрощённо caption выглядит так:
{ "high_level_description": "a continuous rainy neon street at night", "style_description": { "aesthetics": "cinematic, photorealistic", "lighting": "soft neon reflections on wet surfaces", "photo": "cinematic street photograph, 35 mm lens", "medium": "photograph" }, "compositional_deconstruction": { "background": "wet asphalt, shop windows and distant city lights", "elements": [ {"type": "obj", "bbox": [180, 70, 820, 430], "desc": "a female pilot"}, {"type": "obj", "bbox": [420, 380, 900, 930], "desc": "a sports car"} ] } }
Координаты нормализованы в диапазоне 0…1000 и записаны как [ymin, xmin, ymax, xmax]. Для этой schema важны не только типы полей, но и порядок ключей.

Это тот же фиксированный Qwen3-VL-8B, который уже был показан в memory pipeline: он кодирует caption перед каждым рендером, а его vision tower здесь не используется. Выбираемые Gemma, Qwen3-VL 4B и Qwen3.6 — отдельные локальные помощники. Magic Prompt превращает обычную фразу в JSON, а Describe по явной команде описывает фотографию. Сами изображения эти помощники не рисуют.


Структурированный caption оказался полезен и при неприятном поведении checkpoint. Иногда вместо изображения он возвращает технический refusal frame Image blocked by safety filter. В официальном prompting guide отдельно отмечена более высокая частота ложных срабатываний у не-JSON-промптов.
В моих локальных тестах структурирование caption заметно уменьшило число таких случаев, но не устранило их полностью. Это наблюдение, а не статистический бенчмарк. Cyclonminigen распознаёт известную техническую заглушку и не сохраняет её как успешный результат. Это не NSFW-модерация и не попытка отключить safety-механизмы модели.
Как просьба «не рисовать коллаж» помогла нарисовать коллаж
Самая полезная ошибка случилась уже после первой публикации.
Я добавил Regions: пользователь рисует области на холсте, помечает их как Object или Text, а приложение превращает проценты X/Y/W/H во внутренние bbox модели. На словах всё выглядело прекрасно. На практике несколько реальных рендеров вместо одной сцены выдали набор панелей, фрагментов и повторяющихся объектов.


Сначала подозрение упало на seed, Turbo и плотность компоновки. Но проблема повторилась и на Default. Значит, часы рендера можно было продолжать тратить, но расследованию это уже не помогало.
Причина оказалась в моём caption builder. Я повторял персонажей и предметы одновременно в общем описании, background и elements. Более того, пытаясь запретить нежелательный результат, добавлял в положительный caption слова вроде collage, panels, checkerboard и isolated fragments — только внутри фраз «не делай это».
По воспроизводимому результату я сделал вывод, что сами визуальные токены работали как положительные подсказки, несмотря на отрицание вокруг них. Исправление caption и последующие реальные тесты подтвердили эту рабочую гипотезу.
Упрощённо ошибка выглядела так:
"background": "a pilot and a sports car on a neon street, no collage, no panels, no checkerboard", "elements": [ {"type": "obj", "bbox": [...], "desc": "a pilot"}, {"type": "obj", "bbox": [...], "desc": "a sports car"} ]
Исправлением стало не ещё одно отрицание, а удаление нежелательных визуальных терминов из положительного caption. Внутри compositional_deconstruction подробные описания областей живут в elements, а background описывает только непрерывную среду и не повторяет их.
После исправления я провёл реальные тесты с 4, 6 и 10 областями. Четыре объекта собрались в одну гостиную. Плотная сцена с десятью предметами осталась изображением одного стола, хотя маленькие детали модель всё ещё может потерять или объединить. Это уже ограничение мягкого bbox-conditioning, а не прежний баг caption builder.

Этот эпизод напомнил мне простую вещь: иногда проблема не в модели, seed или sampler. Иногда вы слишком выразительно рассказали нейросети, чего именно не хотите увидеть.



Что осталось полезным, кроме долгого ожидания
После всех расследований Cyclonminigen всё-таки остался приложением, а не коллекцией тестовых утилит.
Character Sheet
Приложение формирует character sheet как одну большую структурированную генерацию: Turnaround ×5, Heads + body или Board 3×3. За счёт общего холста одежда, свет и окружение часто остаются похожими между панелями.


Но это не identity lock и не память персонажа. Рука может измениться, ремень — переехать, а меч — внезапно получить собственное мнение о длине.

Photo → Image
Это настоящий latent img2img. VAE кодирует фотографию в латент, смешивает его с воспроизводимым шумом, после чего запускается тот же пайплайн денойзинга. Низкий Strength обычно сохраняет больше композиции, высокий позволяет уйти дальше. Это не процент сохранённых пикселей и не гарантия побитовой копии: даже VAE roundtrip способен изменить изображение.
В версии 1.1 пустой prompt запускает прямой img2img без языкового помощника. Если описание фотографии действительно нужно, Describe вызывается отдельно. Масок, inpainting и локальной перекраски здесь нет — цветовое или стилевое указание действует на изображение глобально.


Gallery и Library
Gallery сохраняет вместе с PNG промпт, фактический caption, переданный рендереру, seed, размер, пресет и время. Это позволяет повторить рецепт генерации или честно сравнить два результата, если зафиксированы все параметры. Library содержит 176 встроенных карточек; пользовательские записи хранятся отдельно, а Gallery новой установки начинается пустой. Также есть режим Compare где два изображения можно сравнить между собой.





Где заканчивается MIT и начинаются веса
У проекта есть важная лицензионная граница.
Исходный код Cyclonminigen и собственные материалы проекта опубликованы по MIT. Но FP8-checkpoint Ideogram 4 к этой лицензии не относится. Это gated-модель с отдельными Non-Commercial условиями издателя. Открытая лицензия приложения не отменяет ограничений весов.
По принятой политике публичный релиз Cyclonminigen v1.1.19 является source-only: GitHub отдаёт исходные архивы, но не готовое .app или установщик. Веса Ideogram в релиз в любом случае не входят и остаются отдельным gated-компонентом со своими условиями. Для официальной загрузки весов через приложение нужны собственный аккаунт Hugging Face, принятие условий модели и Read token; уже имеющийся совместимый каталог можно подключить read-only без повторной загрузки. Токен хранится в macOS Keychain и используется для авторизованной загрузки.
После того как веса и зависимости находятся на Mac, inference выполняется локально: промпты, исходные фотографии и результаты не отправляются приложением во внешний inference API. Но говорить, что проект «никогда не использует сеть», было бы неверно — модели и SwiftPM-зависимости сначала нужно скачать.

Что в итоге получилось
Cyclonminigen не сделал Ideogram 4 быстрой. На моём Mac два обычных Turbo-рендера около одного мегапикселя заняли 11 минут 39 секунд и 13 минут 45 секунд, а рендер 2048×2048 в режиме Quality однажды потребовал 384 минуты 55 секунд. Если задача требует максимального холста, разумнее запускать её перед сном, а не перед кофе.
Зато тяжёлый пайплайн стал предсказуемым. Я понимаю, какие компоненты по очереди занимают память, почему маленький VAE способен совпасть с самым большим пиком, сколько стоили три реальные конфигурации пресетов и почему Regions однажды превратили единую сцену в коллаж.
Самым полезным оказался не очередной процент оптимизации. Сначала я учил приложение переводить человеческую фразу на язык модели. Потом модель коллажами объяснила, что переводчик ошибается. Наверное, в этом и состоит большая часть разработки локального ИИ: половину времени учишь машину понимать человека, вторую половину — учишься сам понимать машину.
Quality никуда не исчез. Просто теперь я выбираю этот режим не потому, что кнопка существует, а только когда результат действительно стоит ночи вычислений.
Cyclonminigen — независимый проект и не связан с Ideogram. Репозиторий с исходниками, документацией и точными сведениями об upstream-источниках и атрибуции находится на GitHub.
Urichi
Как эксперимент - интересно. А рабочей студией можно будет назвать, когда начнет работать на уровне аналогов по скорости. Примерно х10 нужно.