Перейти к содержанию
Публикация AiManual

Как сэкономить память при запуске diffusion transformers с Quanto и Diffusers

Практическое руководство по снижению VRAM в diffusion-пайплайнах Diffusers с помощью Optimum Quanto. Разбираем, что квантизовать первым, как выбрать между FP8,

Коротко

Что будет в материале

  1. 01

    Короткий ответ: что квантовать в первую очередь для снижения VRAM

  2. 02

    Из чего складывается память diffusion-пайплайна

  3. 03

    Как работает квантование diffusion transformers в Quanto и Diffusers

  4. 04

    Квантование diffusion backbone: главный кандидат на экономию памяти

Короткий ответ: что квантовать в первую очередь для снижения VRAM

Для большинства diffusion-пайплайнов первым кандидатом становится diffusion backbone, или DiT. Он обрабатывает латентное представление изображения на каждом шаге denoising и часто формирует основную часть памяти весов и рабочих буферов. Начните с FP8 или INT8 для backbone, измерьте пиковую VRAM, затем отдельно проверьте текстовые энкодеры. INT4 оставляйте для сценариев с жестким лимитом памяти, потому что риск артефактов, потери деталей и проблем совместимости у него выше.

Практическая последовательность выглядит так: базовый запуск без квантования, квантование backbone через Optimum Quanto, повторное измерение, выборочная обработка одного или нескольких text encoder, затем проверка INT4. Все сравнения нужно проводить при одинаковых разрешении, batch size, seed, scheduler, числе шагов, вычислительном dtype и стратегии offload.

Квантование весов уменьшает только часть расходов. Активные тензоры, CUDA-буферы, attention, VAE, текстовые энкодеры и передача компонентов между CPU и GPU могут сохранить высокий peak VRAM. Поэтому маленький размер модели на диске не гарантирует такой же пропорциональный выигрыш во время генерации.

Рабочий порядок действий

  1. Зафиксируйте базовую конфигурацию. Запишите VRAM после загрузки pipeline и пиковое значение во время генерации.
  2. Квантизуйте diffusion backbone. FP8 подходит для осторожного первого эксперимента, INT8 дает более агрессивное уменьшение размера весов.
  3. Повторите измерение. Сравните allocated VRAM, reserved VRAM и peak VRAM, а не только размер каталога модели.
  4. Проверьте текстовые энкодеры. Их можно оставить в FP16 или BF16, если качество prompt-to-image важнее экономии нескольких гигабайт.
  5. Попробуйте INT4. Этот режим имеет смысл при нехватке VRAM после предыдущих шагов и требует отдельной проверки качества и стабильности.

Компонент с самым большим объемом весов после загрузки не всегда создает максимальный пик во время denoising. Backbone может занимать большую часть памяти постоянно, а активации attention определять кратковременный пик на высоком разрешении.

Что квантование не решает

Память diffusion-пайплайна складывается из нескольких независимых расходов:

  • веса моделей: параметры backbone, text encoder и VAE;
  • активации: промежуточные тензоры, которые появляются во время прохода по сети;
  • временные CUDA-буферы: рабочая память библиотек и ядер GPU;
  • attention-структуры: тензоры, размер которых зависит от числа токенов и формы латентов;
  • служебные объекты: scheduler, токенизаторы, embeddings и буферы pipeline;
  • копии при загрузке: временные объекты на CPU и GPU, возникающие во время преобразования модулей.

При большом разрешении растет площадь латентного пространства, а вместе с ней объем промежуточных активаций. В видеопайплайнах добавляется число кадров, поэтому квантование весов может убрать проблему загрузки, но оставить OOM во время генерации. В таких случаях нужно уменьшать разрешение или batch size, включать поддерживаемый CPU offload и проверять внимание к памяти, а не менять битность вслепую.

Из чего складывается память diffusion-пайплайна

Diffusers собирает несколько компонентов в один pipeline. Обычно в него входят diffusion transformer или другой backbone, один либо несколько текстовых энкодеров, VAE, scheduler и токенизаторы. Каждый модуль загружается и перемещается на устройство по своим правилам, поэтому единая команда квантования для всего объекта pipeline подходит не всегда.

