Files
wehub-resource-sync cddb07a176
build container image / cpu (push) Waiting to run
build container image / cuda (push) Waiting to run
build container image / rocm (push) Waiting to run
frontend tests / frontend-tests (push) Waiting to run
openapi checks / openapi-checks (push) Waiting to run
python tests / py3.12: macos-default (push) Waiting to run
python tests / py3.11: windows-cpu (push) Waiting to run
python tests / py3.12: windows-cpu (push) Waiting to run
python tests / py3.11: linux-cpu (push) Waiting to run
python tests / py3.12: linux-cpu (push) Waiting to run
typegen checks / typegen-checks (push) Waiting to run
uv lock checks / uv-lock-checks (push) Waiting to run
frontend checks / frontend-checks (push) Waiting to run
lfs checks / lfs-check (push) Waiting to run
python checks / python-checks (push) Waiting to run
python tests / py3.11: macos-default (push) Waiting to run
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:06 +08:00

200 lines
9.4 KiB
Python

from typing import Literal
import torch
from diffusers.models.autoencoders.autoencoder_kl import AutoencoderKL
from diffusers.models.autoencoders.autoencoder_kl_qwenimage import AutoencoderKLQwenImage
from diffusers.models.autoencoders.autoencoder_kl_wan import AutoencoderKLWan
from diffusers.models.autoencoders.autoencoder_tiny import AutoencoderTiny
from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR
from invokeai.backend.flux.modules.autoencoder import AutoEncoder
def estimate_vae_working_memory_sd15_sdxl(
operation: Literal["encode", "decode"],
image_tensor: torch.Tensor,
vae: AutoencoderKL | AutoencoderTiny,
tile_size: int | None,
fp32: bool,
) -> int:
"""Estimate the working memory required to encode or decode the given tensor."""
# It was found experimentally that the peak working memory scales linearly with the number of pixels and the
# element size (precision). This estimate is accurate for both SD1 and SDXL.
element_size = 4 if fp32 else 2
# This constant is determined experimentally and takes into consideration both allocated and reserved memory. See #8414
# Encoding uses ~45% the working memory as decoding.
scaling_constant = 2200 if operation == "decode" else 1100
latent_scale_factor_for_operation = LATENT_SCALE_FACTOR if operation == "decode" else 1
if tile_size is not None:
if tile_size == 0:
tile_size = vae.tile_sample_min_size
assert isinstance(tile_size, int)
h = tile_size
w = tile_size
working_memory = h * w * element_size * scaling_constant
# We add 25% to the working memory estimate when tiling is enabled to account for factors like tile overlap
# and number of tiles. We could make this more precise in the future, but this should be good enough for
# most use cases.
working_memory = working_memory * 1.25
else:
h = latent_scale_factor_for_operation * image_tensor.shape[-2]
w = latent_scale_factor_for_operation * image_tensor.shape[-1]
working_memory = h * w * element_size * scaling_constant
if fp32:
# If we are running in FP32, then we should account for the likely increase in model size (~250MB).
working_memory += 250 * 2**20
return int(working_memory)
def estimate_vae_working_memory_cogview4(
operation: Literal["encode", "decode"], image_tensor: torch.Tensor, vae: AutoencoderKL
) -> int:
"""Estimate the working memory required by the invocation in bytes."""
latent_scale_factor_for_operation = LATENT_SCALE_FACTOR if operation == "decode" else 1
h = latent_scale_factor_for_operation * image_tensor.shape[-2]
w = latent_scale_factor_for_operation * image_tensor.shape[-1]
element_size = next(vae.parameters()).element_size()
# This constant is determined experimentally and takes into consideration both allocated and reserved memory. See #8414
# Encoding uses ~45% the working memory as decoding.
scaling_constant = 2200 if operation == "decode" else 1100
working_memory = h * w * element_size * scaling_constant
print(f"estimate_vae_working_memory_cogview4: {int(working_memory)}")
return int(working_memory)
def estimate_vae_working_memory_flux(
operation: Literal["encode", "decode"], image_tensor: torch.Tensor, vae: AutoEncoder
) -> int:
"""Estimate the working memory required by the invocation in bytes."""
latent_scale_factor_for_operation = LATENT_SCALE_FACTOR if operation == "decode" else 1
out_h = latent_scale_factor_for_operation * image_tensor.shape[-2]
out_w = latent_scale_factor_for_operation * image_tensor.shape[-1]
element_size = next(vae.parameters()).element_size()
# This constant is determined experimentally and takes into consideration both allocated and reserved memory. See #8414
# Encoding uses ~45% the working memory as decoding.
scaling_constant = 2200 if operation == "decode" else 1100
working_memory = out_h * out_w * element_size * scaling_constant
print(f"estimate_vae_working_memory_flux: {int(working_memory)}")
return int(working_memory)
def estimate_vae_working_memory_anima(
operation: Literal["encode", "decode"],
image_tensor: torch.Tensor,
vae: AutoencoderKLWan,
tile_size: int | None,
) -> int:
"""Estimate the working memory required to encode or decode with the Wan 2.1 VAE (Anima).
The Wan VAE uses 3D convolutions and needs noticeably more working memory per output
pixel than the 2D VAEs estimated above. Calibrated empirically on a 1024x1024 fp16
decode: peak reserved memory was ~5.95GB for a full decode and ~1.73GB with 512px
tiles (384px stride), i.e. ~2900 bytes per output pixel per element byte. Encoding
follows the house ratio of ~50% of decode.
"""
element_size = next(vae.parameters()).element_size()
scaling_constant = 2900 if operation == "decode" else 1450
if tile_size is not None:
h = tile_size
w = tile_size
# Add 25% to account for tile overlap.
working_memory = h * w * element_size * scaling_constant * 1.25
else:
latent_scale_factor_for_operation = LATENT_SCALE_FACTOR if operation == "decode" else 1
h = latent_scale_factor_for_operation * image_tensor.shape[-2]
w = latent_scale_factor_for_operation * image_tensor.shape[-1]
working_memory = h * w * element_size * scaling_constant
return int(working_memory)
def estimate_vae_working_memory_qwen_image(
operation: Literal["encode", "decode"], image_tensor: torch.Tensor, vae: AutoencoderKLQwenImage
) -> int:
"""Estimate the working memory required by the invocation in bytes.
The Qwen Image VAE is a video-style autoencoder that operates on 5D tensors of shape
(B, C, num_frames, H, W). Tiling is not used, so peak working memory scales with the full
spatial output. The two trailing dimensions are the spatial H/W in latent space (decode) or
pixel space (encode), matching the convention used by the other estimators here.
"""
latent_scale_factor_for_operation = LATENT_SCALE_FACTOR if operation == "decode" else 1
h = latent_scale_factor_for_operation * image_tensor.shape[-2]
w = latent_scale_factor_for_operation * image_tensor.shape[-1]
element_size = next(vae.parameters()).element_size()
# The Qwen Image VAE is much heavier than the SD/SDXL VAE and needs correspondingly larger
# constants. These were calibrated by measuring peak *reserved* memory growth (not just allocated
# -- reserved is what the cache's `free >= estimate` check compares against) across a resolution
# grid in fp16, on both an AMD W7900 (ROCm) and an NVIDIA card (CUDA). See
# scripts/calibrate_qwen_vae_working_memory.py.
#
# Implied constant = reserved_bytes / (h * w * element_size). Per-point maxima (fp16):
# 512^2 768^2 1024^2 1536^2 1792^2 2048^2 -> ship (max observed + ~8% headroom)
# ROCm decode 5132 4596 4570 3273 3735 4813 -> 5500
# ROCm encode 5864 5858 5858 3532 4364 (OOM) -> 6300
# CUDA decode 2660 2519 2690 2671 2281 (OOM) -> 2900
# CUDA encode 1456 1451 1458 1456 1455 1455 -> 1600
#
# Why this branches on backend (the only estimator here that does):
# - The Qwen VAE is attention-heavy. With Flash/efficient attention (CUDA) the attention memory
# is O(area) and the curve is flat/linear; the ROCm build falls back to math attention, which
# is O(area^2), so ROCm reserves ~2x (decode) to ~4x (encode) more and goes super-linear above
# ~1792^2. The two backends differ far more than any headroom, so a single constant would
# either under-estimate on ROCm (OOM) or massively over-budget on CUDA (needless eviction).
# - "Encoding is half of decoding" (as the sibling estimators assume) is only true on CUDA. On
# ROCm encode reserves >= decode, so the ROCm encode constant is sized accordingly -- this is
# the path Qwen Image Edit exercises.
# - On ROCm the linear model under-estimates for decodes well above 2048^2, but those OOM on a
# 48GB card regardless; on CUDA the curve stays linear so no extra term is needed.
is_rocm = torch.version.hip is not None
if operation == "decode":
scaling_constant = 5500 if is_rocm else 2900
else: # encode
scaling_constant = 6300 if is_rocm else 1600
working_memory = h * w * element_size * scaling_constant
return int(working_memory)
def estimate_vae_working_memory_sd3(
operation: Literal["encode", "decode"], image_tensor: torch.Tensor, vae: AutoencoderKL
) -> int:
"""Estimate the working memory required by the invocation in bytes."""
# Encode operations use approximately 50% of the memory required for decode operations
latent_scale_factor_for_operation = LATENT_SCALE_FACTOR if operation == "decode" else 1
h = latent_scale_factor_for_operation * image_tensor.shape[-2]
w = latent_scale_factor_for_operation * image_tensor.shape[-1]
element_size = next(vae.parameters()).element_size()
# This constant is determined experimentally and takes into consideration both allocated and reserved memory. See #8414
# Encoding uses ~45% the working memory as decoding.
scaling_constant = 2200 if operation == "decode" else 1100
working_memory = h * w * element_size * scaling_constant
print(f"estimate_vae_working_memory_sd3: {int(working_memory)}")
return int(working_memory)