| """ |
| Hugging Face Spaces / ZeroGPU AoTI helper for Qwen image pipelines. |
| |
| Usage: |
| from optimization_optimized import optimize_pipeline_ |
| |
| optimize_pipeline_( |
| pipe, |
| image=[Image.new("RGB", (1024, 1024)), Image.new("RGB", (1024, 1024))], |
| prompt="warmup prompt", |
| num_inference_steps=4, |
| true_cfg_scale=1.0, |
| height=1024, |
| width=1024, |
| ) |
| |
| Design goals: |
| - Keep the same in-place API as your original optimize_pipeline_. |
| - Avoid crashing the Space if AoTI export/compile fails. |
| - Only mark transformer dimensions as dynamic when the captured call actually contains them. |
| - Keep FP8 quantization optional, because it can change quality and may break export on some setups. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import logging |
| from contextlib import nullcontext |
| from typing import Any, Callable, Mapping, Optional, ParamSpec |
|
|
| import torch |
| from torch.utils._pytree import tree_map |
|
|
| try: |
| import spaces |
| except Exception: |
| spaces = None |
|
|
| try: |
| from torchao.quantization import Float8DynamicActivationFloat8WeightConfig, quantize_ |
| except Exception: |
| Float8DynamicActivationFloat8WeightConfig = None |
| quantize_ = None |
|
|
|
|
| P = ParamSpec("P") |
| LOGGER = logging.getLogger(__name__) |
|
|
|
|
| |
| |
| |
| |
| |
| TRANSFORMER_IMAGE_SEQ_LENGTH_DIM = torch.export.Dim("image_seq_length", min=1) |
| TRANSFORMER_TEXT_SEQ_LENGTH_DIM = torch.export.Dim("text_seq_length", min=1) |
|
|
| DEFAULT_TRANSFORMER_DYNAMIC_SHAPES: dict[str, Any] = { |
| "hidden_states": {1: TRANSFORMER_IMAGE_SEQ_LENGTH_DIM}, |
| "encoder_hidden_states": {1: TRANSFORMER_TEXT_SEQ_LENGTH_DIM}, |
| "encoder_hidden_states_mask": {1: TRANSFORMER_TEXT_SEQ_LENGTH_DIM}, |
| "image_rotary_emb": ( |
| {0: TRANSFORMER_IMAGE_SEQ_LENGTH_DIM}, |
| {0: TRANSFORMER_TEXT_SEQ_LENGTH_DIM}, |
| ), |
| } |
|
|
|
|
| |
| |
| DEFAULT_INDUCTOR_CONFIGS: dict[str, Any] = { |
| "conv_1x1_as_mm": True, |
| "epilogue_fusion": False, |
| "coordinate_descent_tuning": True, |
| "coordinate_descent_check_all_directions": True, |
| "max_autotune": True, |
| "triton.cudagraphs": True, |
| } |
|
|
|
|
| def _has_zero_gpu_aoti() -> bool: |
| return ( |
| spaces is not None |
| and hasattr(spaces, "GPU") |
| and hasattr(spaces, "aoti_capture") |
| and hasattr(spaces, "aoti_compile") |
| and hasattr(spaces, "aoti_apply") |
| ) |
|
|
|
|
| def _supports_float8() -> bool: |
| """FP8 is mainly useful on Hopper-class GPUs such as H100/H200.""" |
| if not torch.cuda.is_available(): |
| return False |
| major, _minor = torch.cuda.get_device_capability() |
| return major >= 9 |
|
|
|
|
| def _maybe_disable_progress_bar(pipeline: Callable[..., Any]) -> None: |
| """Avoid tqdm/progress side effects during graph capture.""" |
| setter = getattr(pipeline, "set_progress_bar_config", None) |
| if callable(setter): |
| try: |
| setter(disable=True) |
| except Exception: |
| pass |
|
|
|
|
| def _build_dynamic_shapes( |
| captured_kwargs: Mapping[str, Any], |
| shape_specs: Optional[Mapping[str, Any]] = None, |
| ) -> dict[str, Any]: |
| """ |
| Build a dynamic_shapes dict matching the captured transformer kwargs. |
| |
| torch.export expects the dynamic_shapes tree to mirror args/kwargs. The original |
| code unconditionally OR'ed every known Qwen key into the captured tree. This |
| version only injects a dynamic spec when that key is present in the actual |
| captured transformer call, which makes the helper more robust across Qwen / |
| Diffusers versions. |
| """ |
| dynamic_shapes = tree_map(lambda _leaf: None, dict(captured_kwargs)) |
| specs = dict(shape_specs or DEFAULT_TRANSFORMER_DYNAMIC_SHAPES) |
|
|
| for name, spec in specs.items(): |
| if name not in captured_kwargs: |
| continue |
|
|
| |
| |
| if name == "image_rotary_emb": |
| value = captured_kwargs[name] |
| if not isinstance(value, (tuple, list)) or len(value) != 2: |
| LOGGER.warning( |
| "Skipping dynamic shape for image_rotary_emb: expected a 2-item tuple/list, got %s", |
| type(value).__name__, |
| ) |
| continue |
|
|
| dynamic_shapes[name] = spec |
|
|
| return dynamic_shapes |
|
|
|
|
| def _maybe_quantize_transformer(transformer: torch.nn.Module, enable_float8: bool) -> None: |
| """ |
| Optional in-place FP8 quantization. |
| |
| Keep disabled by default: |
| - It can affect image quality. |
| - It requires torchao. |
| - It is most beneficial on H100/H200-class GPUs. |
| - If export fails after quantization, the module has still been mutated. |
| """ |
| if not enable_float8: |
| return |
|
|
| if quantize_ is None or Float8DynamicActivationFloat8WeightConfig is None: |
| raise RuntimeError("enable_float8=True requires torchao to be installed.") |
|
|
| if not _supports_float8(): |
| raise RuntimeError("enable_float8=True requires a Hopper-class CUDA GPU such as H100/H200.") |
|
|
| LOGGER.info("Applying experimental FP8 dynamic activation + FP8 weight quantization.") |
| quantize_(transformer, Float8DynamicActivationFloat8WeightConfig()) |
|
|
|
|
| def optimize_pipeline_( |
| pipeline: Callable[P, Any], |
| *args: P.args, |
| duration: int = 1500, |
| inductor_configs: Optional[Mapping[str, Any]] = None, |
| dynamic_shape_specs: Optional[Mapping[str, Any]] = None, |
| enable_float8: bool = False, |
| strict_export: bool = False, |
| capture_autocast_dtype: Optional[torch.dtype] = None, |
| fail_silently: bool = True, |
| **kwargs: P.kwargs, |
| ) -> Callable[P, Any]: |
| """ |
| Compile and apply AoTI to pipeline.transformer in-place. |
| |
| Parameters: |
| pipeline: |
| Diffusers-style pipeline with a `.transformer` module. |
| *args, **kwargs: |
| Warmup call passed to the pipeline during AoTI capture. |
| Use representative image size, prompt length, step count, and guidance. |
| duration: |
| ZeroGPU allocation duration for compilation. |
| inductor_configs: |
| Optional overrides for DEFAULT_INDUCTOR_CONFIGS. |
| dynamic_shape_specs: |
| Optional overrides for dynamic-shape specs. |
| enable_float8: |
| Optional experimental torchao FP8 quantization before export. |
| strict_export: |
| Passed to torch.export.export(..., strict=...). |
| False is more permissive for complex model code. |
| capture_autocast_dtype: |
| Set to torch.bfloat16 or torch.float16 if your real inference path uses autocast. |
| Leave None when the pipeline/model is already loaded in the desired dtype. |
| fail_silently: |
| If True, log and keep the original transformer on compile failure. |
| If False, re-raise the error. |
| |
| Returns: |
| The same pipeline object, mutated in-place if compile/apply succeeds. |
| """ |
| if not _has_zero_gpu_aoti(): |
| LOGGER.warning("Hugging Face Spaces AoTI APIs are unavailable; skipping optimization.") |
| return pipeline |
|
|
| transformer = getattr(pipeline, "transformer", None) |
| if transformer is None: |
| message = "pipeline has no `.transformer` attribute; cannot apply transformer AoTI." |
| if fail_silently: |
| LOGGER.warning(message) |
| return pipeline |
| raise AttributeError(message) |
|
|
| if isinstance(transformer, torch.nn.Module): |
| transformer.eval() |
|
|
| _maybe_disable_progress_bar(pipeline) |
|
|
| configs = dict(DEFAULT_INDUCTOR_CONFIGS) |
| if inductor_configs: |
| configs.update(inductor_configs) |
|
|
| |
| @spaces.GPU(duration=duration) |
| def compile_transformer(): |
| LOGGER.info("Capturing transformer call for AoTI export.") |
|
|
| autocast_ctx = ( |
| torch.autocast(device_type="cuda", dtype=capture_autocast_dtype) |
| if capture_autocast_dtype is not None and torch.cuda.is_available() |
| else nullcontext() |
| ) |
|
|
| with torch.inference_mode(), autocast_ctx: |
| with spaces.aoti_capture(transformer) as call: |
| pipeline(*args, **kwargs) |
|
|
| dynamic_shapes = _build_dynamic_shapes(call.kwargs, dynamic_shape_specs) |
|
|
| _maybe_quantize_transformer(transformer, enable_float8) |
|
|
| LOGGER.info("Exporting transformer with torch.export; strict=%s", strict_export) |
| exported = torch.export.export( |
| mod=transformer, |
| args=call.args, |
| kwargs=call.kwargs, |
| dynamic_shapes=dynamic_shapes, |
| strict=strict_export, |
| ) |
|
|
| LOGGER.info("Compiling exported transformer with AoTI.") |
| return spaces.aoti_compile(exported, configs) |
|
|
| try: |
| compiled_transformer = compile_transformer() |
| spaces.aoti_apply(compiled_transformer, transformer) |
| LOGGER.info("AoTI transformer optimization applied successfully.") |
| except Exception as exc: |
| LOGGER.exception("AoTI transformer optimization failed.") |
| if not fail_silently: |
| raise |
| LOGGER.warning("Continuing with the original uncompiled transformer. Error: %s", exc) |
|
|
| return pipeline |
|
|