Весы, активации и временные буферы

Quanto прежде всего меняет представление весов поддерживаемых слоев. В упрощенной оценке один параметр в 16-битном формате занимает 2 байта, в 8-битном представлении около 1 байта, в 4-битном около 0,5 байта. На практике к квантованным весам добавляются масштабы, нулевые точки, упаковка и служебные структуры, поэтому итоговый размер отличается от идеальной арифметики.

Битность весов не задает автоматически dtype всех вычислений. Квантованный слой может хранить веса в qint8, qint4 или qfloat8, а активации и часть операций выполнять в FP16 или BF16. Память промежуточных тензоров при этом продолжит зависеть от разрешения, batch size, архитектуры attention и выбранного compute dtype.

Временные буферы появляются во время конкретных операций и могут освобождаться после их завершения. Из-за этого значение VRAM после загрузки и peak VRAM во время denoising отвечают на разные вопросы.

Память при загрузке и память во время генерации

СимптомЧто проверить первымРабочий шаг
OOM возникает при загрузке pipelineВесовые компоненты, dtype и наличие временных копийКвантизовать backbone до переноса на GPU, загрузить остальные модули по отдельности
Pipeline загружается, но падает на denoisingАктивации, attention и peak VRAMСнизить разрешение или batch size, проверить attention memory и offload
Ошибка появляется только при нескольких изображенияхBatch size и размер промежуточных тензоровГенерировать по одному изображению или уменьшить batch size
VRAM заканчивается после добавления text encoderКоличество энкодеров и их dtypeОставить критичный энкодер в FP16 или BF16, остальные квантизовать выборочно

VAE и scheduler обычно требуют меньше памяти, чем крупный backbone, но исключения встречаются в пайплайнах с дополнительными модулями. Квантизация VAE ради формальной однородности часто не оправдывает риск изменения декодирования. Сначала измерьте его фактический вклад.

Как работает квантование diffusion transformers в Quanto и Diffusers

Optimum Quanto отвечает за представление квантованных весов и преобразование поддерживаемых модулей. Diffusers загружает отдельные компоненты и передает их pipeline. PyTorch задает устройство и вычислительный dtype, но не превращает автоматически весь pipeline в INT4 или FP8.

Квантование весов не равно изменению compute dtype

Параметры weights и torch_dtype описывают разные уровни настройки. Первый определяет способ хранения весов в квантованных слоях. Второй влияет на загрузку и вычисления тех операций, которые работают в обычном формате.

Поэтому комбинация weights=qfloat8 и torch_dtype=torch.bfloat16 не противоречит сама себе. Весы backbone квантизуются, а вычисления, активации и неподдерживаемые операции могут оставаться в BF16. Итоговую память и скорость нужно измерять на конкретной связке GPU, PyTorch, CUDA, Diffusers и Quanto.

Какие части пайплайна квантируются отдельно

Backbone, текстовые энкодеры и VAE представляют собой разные модули. Для каждого нужно отдельно проверить поддерживаемые слои, устройство, dtype и способ сохранения. Имена text_encoder и text_encoder_2 часто встречаются в Diffusers, но конкретный pipeline может использовать один энкодер, несколько энкодеров или собственную структуру компонентов.

Самый надежный подход, выборочная квантизация. Сначала меняется один модуль, затем измеряются VRAM, время генерации и качество. Если результат приемлем, добавляется следующий компонент.

Квантование diffusion backbone: главный кандидат на экономию памяти

Базовый вариант: квантовать только backbone

Начальный эксперимент должен менять один фактор. Оставьте text encoder и VAE в исходной конфигурации, а к backbone примените FP8 или INT8. Для Flux-подобного pipeline шаблон может выглядеть так:

import torch
from diffusers import FluxPipeline, FluxTransformer2DModel
from optimum.quanto import freeze, qfloat8, quantize

model_id = 'your-model-id'

transformer = FluxTransformer2DModel.from_pretrained(
    model_id,
    subfolder='transformer',
    torch_dtype=torch.bfloat16,
)

quantize(transformer, weights=qfloat8)
freeze(transformer)

