- Регистрация
- 23 Авг 2023
- Сообщения
- 3,969
- Реакции
- 0
- Баллы
- 36
Ofline
Запуск современных Text-to-Video моделей локально — задача не для слабонервных. Когда китайские исследователи из PKU-YuanGroup выложили в open-source свою модель Open-Sora-Plan, энтузиасты бросились её тестировать. Но есть нюанс: оригинальный пайплайн рассчитан на кластеры уровня H100/A100. Веса модели в полном разрешении занимают десятки гигабайт.
Моя цель заключалась в том, чтобы запустить инференс Open-Sora-Plan (v1.3.0) в спартанских условиях — на абсолютно бесплатном инстансе Google Colab с видеокартой NVIDIA T4 (15 ГБ VRAM, архитектура Turing 2018 года) и 12.7 ГБ системной ОЗУ.
Спойлер: скрипт отработал от начала и до конца без OOM (Out of Memory). Но для этого нам пришлось вскрывать исходники, бороться с аппаратными лимитами GPU и в прямом смысле делать нейросети математическую «лоботомию».
Архитектура Open-Sora-Plan состоит из двух тяжеловесных компонентов:
Суммарно это 30 ГБ. Попытка загрузить всё в 15 ГБ VRAM ожидаемо вызывала смерть ядра. Файл подкачки (SWAP) спасал системную ОЗУ при загрузке весов с диска, но не решал проблему видеопамяти.
Решение: 4-bit квантование и поэтапная выгрузка
Во-первых, мы принудительно квантовали T5-XXL до 4 бит «на лету» с помощью
Python
Во-вторых, мы реализовали Staged Execution. Поскольку текстовый энкодер нужен только на первой секунде (чтобы получить
Мы загружали T5 на GPU, получали векторы, а затем безжалостно убивали объекты с принудительным вызовом сборщика мусора и очисткой кэша CUDA, освобождая плацдарм для загрузки DiT:
Python
На этапе сборки пайплайна мы обнаружили, что в релизе v1.5.0 разработчики выложили VAE-декодер только в формате
Попытка разобраться в их
Bash
Скрипт упорно пытался найти модель на жестком диске китайского аспиранта. Чтобы обойти это, мы написали точечный загрузчик через
Загрузив DiT в видеокарту, мы запустили генерацию. Скрипт отработал 50 шагов, но на выходе выдал абсолютно черный экран.
Проблема крылась в аппаратной несовместимости. Open-Sora обучалась в формате
Нам пришлось опустить
Лимит
VAE-декодер, получая тензор из
Чтобы спасти математику на старом железе, мы написали "Квантовый стабилизатор". Мы не могли перевести всю модель в FP32 (не хватило бы VRAM), поэтому мы перехватили самую уязвимую функцию —
Вот как выглядит этот хак (Monkey-patching):
Python
Аналогичный патч мы применили к слоям
Дополнительно, чтобы гарантированно избежать
Python
Результат: Математика перестала взрываться. Мы победили черный экран. Но ценой семантики.
Из-за того, что мы агрессивно срезали экстремальные значения латентов (которые отвечали за детализацию и форму объектов), нейросеть потеряла структуру. Вместо промпта "Величественный дракон летит над городом" мы получили светящееся, размытое неоновое НЛО (цветовой пиксельный шум).
Да, вместо дракона мы получили пиксельное НЛО. Но это успешный Proof-of-Concept, показывающий, что «железные» ограничения всегда можно обойти, если знать, где ковырять исходники PyTorch.
Моя цель заключалась в том, чтобы запустить инференс Open-Sora-Plan (v1.3.0) в спартанских условиях — на абсолютно бесплатном инстансе Google Colab с видеокартой NVIDIA T4 (15 ГБ VRAM, архитектура Turing 2018 года) и 12.7 ГБ системной ОЗУ.
Спойлер: скрипт отработал от начала и до конца без OOM (Out of Memory). Но для этого нам пришлось вскрывать исходники, бороться с аппаратными лимитами GPU и в прямом смысле делать нейросети математическую «лоботомию».
Вызов 1: OOM Killer и Staged Execution
Архитектура Open-Sora-Plan состоит из двух тяжеловесных компонентов:
Текстовый энкодер (T5-XXL) — переводит промпт в эмбеддинги. Вfp32он весит под 19 ГБ.
Диффузионный трансформер (DiT) — генерирует кадры (ещё около 11 ГБ).
Суммарно это 30 ГБ. Попытка загрузить всё в 15 ГБ VRAM ожидаемо вызывала смерть ядра. Файл подкачки (SWAP) спасал системную ОЗУ при загрузке весов с диска, но не решал проблему видеопамяти.
Решение: 4-bit квантование и поэтапная выгрузка
Во-первых, мы принудительно квантовали T5-XXL до 4 бит «на лету» с помощью
bitsandbytes (формат NF4). Это сжало модель до приемлемых 5-6 ГБ.Python
Код:
import transformers
import torch
kwargs['quantization_config'] = transformers.BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.float16
)
Во-вторых, мы реализовали Staged Execution. Поскольку текстовый энкодер нужен только на первой секунде (чтобы получить
prompt_embeds), нет смысла держать его в памяти при генерации видео.Мы загружали T5 на GPU, получали векторы, а затем безжалостно убивали объекты с принудительным вызовом сборщика мусора и очисткой кэша CUDA, освобождая плацдарм для загрузки DiT:
Python
Код:
del pipe.text_encoder
gc.collect()
torch.cuda.empty_cache()
Вызов 2: Хардкод и следы Huawei Ascend
На этапе сборки пайплайна мы обнаружили, что в релизе v1.5.0 разработчики выложили VAE-декодер только в формате
.ckpt для китайских NPU Huawei Ascend (папка MindSpeed). Привычного config.json для GPU просто не было.Попытка разобраться в их
.sh скриптах привела к забавной находке. В эталонном коде инференса разработчики забыли вычистить пути к своим локальным серверам:Bash
Код:
--ae_path "/home/save_dir/lzj/Formal_8dim/latent8"
Скрипт упорно пытался найти модель на жестком диске китайского аспиранта. Чтобы обойти это, мы написали точечный загрузчик через
huggingface_hub, который "пинцетно" вытащил нужную папку VAE из предыдущего релиза (v1.3.0), игнорируя десятки гигабайт ненужных DiT-весов.Вызов 3: Архитектурный лимит NVIDIA T4 (FP16 vs BFloat16)
Загрузив DiT в видеокарту, мы запустили генерацию. Скрипт отработал 50 шагов, но на выходе выдал абсолютно черный экран.
Проблема крылась в аппаратной несовместимости. Open-Sora обучалась в формате
bfloat16, который обладает огромным динамическим диапазоном (до ~3.39e38). Но видеокарта T4 (Turing) физически не поддерживает вычисления в bfloat16. При попытке запуска PyTorch ругался: RuntimeError: No available kernel.Нам пришлось опустить
torch_dtype до float16. И вот тут начался настоящий ад.Лимит
float16 — это 65504. Во время диффузии, особенно при высоком параметре CFG (Guidance Scale) и в слоях LayerNorm / AdaLN, дисперсия значений стремительно росла. Как только одно из чисел превышало лимит, происходило переполнение (Overflow). Число превращалось в inf, а на следующем шаге умножения матриц — в NaN.VAE-декодер, получая тензор из
NaN, просто декодировал его как нули (черный цвет в RGB).Решение: Хирургический Monkey-Patching (Апкаст Attention)
Чтобы спасти математику на старом железе, мы написали "Квантовый стабилизатор". Мы не могли перевести всю модель в FP32 (не хватило бы VRAM), поэтому мы перехватили самую уязвимую функцию —
scaled_dot_product_attention — и заставили её конвертировать тензоры в float32 ровно на миллисекунду перед перемножением матриц, а затем возвращать обратно в float16.Вот как выглядит этот хак (Monkey-patching):
Python
Код:
import torch.nn.functional as F
orig_sdpa = F.scaled_dot_product_attention
def sdpa_fp32(query, key, value, *args_s, **kwargs_s):
# Принудительный апкаст в FP32 перед вычислением Attention
q, k, v = query.float(), key.float(), value.float()
new_args = tuple(a.float() if isinstance(a, torch.Tensor) and a.dtype.is_floating_point else a for a in args_s)
new_kwargs = {kw: (val.float() if isinstance(val, torch.Tensor) and val.dtype.is_floating_point else val) for kw, val in kwargs_s.items()}
# Считаем в FP32 и возвращаем в исходный формат (FP16)
return orig_sdpa(q, k, v, *new_args, **new_kwargs).to(query.dtype)
# Переопределяем функцию глобально
F.scaled_dot_product_attention = sdpa_fp32
Аналогичный патч мы применили к слоям
LayerNorm внутри пайплайна DiT.Побочный эффект: "Лоботомия" и рождение НЛО
Дополнительно, чтобы гарантированно избежать
NaN, мы внедрили жесткий clamp (ограничитель) латентов перед отправкой их в VAE:Python
Код:
def sanitize(x, limit=15.0):
return torch.nan_to_num(x.float(), nan=0.0, posinf=limit, neginf=-limit)
Результат: Математика перестала взрываться. Мы победили черный экран. Но ценой семантики.
Из-за того, что мы агрессивно срезали экстремальные значения латентов (которые отвечали за детализацию и форму объектов), нейросеть потеряла структуру. Вместо промпта "Величественный дракон летит над городом" мы получили светящееся, размытое неоновое НЛО (цветовой пиксельный шум).
Выводы
Запустить 30-гигабайтную видео-модель на 15 ГБ VRAM реально, если грамотно жонглировать памятью, использовать 4-bit квантование для энкодеров и применятьgc.collect().
Главная проблема инференса современных моделей на картах Turing (T4/RTX 20xx) и Pascal — отсутствие поддержки BFloat16. Переход на FP16 неизбежно ведет к математическим взрывам в слоях Attention.
Точечный апкаст критических слоев в FP32 через Monkey-Patching спасает ядро от краша, но требует ювелирной работы со сэмплером, чтобы не разрушить семантику итогового изображения.
Да, вместо дракона мы получили пиксельное НЛО. Но это успешный Proof-of-Concept, показывающий, что «железные» ограничения всегда можно обойти, если знать, где ковырять исходники PyTorch.