"""ZeroGPU AOTI for SenseNova-U1.5-8B-MoT. One compiled `Qwen3DecoderLayer` gen-path package, reused by all 42 layers. The per-denoising-step hot path is `language_model.model` (94.9% of wall time on a 2048x2048 T2I), and inside it all the time is in the 42 identical `Qwen3DecoderLayer`s. Their image-generation path reads the preallocated flash KV buffers off the cache object as *attributes*, which `torch.export` cannot trace, so `sensenova_u1.models.neo_unify.modeling_qwen3` adds a pure-tensor restatement (`gen_block_core` / `Qwen3GenBlock` / `Qwen3DecoderLayer.gen_core`) that takes the prefix K/V as explicit tensor arguments. Only `gen_core` is compiled; the und/prefill paths stay eager. A Space only calls `maybe_load(model)`; everything else is the build path. """ from __future__ import annotations import json import os from pathlib import Path AOTI = os.environ.get("SN_AOTI", "0") == "1" AOTI_REPO = os.environ.get("SN_AOTI_REPO", "zerogpu-hacking/sensenova-u1-5-aoti") AOTI_REPO_TYPE = os.environ.get("SN_AOTI_REPO_TYPE", "model") # A package is valid for exactly one `/torch/sm/`, and a # mismatched one segfaults rather than raising, so `maybe_load` refuses anything # that is not an exact match. AOTI_KEY = os.environ.get("SN_AOTI_KEY", "bf16/torch2.8/sm120/dynamic") AOTI_SHAPE = os.environ.get("SN_AOTI_SHAPE", "dynamic") AOTI_DURATION = int(os.environ.get("SN_AOTI_DURATION", "1500")) SUBMODULE = "layers" METADATA_FILENAME = "metadata.json" ALIASES_FILENAME = "constant_aliases.json" # Image-token sequence length. The T2I aspect buckets run 1152x3456 .. 2880x1440 # at patch 16 x merge 2, i.e. 3888..4096 tokens; editing prefixes are longer, so # leave generous headroom rather than recompiling per bucket. S_MIN, S_MAX = int(os.environ.get("SN_AOTI_S_MIN", "256")), int(os.environ.get("SN_AOTI_S_MAX", "16384")) # Text (or text+reference-image) prefix already in the KV cache. P_MIN, P_MAX = int(os.environ.get("SN_AOTI_P_MIN", "8")), int(os.environ.get("SN_AOTI_P_MAX", "32768")) _LOADED: set[int] = set() def trunk_of(model): """The `Qwen3Model` whose `.layers` hold the 42 decoder layers.""" return model.language_model.model def layers_of(model): return trunk_of(model).layers def artifact_key() -> str | None: """`/torch/sm/` for this process, or None without CUDA.""" try: import torch torch_version = ".".join(torch.__version__.split(".")[:2]) major, minor = torch.cuda.get_device_capability() except Exception: return None return f"bf16/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** (`SN_AOTI=1` to load compiled decoder layers)" ) # --------------------------------------------------------------------------- weights def layer_weights(layer) -> dict: """Constant map for one layer. `state_dict()` is NOT enough: the two rotary `inv_freq` buffers are registered non-persistent, so they are absent from `state_dict()` but are real graph constants once `package_constants_in_so=False`. Binding them partially is a SIGSEGV, not an error, so include every parameter and buffer. """ import torch from torch._functorch._aot_autograd.subclass_parametrization import ( unwrap_tensor_subclass_parameters, ) module = layer if any(type(p) is not torch.nn.Parameter for p in layer.parameters()): from spaces.zero.torch.aoti import _shallow_clone_module module = _shallow_clone_module(layer) unwrap_tensor_subclass_parameters(module) return {**dict(module.named_parameters()), **dict(module.named_buffers())} def anonymous_constants(exported_program) -> list[str]: return [ spec.target for spec in exported_program.graph_signature.input_specs if spec.kind.name == "CONSTANT_TENSOR" and str(spec.target).startswith("_tensor_constant") ] def constant_aliases(exported_program) -> dict: """`{'_tensor_constant': ''}` for anonymously lifted constants. Empty for this model as long as everything stays a registered parameter or buffer; written anyway so the loader can bind if that ever changes. """ targets = [ spec.target for spec in exported_program.graph_signature.input_specs if spec.kind.name == "CONSTANT_TENSOR" ] return {f"_tensor_constant{i}": t for i, t in enumerate(targets)} # --------------------------------------------------------------------------- load side def patch_layers(model, package_dir, verbose: bool = True) -> int: """Point every layer's `gen_core` at the one compiled package. Weights are read on the first call, not here: this can run at startup, and ZeroGPU rebinds `param.data` to fresh CUDA tensors when the GPU worker materializes the model, which would leave patch-time tensors stale. """ from spaces.zero.torch.aoti import LazyAOTIModel package_dir = Path(package_dir) pt2 = package_dir / "submodules" / SUBMODULE / "package.pt2" if not pt2.is_file(): raise FileNotFoundError(pt2) aliases_path = package_dir / "submodules" / SUBMODULE / ALIASES_FILENAME aliases = json.loads(aliases_path.read_text()) if aliases_path.is_file() else {} shared = LazyAOTIModel(pt2) layers = layers_of(model) for layer in layers: layer.gen_core = _make_gen_core(shared, pt2, aliases, layer) if verbose: print(f"[sn-aoti] {len(layers)} decoder layers patched (gen_core)", flush=True) return len(layers) _WARNED: set[str] = set() def _warn_once(message: str) -> None: if message not in _WARNED: _WARNED.add(message) print(f"[sn-aoti] {message}", flush=True) def _eager_gen_core(layer, hidden_states, indexes, prefix_k, prefix_v): # sn_aoti is a top-level module, not part of the sensenova_u1 package, # so this has to be an absolute import. from sensenova_u1.models.neo_unify.modeling_qwen3 import gen_block_core return gen_block_core(layer, hidden_states, indexes, prefix_k, prefix_v) def _in_range(hidden_states, prefix_k) -> bool: """Is this call inside the shapes the package was exported for? Batch is *specialized* to 1 (only dims 1 were made dynamic), and S/P have explicit bounds. Everything the demo does sits well inside this, but an out-of-range call must degrade to eager rather than rely on the compiled guards -- so check rather than find out. """ batch, seq = hidden_states.shape[0], hidden_states.shape[1] prefix = prefix_k.shape[1] return batch == 1 and S_MIN <= seq <= S_MAX and P_MIN <= prefix <= P_MAX def _make_gen_core(shared, pt2, aliases, layer): """One layer's replacement `gen_core`, binding its weights on first call.""" bound: dict = {} state = {"disabled": False} def gen_core(hidden_states, indexes, prefix_k, prefix_v): if state["disabled"]: return _eager_gen_core(layer, hidden_states, indexes, prefix_k, prefix_v) if not _in_range(hidden_states, prefix_k): _warn_once( f"shape outside the compiled range (B={hidden_states.shape[0]}, " f"S={hidden_states.shape[1]}, P={prefix_k.shape[1]}); running eager" ) return _eager_gen_core(layer, hidden_states, indexes, prefix_k, prefix_v) # Any failure here -- unbindable constants, a guard the compiled model # rejects, anything -- disables the compiled path for this layer and # runs eager instead. The invariant that matters is that a partially # bound package is never *executed*; declining to use it satisfies that # just as well as raising, and keeps the demo serving. try: first = not bound if first: bound["weights"] = _resolve_constants(shared, pt2, aliases, layer) return shared(bound["weights"], first, hidden_states, indexes, prefix_k, prefix_v) except Exception as error: _warn_once(f"compiled path unusable ({type(error).__name__}: {error}); " f"this layer runs eager from now on") state["disabled"] = True return _eager_gen_core(layer, hidden_states, indexes, prefix_k, prefix_v) return gen_core def _resolve_constants(shared, pt2, aliases, layer) -> dict: """Bind every compiled constant to one of the layer's own tensors, or refuse. Name first, then the compile-side alias sidecar. Partial binding is not an option: an unset constant is a SIGSEGV, not an exception. """ weights = layer_weights(layer) fqns = _constant_fqns(shared, pt2) resolved = {name: weights[name] for name in fqns if name in weights} for name in [n for n in fqns if n not in resolved]: target = aliases.get(name) if target in weights: resolved[name] = weights[target] missing = [n for n in fqns if n not in resolved] if missing: raise RuntimeError( f"{len(missing)} of {len(fqns)} AOTI constants unbindable from the layer's " f"parameters/buffers: {missing[:8]}. Refusing to run: a partially bound " f"package dereferences constants nobody set (SIGSEGV rather than an error)." ) return resolved def _constant_fqns(shared, pt2): """`get_constant_fqns()` needs the package loaded; do it once, off the GPU path.""" import torch compiled = shared.compiled_model.get() if compiled is None: from spaces.zero.torch.aoti import _register_aoti_cleanup with _register_aoti_cleanup(): compiled = torch._inductor.aoti_load_package(pt2) shared.compiled_model.set(compiled) return compiled.get_constant_fqns() def maybe_load(model, verbose: bool = True) -> bool: """Patch the decoder stack with its compiled package, or leave it eager. Safe to call at startup and safe to call when nothing is published: every mismatch falls back to eager with one printed line rather than raising. """ if not AOTI or id(model) in _LOADED: return False key = artifact_key() if key is None: print("[sn-aoti] no CUDA device visible; running eager", flush=True) return False if key != AOTI_KEY: print(f"[sn-aoti] this card wants `{key}`, only `{AOTI_KEY}` is published; running eager", flush=True) return False try: from huggingface_hub import snapshot_download from spaces.zero.torch.aoti import LazyAOTIModel # noqa: F401 except Exception as error: print(f"[sn-aoti] no AOTI loader here ({type(error).__name__}: {error}); running eager", flush=True) return False try: local = snapshot_download( repo_id=AOTI_REPO, repo_type=AOTI_REPO_TYPE, allow_patterns=f"{key}/package/*" ) except Exception as error: print(f"[sn-aoti] {AOTI_REPO}:{key} unreachable ({type(error).__name__}: {error}); running eager", flush=True) return False package_dir = Path(local) / key / "package" if not package_dir.is_dir(): print(f"[sn-aoti] no package at `{AOTI_REPO}:{key}/package`; running eager", flush=True) return False meta_path = package_dir / METADATA_FILENAME if meta_path.is_file(): meta = json.loads(meta_path.read_text()) mismatch = _metadata_mismatch(meta, model) if mismatch: print(f"[sn-aoti] metadata mismatch ({mismatch}); running eager", flush=True) return False try: patch_layers(model, package_dir, verbose=verbose) except Exception as error: print(f"[sn-aoti] patching failed ({type(error).__name__}: {error}); running eager", flush=True) return False _LOADED.add(id(model)) return True def _metadata_mismatch(meta: dict, model) -> str | None: """Return a human-readable reason the published package does not fit, or None.""" import torch checks = [] if "torch" in meta: want = ".".join(str(meta["torch"]).split(".")[:2]) have = ".".join(torch.__version__.split(".")[:2]) checks.append(("torch", want, have)) if "sm" in meta: major, minor = torch.cuda.get_device_capability() checks.append(("sm", str(meta["sm"]), f"sm{major}{minor}")) if "attn_backend" in meta: # The attention kernel is baked into the graph. If flash-attn later # becomes importable the eager path would switch to it while this # package stays on SDPA: different numerics, and possibly slower. checks.append(("attn_backend", str(meta["attn_backend"]), __import__("sensenova_u1").effective_attn_backend())) if "num_hidden_layers" in meta: checks.append(("num_hidden_layers", str(meta["num_hidden_layers"]), str(len(layers_of(model))))) if "hidden_size" in meta: checks.append(("hidden_size", str(meta["hidden_size"]), str(model.language_model.config.hidden_size))) for name, want, have in checks: if want != have: return f"{name}: package {want} != runtime {have}" return None # --------------------------------------------------------------------------- build side def capture_layer_call(model, tokenizer, prompt: str, image_size=(2048, 2048), steps: int = 2): """Run a couple of real denoising steps and keep one layer-0 `gen_core` call. The inputs must come from a real run: `prefix_k`/`prefix_v` are slices of the preallocated flash KV cache built by `prepare_flash_kv_cache`, and their prefix length depends on the tokenized prompt. """ import torch layers = layers_of(model) layer0 = layers[0] original = layer0.gen_core captured = {} def recording(hidden_states, indexes, prefix_k, prefix_v): if "args" not in captured: captured["args"] = (hidden_states, indexes, prefix_k, prefix_v) return original(hidden_states, indexes, prefix_k, prefix_v) layer0.gen_core = recording try: # Same knobs the production demo uses, so the captured shapes and the # baked-in graph match what will actually run. with torch.inference_mode(): model.t2i_generate( tokenizer, prompt, image_size=image_size, cfg_scale=4.0, cfg_norm="none", timestep_shift=3.0, cfg_interval=(0.0, 1.0), num_steps=steps, batch_size=1, seed=42, think_mode=False, ) finally: layer0.gen_core = original if "args" not in captured: raise RuntimeError("gen_core was never called - the fast denoise path did not engage.") args = tuple(t.detach().clone() for t in captured["args"]) print(f"[sn-aoti] captured shapes: " f"{[tuple(t.shape) for t in args]} dtypes={[str(t.dtype) for t in args]}", flush=True) return args def export_layer(model, args, shape: str = AOTI_SHAPE): """Export layer 0's gen core, with the image-token and prefix lengths dynamic.""" import torch from sensenova_u1.models.neo_unify.modeling_qwen3 import Qwen3GenBlock layer0 = layers_of(model)[0] shim = Qwen3GenBlock(layer0) # The shim shares the layer's submodules under the same names, so exported # constant FQNs are exactly the layer's own parameter/buffer names -- which # is what makes one package reusable by all 42 layers. sd_layer, sd_shim = set(layer0.state_dict()), set(shim.state_dict()) assert sd_layer == sd_shim, (sorted(sd_layer - sd_shim)[:5], sorted(sd_shim - sd_layer)[:5]) if shape == "dynamic": S = torch.export.Dim("S", min=S_MIN, max=S_MAX) P = torch.export.Dim("P", min=P_MIN, max=P_MAX) dynamic_shapes = ( {1: S}, # hidden_states [B, S, C] {1: S}, # indexes [3, S] {1: P}, # prefix_k [B, P, H_kv, D] {1: P}, # prefix_v ) else: dynamic_shapes = None print(f"[sn-aoti] exporting Qwen3GenBlock, shapes={shape} ...", flush=True) with torch.inference_mode(): ep = torch.export.export(shim, args, {}, dynamic_shapes=dynamic_shapes) anon = anonymous_constants(ep) if anon: print(f"[sn-aoti] WARNING {len(anon)} anonymously lifted constants: {anon[:6]}; " f"the alias sidecar will carry their real names", flush=True) return ep def compile_and_save(ep, destination, model, args, extra: dict | None = None) -> Path: """Inductor-compile into `/package/submodules/layers/package.pt2`.""" import torch import spaces package_dir = Path(destination) / "package" print("[sn-aoti] inductor compile (minutes) ...", flush=True) spaces.aoti_compile_and_save(package_dir, ep, submodule=SUBMODULE) subdir = package_dir / "submodules" / SUBMODULE aliases = constant_aliases(ep) if aliases: (subdir / ALIASES_FILENAME).write_text(json.dumps(aliases, indent=2)) major, minor = torch.cuda.get_device_capability() llm_cfg = model.language_model.config meta = { "model_id": "sensenova/SenseNova-U1.5-8B-MoT", "module": "language_model.model.layers[*].gen_core " "(Qwen3DecoderLayer image-generation path)", "compiled_class": "Qwen3GenBlock", "torch": torch.__version__, "cuda": torch.version.cuda, "sm": f"sm{major}{minor}", "device": torch.cuda.get_device_name(0), "dtype": "bfloat16", "attn_backend": __import__("sensenova_u1").effective_attn_backend(), "num_hidden_layers": llm_cfg.num_hidden_layers, "hidden_size": llm_cfg.hidden_size, "shape": AOTI_SHAPE, "signature": ["hidden_states[B,S,C]", "indexes[3,S]", "prefix_k[B,P,H_kv,D]", "prefix_v[B,P,H_kv,D]"], "dynamic_dims": {"S": [S_MIN, S_MAX], "P": [P_MIN, P_MAX]}, "example_shapes": [list(t.shape) for t in args], "key": artifact_key(), "anonymous_constants": anonymous_constants(ep), } if extra: meta.update(extra) (package_dir / METADATA_FILENAME).write_text(json.dumps(meta, indent=2)) files = sorted(str(p.relative_to(package_dir)) for p in package_dir.rglob("*") if p.is_file()) total = sum(p.stat().st_size for p in package_dir.rglob("*") if p.is_file()) print(f"[sn-aoti] package written ({total/1e6:.1f} MB): {files}", flush=True) return package_dir def upload(package_dir, key: str, token: str | None = None) -> str: """Push the package under its key. CPU work - never inside GPU time.""" from huggingface_hub import HfApi token = token or 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/{AOTI_REPO}/tree/main/{key}"