pipe = FluxPipeline.from_pretrained(
    model_id,
    transformer=transformer,
    torch_dtype=torch.bfloat16,
)
pipe = pipe.to('cuda')

image = pipe(
    'a detailed industrial workshop, natural light',
    num_inference_steps=20,
).images[0]

Это шаблон для pipeline с классами FluxTransformer2DModel и FluxPipeline. Для другого DiT нужно использовать класс backbone и pipeline, предусмотренные его реализацией в Diffusers. Название компонента, аргумент загрузки и набор текстовых энкодеров нельзя переносить между моделями механически.

quantize меняет выбранный модуль, а freeze фиксирует его параметры для инференса. Квантизация должна выполняться до переноса всей конструкции на GPU, если это позволяет конкретная модель. Так уменьшается риск временного хранения полноразмерной и квантованной копий одновременно.

Размещение на устройстве и offload

После квантования проверьте, где находится backbone и где находятся остальные компоненты. Ошибка устройства возникает, когда входные тензоры, модель и отдельный энкодер ожидают разные устройства.

  • При полном размещении на GPU перемещайте pipeline после подготовки компонентов.
  • При CPU offload используйте штатный механизм Diffusers, если он совместим с выбранным квантованным модулем.
  • Не сочетайте без проверки вызов pipe.to('cuda') с offload: компоненты могут оказаться на GPU одновременно.
  • Следите за моментом загрузки, потому что временная полная копия модуля способна вызвать OOM еще до первой генерации.

Offload уменьшает VRAM за счет передачи модулей между CPU и GPU. Цена, время обмена и требования к оперативной памяти зависят от pipeline и конкретного сценария.

Текстовые энкодеры: когда их тоже стоит квантовать

Один или несколько text encoder

Текстовые энкодеры кодируют prompt и могут заметно увеличивать память при старте, особенно если pipeline содержит два крупных языковых модуля. В некоторых схемах после кодирования они не создают главный пик denoising, поэтому их агрессивное квантование не всегда дает заметный результат во время генерации.

Состав компонентов лучше определить программно и обработать только реально существующие модули:

from optimum.quanto import freeze, qfloat8, quantize

for name in ('text_encoder', 'text_encoder_2'):
    encoder = getattr(pipe, name, None)
    if encoder is not None:
        quantize(encoder, weights=qfloat8)
        freeze(encoder)

Этот фрагмент нужно выполнять до переноса pipeline на GPU либо на том устройстве, которое поддерживает выбранная версия Quanto. Для text encoder с нестандартным устройством или интерфейсом сначала проверьте загрузку и генерацию без квантования.

Смешанное квантование компонентов

Смешанная схема часто дает более предсказуемый результат, чем одинаковая битность для всех модулей:

КомпонентОсторожная схемаАгрессивная схема
Diffusion backboneFP8INT8 или INT4 после проверки
Основной text encoderBF16 или FP16FP8 или INT8
Второй text encoderFP8 или INT8INT4 только при нехватке VRAM
VAEИсходный dtypeКвантование только после отдельного теста

Например, backbone можно оставить в FP8, а энкодеры перевести в INT8. Другой вариант, INT8 для backbone и BF16 для текстовой части, если чувствительность prompt adherence выше, чем потребность в минимальном размере весов. Каждая комбинация требует собственной проверки.

Квантование FP8 в Diffusers: когда выбрать FP8, INT8 или INT4

РежимЭкономия на весахРиск для качестваКогда начинать проверку
FP8УмереннаяОбычно осторожный первый вариант, но зависит от слоев и GPUКогда нужен компромисс с минимальным вмешательством
INT8Выше, чем у FP8 в подходящих конфигурацияхСредний, оценивается по компонентамКогда FP8 не освобождает достаточно VRAM
INT4Максимальная среди трех режимов для весовВыше риск потери деталей, prompt adherence и стабильностиПри жестком лимите VRAM после проверки FP8 и INT8

FP8 как осторожная отправная точка

FP8 сохраняет больше числовой информации, чем 4-битные варианты, и часто подходит для первого эксперимента с Quanto. Название FP8 само по себе не гарантирует поддержку нужных операций. Проверьте GPU, версию PyTorch, CUDA, Diffusers и Quanto, затем убедитесь, что вычислительный dtype согласован с pipeline.

