Spaces:
Running on Zero
Running on Zero
multimodalart HF Staff
Price bookings against the AoTI blocks, generate from 2 s again, cross-link the demos
92693c1 | """ZeroGPU AoTI for MiniMax-H3: one compiled `MiniMaxH3TransformerBlock` package, reused by all 50 blocks. | |
| Shared byte-identically by every MiniMax-H3 Space. A Space only calls `maybe_load()`; the rest is the build path. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from pathlib import Path | |
| AOTI = os.environ.get("H3_AOTI", "0") == "1" | |
| AOTI_REPO = os.environ.get("H3_AOTI_REPO", "multimodalart/minimax-h3-aoti") | |
| AOTI_REPO_TYPE = os.environ.get("H3_AOTI_REPO_TYPE", "model") | |
| # A package is valid for exactly one `<width>/torch<X.Y>/sm<cc>/<shape>`, and a mismatched one segfaults rather than | |
| # raising, so `maybe_load` refuses anything but this key. | |
| AOTI_KEY = os.environ.get("H3_AOTI_KEY", "bf16/torch2.11/sm120/dynamic") | |
| # `dynamic` is the sequence dimension: `build_packed_sequence` pads nothing, so `S` moves with the prompt as well as | |
| # the canvas and a static package would serve one prompt length. | |
| AOTI_SHAPE = os.environ.get("H3_AOTI_SHAPE", "dynamic") | |
| AOTI_DURATION = int(os.environ.get("H3_AOTI_DURATION", "1500")) | |
| # Where a step spends its time. `MiniMaxH3TokenRefinerBlock` is also repeated but runs a handful of text rows. | |
| BLOCK_CONTAINER = "transformer_blocks" | |
| # Height of the AdaLN table baked into the package. `temb` grows from 1 row (step 0, both streams at one noise level) | |
| # to 2 (from step 1, sigmas diverged), and the block gathers from `3 * rows`, so the row count is part of the compiled | |
| # shape and is pinned by padding on both sides of the compile. Must match the package's `H3_AOTI_TEMB_ROWS`. | |
| TEMB_ROWS = int(os.environ.get("H3_AOTI_TEMB_ROWS", "4")) | |
| _LOADED: set[int] = set() | |
| def pad_temb(temb, rows: int = TEMB_ROWS): | |
| """Grow `temb` to exactly `rows` timestep rows by repeating its last one.""" | |
| present = temb.shape[0] | |
| if present == rows: | |
| return temb | |
| if present > rows: | |
| raise RuntimeError( | |
| f"{present} distinct timesteps, but this AoTI package holds at most {rows}. " | |
| f"Recompile with H3_AOTI_TEMB_ROWS>={present}." | |
| ) | |
| import torch | |
| return torch.cat([temb, temb[-1:].expand(rows - present, *temb.shape[1:])], dim=0) | |
| def width() -> str: | |
| """Which transformer these artifacts belong to: `bf16`, `fp8`, `nvfp4`, ...""" | |
| if explicit := os.environ.get("H3_WIDTH"): | |
| return explicit.lower() | |
| try: | |
| import h3_core | |
| return h3_core.WIDTH | |
| except Exception: | |
| return "bf16" | |
| def artifact_key() -> str | None: | |
| """`<width>/torch<X.Y>/sm<cc>/<shape>` of the card this process is on, or `None` when there is no CUDA.""" | |
| try: | |
| import torch | |
| torch_version = ".".join(torch.__version__.split(".")[:2]) | |
| major, minor = torch.cuda.get_device_capability() | |
| except Exception: | |
| return None | |
| return f"{width()}/torch{torch_version}/sm{major}{minor}/{AOTI_SHAPE}" | |
| def status() -> str: | |
| return ( | |
| f"AoTI **on** · `{AOTI_REPO}` ({AOTI_REPO_TYPE}) · shape `{AOTI_SHAPE}`" | |
| if AOTI | |
| else "AoTI **off** (`H3_AOTI=1` to load compiled blocks)" | |
| ) | |
| def patch_blocks(transformer, package_dir) -> None: | |
| """Point all 50 blocks at the one compiled package, binding each block's own weights on its first call. | |
| `spaces.aoti_load_from_package_dir` with two changes. Weights are read on the first forward rather than at patch | |
| time, because this runs at startup and `Module.to` later rebinds `param.data` to fresh CUDA tensors. And `temb` is | |
| padded to the height the package was exported with — see `TEMB_ROWS`. | |
| """ | |
| from spaces.zero.torch.aoti import LazyAOTIModel, _shallow_clone_module | |
| from torch._functorch._aot_autograd.subclass_parametrization import unwrap_tensor_subclass_parameters | |
| # `LazyAOTIModel` binds constants by name and silently keeps what it cannot match, which is a SIGSEGV rather than | |
| # an error. The patch resolves anonymous names through the compile side's sidecar and raises if it still cannot. | |
| try: | |
| from spaces_constant_binding_patch import apply_spaces_constant_binding_patch | |
| apply_spaces_constant_binding_patch() | |
| except ImportError: | |
| print("[h3-aoti] spaces_constant_binding_patch.py is missing; an unbindable constant would segfault", flush=True) | |
| model = LazyAOTIModel(Path(package_dir) / "submodules" / BLOCK_CONTAINER / "package.pt2") | |
| for block in getattr(transformer, BLOCK_CONTAINER): | |
| bound: dict = {} | |
| def forward(hidden_states, temb, *rest, _block=block, _bound=bound): | |
| first = not _bound | |
| if first: | |
| clone = _shallow_clone_module(_block) | |
| unwrap_tensor_subclass_parameters(clone) | |
| _bound["weights"] = clone.state_dict() | |
| return model(_bound["weights"], first, hidden_states, pad_temb(temb), *rest) | |
| block.forward = forward | |
| print(f"[h3-aoti] {len(getattr(transformer, BLOCK_CONTAINER))} blocks patched (temb padded to {TEMB_ROWS})", flush=True) | |
| def maybe_load(transformer) -> None: | |
| """Patch the block stack with its compiled package, or leave it eager. Safe to call at **startup**. | |
| Off unless `H3_AOTI=1`, and anything that does not line up — another card, another torch, no `spaces` AoTI | |
| helpers, no published package — falls back to eager with one line rather than raising or segfaulting. Nothing here | |
| touches a GPU: the download is CPU work and the `.pt2` is not opened until the first forward. | |
| """ | |
| if not AOTI or id(transformer) in _LOADED: | |
| return | |
| key = artifact_key() | |
| if key is None: | |
| print("[h3-aoti] no CUDA device visible; running eager", flush=True) | |
| return | |
| if key != AOTI_KEY: | |
| print(f"[h3-aoti] this card wants `{key}`, only `{AOTI_KEY}` is published; running eager", flush=True) | |
| return | |
| try: | |
| from huggingface_hub import snapshot_download | |
| from spaces.zero.torch.aoti import LazyAOTIModel # noqa: F401 | |
| except Exception as error: | |
| print(f"[h3-aoti] no AoTI loader here ({type(error).__name__}: {error}); running eager", flush=True) | |
| return | |
| print(f"[h3-aoti] loading {AOTI_REPO}:{key} ...", flush=True) | |
| try: | |
| local = snapshot_download(repo_id=AOTI_REPO, repo_type=AOTI_REPO_TYPE, allow_patterns=f"{key}/package/*") | |
| except Exception as error: | |
| print(f"[h3-aoti] {AOTI_REPO}:{key} unreachable ({type(error).__name__}: {error}); running eager", flush=True) | |
| return | |
| package_dir = Path(local) / key / "package" | |
| if not package_dir.is_dir(): | |
| print(f"[h3-aoti] no package at `{AOTI_REPO}:{key}/package`; running eager", flush=True) | |
| return | |
| patch_blocks(transformer, package_dir) | |
| _LOADED.add(id(transformer)) | |
| print(f"[h3-aoti] compiled blocks in place (temb padded to {TEMB_ROWS} rows)", flush=True) | |
| def export_block(pipe, height: int, width: int, num_frames: int, prompt: str): | |
| """Capture one block call out of a real request and export it with a dynamic sequence dimension. | |
| Runs on the GPU, after the transformer has been quantized and moved there: a package compiled for one | |
| quantization mode is meaningless for another. | |
| """ | |
| import torch | |
| import spaces | |
| import h3_core as h3 | |
| transformer = h3.transformer_of(pipe) | |
| blocks = getattr(transformer, BLOCK_CONTAINER) | |
| # Keep the widest `temb` over a short real run rather than `spaces.aoti_capture`'s first call, which is the | |
| # 1-row one — see `TEMB_ROWS`. | |
| original_forward = blocks[0].forward | |
| widest = {"args": (), "kwargs": {}, "rows": -1} | |
| seen = [] | |
| def recording(*args, **kwargs): | |
| rows = int(args[1].shape[0]) if len(args) > 1 and hasattr(args[1], "shape") else -1 | |
| seen.append(rows) | |
| if rows > widest["rows"]: | |
| widest.update(args=args, kwargs=kwargs, rows=rows) | |
| return original_forward(*args, **kwargs) | |
| blocks[0].forward = recording | |
| try: | |
| pipe( | |
| prompt=prompt, | |
| height=height, | |
| width=width, | |
| num_frames=num_frames, | |
| num_inference_steps=int(os.environ.get("H3_AOTI_CAPTURE_STEPS", "4")), | |
| generator=torch.Generator("cpu").manual_seed(42), | |
| ) | |
| finally: | |
| blocks[0].forward = original_forward | |
| call = type("Captured", (), widest) | |
| if not call.args: | |
| raise RuntimeError("Nothing was captured — the transformer block was never called.") | |
| print(f"[h3-aoti] temb rows seen: {sorted(set(seen))}; exporting with {TEMB_ROWS} (padded)", flush=True) | |
| # `block(hidden_states, temb, adaln_indices, rotary_emb, attention_mask)`, `attention_mask` being `None` for the | |
| # padless sequences these pipelines build. Only the sequence is dynamic: `torch.export` specializes size-1 | |
| # dimensions unconditionally, so a `Dim` on `temb`'s rows cannot be expressed at all. | |
| if AOTI_SHAPE == "dynamic": | |
| sequence = torch.export.Dim("sequence", min=2048, max=262144) | |
| dynamic_shapes = ({1: sequence}, None, {0: sequence}, ({0: sequence}, {0: sequence}), None) | |
| dynamic_shapes = dynamic_shapes[: len(call.args)] | |
| else: | |
| dynamic_shapes = None | |
| args = (call.args[0], pad_temb(call.args[1]), *call.args[2:]) | |
| # Export the **live** block, non-strict. A shallow clone under non-strict tracing lifts every weight twice — once | |
| # named, once as an anonymous `CONSTANT_TENSOR` aliasing it — and the loader binds by name, so the compiled block | |
| # dereferences constants nobody set. The clone is only for flattening tensor-subclass parameters, which inductor's | |
| # constant handling cannot wrap back into a `Parameter`, and it needs `strict=True`. | |
| from spaces.zero.torch.aoti import _shallow_clone_module | |
| from torch._functorch._aot_autograd.subclass_parametrization import unwrap_tensor_subclass_parameters | |
| subclassed = sorted({type(p).__name__ for p in blocks[0].parameters()} - {"Parameter"}) | |
| if subclassed: | |
| block = _shallow_clone_module(blocks[0]) | |
| unwrap_tensor_subclass_parameters(block) | |
| strict = True | |
| print(f"[h3-aoti] tensor-subclass parameters {subclassed}: exporting a flattened clone, strict=True", flush=True) | |
| else: | |
| block = blocks[0] | |
| strict = False | |
| print("[h3-aoti] plain parameters: exporting the live block, non-strict", flush=True) | |
| # `torch.export` only gives a lifted tensor a real FQN when it is a registered parameter or buffer; a plain | |
| # attribute becomes an anonymous constant the loader can never match. Only ever on the clone, since this | |
| # re-registers attributes and the live block is what the eager path runs. | |
| if block is not blocks[0]: | |
| try: | |
| from spaces_constant_binding_patch import register_loose_tensors | |
| if loose := register_loose_tensors(block): | |
| print(f"[h3-aoti] re-registered {len(loose)} loose tensors as buffers: {loose[:6]}", flush=True) | |
| except ImportError: | |
| pass | |
| print(f"[h3-aoti] exporting {type(blocks[0]).__name__}, shapes={AOTI_SHAPE}, strict={strict} ...", flush=True) | |
| try: | |
| exported = torch.export.export(block, args, call.kwargs or None, dynamic_shapes=dynamic_shapes, strict=strict) | |
| except Exception as error: | |
| if not strict: | |
| raise | |
| print(f"[h3-aoti] strict export failed ({type(error).__name__}: {error}); retrying non-strict", flush=True) | |
| exported = torch.export.export(block, args, call.kwargs or None, dynamic_shapes=dynamic_shapes) | |
| anonymous = [ | |
| spec.target for spec in exported.graph_signature.input_specs if spec.kind.name == "CONSTANT_TENSOR" | |
| ] | |
| if anonymous: | |
| print( | |
| f"[h3-aoti] WARNING {len(anonymous)} constants lifted anonymously: {anonymous[:6]}. The loader binds by " | |
| f"name, so `compile_and_save` writes the alias sidecar and `patch_blocks` raises rather than segfaulting.", | |
| flush=True, | |
| ) | |
| return exported | |
| def compile_and_save(exported_program, destination: str | os.PathLike[str]) -> Path: | |
| """Inductor-compile the exported block into `<destination>/package/submodules/transformer_blocks/package.pt2`. | |
| That layout is what `aoti_load_from_package_dir` walks, resolving the submodule name to the transformer's | |
| `transformer_blocks` `ModuleList` and patching every block in it with this one package. | |
| """ | |
| import spaces | |
| package_dir = Path(destination) / "package" | |
| print("[h3-aoti] inductor compile (minutes) ...", flush=True) | |
| spaces.aoti_compile_and_save(package_dir, exported_program, submodule=BLOCK_CONTAINER) | |
| # The compiled artifact drops a constant's FQN when the export lifted it anonymously; the `ExportedProgram` still | |
| # has the real names, so record the mapping for the loader while it is available. | |
| try: | |
| from spaces_constant_binding_patch import write_constant_aliases | |
| if sidecar := write_constant_aliases(package_dir, exported_program, submodule=BLOCK_CONTAINER): | |
| print(f"[h3-aoti] constant alias sidecar written: {sidecar.name}", flush=True) | |
| except ImportError: | |
| pass | |
| files = sorted(str(path.relative_to(package_dir)) for path in package_dir.rglob("*") if path.is_file()) | |
| print(f"[h3-aoti] package written: {files}", flush=True) | |
| return package_dir | |
| def upload(package_dir: str | os.PathLike[str], key: str) -> str: | |
| """Push the package under its `<width>/torch<X.Y>/sm<cc>/<shape>` key. CPU work — never inside GPU time.""" | |
| from huggingface_hub import HfApi | |
| token = os.environ.get("HF_TOKEN") | |
| if not token: | |
| raise RuntimeError("`HF_TOKEN` is needed to push the AoTI package.") | |
| api = HfApi(token=token) | |
| api.create_repo(repo_id=AOTI_REPO, repo_type=AOTI_REPO_TYPE, private=False, exist_ok=True) | |
| api.upload_folder( | |
| folder_path=str(package_dir), | |
| path_in_repo=f"{key}/package", | |
| repo_id=AOTI_REPO, | |
| repo_type=AOTI_REPO_TYPE, | |
| commit_message=f"AoTI package for {key}", | |
| ) | |
| return f"https://huggingface.co/{'datasets/' if AOTI_REPO_TYPE == 'dataset' else ''}{AOTI_REPO}/tree/main/{key}" | |