В коде Quanto может использоваться объект вроде qfloat8. Точное имя доступного FP8-типа зависит от версии пакета. Если ядро операции не поддерживается, ошибка проявится при запуске конкретного слоя, а не обязательно при импорте библиотеки.

INT8 как компромиссный режим

INT8 подходит, когда размера весов в FP8 недостаточно для целевого GPU, а переход к INT4 кажется чрезмерным. Сравнивайте его с FP8 по четырем показателям: peak VRAM, время первого запуска, длительность генерации и качество на наборе промптов.

В Quanto может использоваться квантователь qint8, если он экспортируется вашей версией пакета и поддерживается нужным модулем. Менять только один импорт недостаточно. После замены типа нужно повторить проверку загрузки, устройств, dtype и результата.

INT4: максимальная экономия с более жесткими оговорками

INT4 сокращает размер весов сильнее, но оставляет меньше запаса для чувствительных слоев. Возможные признаки проблем: потеря мелких деталей, слабое следование длинному prompt, изменение композиции, артефакты на лицах и нестабильное поведение на отдельных сценах.

INT4 оправдан, когда модель не помещается в доступную VRAM после квантизации backbone и текстовых энкодеров в более высокой точности. Используйте его как отдельный профиль, а не как автоматическую замену всем компонентам. В некоторых версиях Quanto тип может называться qint4, но доступность конкретного объекта и поддержка слоев должны подтверждаться установленным пакетом.

Практическая настройка Quanto в Diffusers

Минимальная конфигурация для первого запуска

Сначала уберите лишние переменные. Возьмите один prompt, одно разрешение, один seed и небольшой набор шагов, достаточный для проверки самого pipeline. После успешного запуска добавляйте квантование по одному компоненту.

import torch

if not torch.cuda.is_available():
    raise RuntimeError('CUDA is not available')

device = torch.device('cuda')
generator = torch.Generator(device=device).manual_seed(1234)

torch.cuda.empty_cache()
torch.cuda.reset_peak_memory_stats(device)

pipe = pipe.to(device)
result = pipe(
    'a red bicycle near a concrete wall, overcast light',
    height=512,
    width=512,
    num_inference_steps=20,
    generator=generator,
)

print('allocated:', torch.cuda.memory_allocated(device))
print('reserved:', torch.cuda.memory_reserved(device))
print('peak allocated:', torch.cuda.max_memory_allocated(device))
print('peak reserved:', torch.cuda.max_memory_reserved(device))

Размер 512 на 512 в этом примере нужен для контрольного запуска, а не как универсальная рекомендация для всех моделей. Для честного сравнения сохраните одинаковые параметры в каждом профиле.

Переход от полного pipeline к выборочной квантализации

  1. Исходный профиль: все компоненты загружаются в выбранном базовом dtype.
  2. Профиль backbone: меняется только diffusion transformer.
  3. Смешанный профиль: к backbone добавляется один или несколько текстовых энкодеров.
  4. Профиль INT4: более низкая битность включается только после фиксации результатов предыдущих вариантов.

Для каждого профиля записывайте время загрузки, VRAM после загрузки, peak VRAM, время генерации и визуальные отличия. Такой журнал помогает отделить эффект Quanto от влияния разрешения, числа шагов, offload или случайно измененного seed.

Совместимость версий и аппаратуры

Квантование зависит сразу от нескольких слоев стека: GPU, драйвера, CUDA, PyTorch, Diffusers и Optimum Quanto. Поддержка типа веса еще не означает поддержку всех операций конкретной архитектуры.

  • Зафиксируйте версии Python, PyTorch, CUDA, Diffusers и Quanto.
  • Запишите модель GPU и используемый compute dtype.
  • Проверьте импорт квантователей и загрузку каждого компонента.
  • Сохраните рабочую конфигурацию до обновления библиотек.
  • После обновления повторите холодный запуск и тест качества.

Команда установки без привязки к версиям редко подходит для воспроизводимого гайда. Сначала выберите совместимую связку, потом фиксируйте ее в окружении проекта.

Как сохранить и загрузить квантованную модель

Сохранение через штатный API Diffusers

Предпочтительный формат зависит от того, поддерживает ли конкретный класс квантованных модулей сохранение через save_pretrained. Если поддержка есть, можно сохранить отдельный backbone:

transformer.save_pretrained('quantized-transformer')

При поддержке сохранения на уровне pipeline используется аналогичный подход:

pipe.save_pretrained('quantized-pipeline')

Перед этим проверьте, что каталог содержит конфигурацию компонента и сведения, необходимые загрузчику Quanto. Если класс не сохраняет квантованные параметры штатно, переносить только файлы весов в обычный каталог Diffusers рискованно: при загрузке компонент может интерпретироваться как полноточный.

Что проверить после повторной загрузки

  • структуру каталога и конфигурационные файлы;
  • наличие метаданных о квантовании, если их записывает используемая версия;
  • устройство backbone, text encoder и VAE;
  • фактический dtype вычислений;
  • VRAM при холодной загрузке;
  • результат на тех же prompt и seed.

Проверка torch_dtype не доказывает, что веса остались квантованными. Квантованный модуль может выполнять вычисления в FP16 или BF16. Надежнее сверять конфигурацию, структуру модуля и пиковую память с исходным запуском.

Версии и переносимость сохраненной модели

Сохраненный артефакт связан с классами Diffusers, форматом конфигурации и механизмом сериализации Quanto. Обновление библиотеки может привести к ошибке неизвестного поля, отсутствующему классу квантования или неподдерживаемой операции.

Храните рядом с моделью список версий, выбранный тип квантования, compute dtype, GPU и команду запуска. Размер каталога на диске и peak VRAM после загрузки фиксируйте раздельно. Маленький файл может распаковываться во временные буферы, которые заметно увеличат потребление памяти.

Где квантование начинает влиять на качество и стабильность

Признаки ухудшения качества

Проверяйте результат на нескольких типах сцен, а не на одном удачном prompt. В набор можно включить портрет, сложную композицию с несколькими объектами, сцену с мелкими материалами, изображение с надписью и prompt с пространственными отношениями.

  • Prompt adherence: все ли объекты и атрибуты из запроса появились в сцене;
  • композиция: сохранились ли расположение, масштаб и взаимное положение объектов;
  • детали: не исчезли ли фактура, мелкие контуры и особенности освещения;
  • лица и руки: не выросло ли число деформаций;
  • повторяемость: одинаково ли ведет себя профиль на разных seed и prompt.

Одинаковый seed облегчает сравнение, но не гарантирует пиксельную идентичность после изменения вычислений. Сравнивайте смысловой результат и характер артефактов, а не только разницу пикселей.

Признаки технической нестабильности

После включения квантования возможны OOM, NaN, ошибки device mismatch и сообщения о неподдерживаемой операции. Причина может находиться в одном слое, а проявиться только на определенном разрешении.

  1. Проверьте устройства всех компонентов и входных тензоров.
  2. Сверьте compute dtype backbone, энкодеров и VAE.
  3. Отключите квантование text encoder, если ошибка появилась после его добавления.
  4. Замените INT4 на INT8 или FP8.
  5. Уменьшите разрешение и batch size, чтобы отделить проблему весов от нехватки памяти активаций.
  6. Проверьте отдельный минимальный prompt и холодный запуск.

Если после перехода на более высокую точность ошибка исчезла, причина, вероятно, связана с конкретным квантованным модулем или операцией. Если ошибка сохраняется, ищите проблему в размещении компонентов, памяти или совместимости окружения.

Как измерить экономию VRAM без самообмана

Какие метрики фиксировать

МетрикаЧто показывает
Размер весов на дискеОбъем сохраненного артефакта, но не пик во время работы
torch.cuda.memory_allocatedПамять, занятая активными объектами PyTorch в момент измерения
torch.cuda.memory_reservedПамять, удерживаемая аллокатором PyTorch
torch.cuda.max_memory_allocatedПиковое выделение памяти с момента сброса статистики
torch.cuda.max_memory_reservedМаксимальный объем, зарезервированный аллокатором
CPU RAM и время передачиЦена CPU offload и перемещения компонентов

Перед каждым прогоном очищайте неиспользуемые объекты, вызывайте torch.cuda.empty_cache() и сбрасывайте peak-статистику. Снимайте показатели до загрузки, после загрузки и после генерации. Холодный старт и прогретый запуск храните в разных строках отчета.

Набор проверочных промптов

Минимальный набор должен включать короткий prompt, длинное описание, сцену с несколькими объектами, портрет и изображение с мелкими деталями. Для каждого профиля используйте одинаковые разрешение, число шагов, scheduler, seed и вычислительный dtype.

Если задача связана с текстом на изображении или точной композицией, добавьте отдельные проверки для этих случаев. Один визуально удачный пример не подтверждает пригодность INT4 для всего рабочего набора.

Публикуя цифры, указывайте модель, класс pipeline, GPU, версии PyTorch, Diffusers и Quanto, разрешение, batch size, число шагов, seed и стратегию offload. Без этих параметров сравнение peak VRAM остается неполным.

Когда квантование помогает запустить большую T2I-модель на доступном GPU

Матрица выбора под ограничение памяти

ПриоритетНачальная конфигурацияОбязательная проверка
Сохранить качество и снизить рискFP8 для backbone, text encoder в BF16 или FP16Поддержка FP8, peak VRAM, prompt adherence
Получить заметно меньший размер весовINT8 для backbone, выборочная квантизация энкодеровКачество деталей, время генерации, ошибки операций
Уместить модель при жестком лимитеINT4 для части или всех подходящих модулейАртефакты, NaN, device mismatch, повторная загрузка
Сохранить критичные компонентыСмешанная схема, backbone в низкой битности, энкодер в BF16Общий peak VRAM и качество целевых prompt

Если проблема связана с объемом весов, Quanto может освободить место для запуска более крупного T2I-пайплайна на GPU с ограниченной VRAM. Если пик создают активации, одно квантование весов даст ограниченный результат. Тогда понадобятся меньшие разрешение и batch size, CPU offload или раздельное размещение компонентов.

Когда одного Quanto недостаточно

  • Высокое разрешение увеличивает пространственное число токенов и объем активаций.
  • Batch size больше единицы масштабирует часть рабочих тензоров вместе с количеством изображений.
  • Несколько текстовых энкодеров могут занимать значимую память еще до начала denoising.
  • Видео добавляет измерение кадров и повышает требования к промежуточным буферам.
  • Offload снижает VRAM, но увеличивает объем CPU RAM и обмен данными.
  • Дополнительные control, adapter или conditioning-модули меняют профиль памяти.

Для пайплайнов, где главная проблема связана с загрузкой весов, можно сравнить Quanto с другими способами квантизации в Diffusers. Например, в разборе Stable Diffusion 3.5 Large в Diffusers отдельно описаны CPU offload и 4-битная NF4-квантизация через bitsandbytes. Это другой стек, поэтому его результаты нельзя переносить на Quanto без повторного измерения.

Итоговая схема настройки

  1. Загрузите исходный pipeline и измерьте память после загрузки и на пике генерации.
  2. Определите источник ограничения: веса, активации, attention, текстовые энкодеры или временные копии.
  3. Квантизуйте только diffusion backbone, начав с FP8.
  4. Если памяти все еще мало, сравните INT8 и повторите тест качества.
  5. Отдельно проверьте один или несколько text encoder, не меняя VAE без необходимости.
  6. Используйте INT4 при жестком лимите VRAM и только после проверки артефактов, NaN и совместимости.
  7. При проблемах с активациями добавьте уменьшение разрешения, batch size или поддерживаемый CPU offload.
  8. Сохраните рабочий компонент или pipeline через save_pretrained, если это поддерживает конкретная связка классов и версий.
  9. Повторите холодную загрузку и сравните ее с исходным прогоном по памяти, времени, качеству и стабильности.

Практический выбор между FP8, INT8 и INT4 определяется четырьмя параметрами: пиковая VRAM, качество изображения, стабильность операций и переносимость окружения. Начинайте с backbone, добавляйте изменения постепенно и оставляйте наиболее агрессивную битность для тех случаев, где она действительно решает проблему запуска.

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