Download _overlay/materialize.py from kevin-mi/FastH3-4step-Preview-overlay: direct link, hf CLI and curl.
- Browser
- Download file 8.08 kB
-
https://huggingface.co/kevin-mi/FastH3-4step-Preview-overlay/resolve/main/_overlay/materialize.py
- Command line
-
hf download hf://kevin-mi/FastH3-4step-Preview-overlay/_overlay/materialize.py
-
curl -L -o materialize.py https://huggingface.co/kevin-mi/FastH3-4step-Preview-overlay/resolve/main/_overlay/materialize.py
8.08 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| """Materialize FastH3's native-Diffusers export into the base-H3 layout. | |
| Two derived artifacts make the materialized checkpoint byte-compatible with | |
| what SGLang's MiniMax-H3 loaders already consume, so the runtime needs no | |
| FastH3-specific weight handling: | |
| 1. ``transformer/sglang_rope_inv_freq.safetensors`` - the Diffusers export | |
| drops the derivable ``rope.inv_freq`` buffer; the fp32 expression below | |
| reproduces the base checkpoint tensor bit-for-bit (verified against | |
| MiniMaxAI/MiniMax-H3). | |
| 2. ``video_vae/source/model.safetensors`` - the video VAE re-serialized in | |
| the fused source form the native decoder loads strictly. The tensor | |
| values are a bit-identical re-export of the base H3 VAE; only names and | |
| the fused-QKV row order differ (verified tensor-by-tensor). | |
| """ | |
| import json | |
| import os | |
| from collections import defaultdict | |
| import torch | |
| from safetensors import safe_open | |
| from safetensors.torch import save_file | |
| _ROPE_FILE = "sglang_rope_inv_freq.safetensors" | |
| def _write_rope_inv_freq(*, source_dir: str, output_dir: str) -> None: | |
| with open(os.path.join(source_dir, "transformer", "config.json")) as f: | |
| rope_freq_dim = int(json.load(f)["rope_freq_dim"]) | |
| exponents = torch.arange(0, 2 * rope_freq_dim, 2, dtype=torch.float32) / ( | |
| 2 * rope_freq_dim | |
| ) | |
| inv_freq = 1.0 / (10000.0**exponents) | |
| transformer_dir = os.path.join(output_dir, "transformer") | |
| save_file({"rope.inv_freq": inv_freq}, os.path.join(transformer_dir, _ROPE_FILE)) | |
| # Keep the weight index consistent with the added shard. The symlinked | |
| # index is replaced by a patched real file. | |
| index_path = os.path.join( | |
| transformer_dir, "diffusion_pytorch_model.safetensors.index.json" | |
| ) | |
| with open(index_path) as f: | |
| index = json.load(f) | |
| index["weight_map"]["rope.inv_freq"] = _ROPE_FILE | |
| if os.path.lexists(index_path): | |
| os.remove(index_path) | |
| with open(index_path, "w") as f: | |
| json.dump(index, f, indent=2, sort_keys=True) | |
| def _interleave_qkv_rows( | |
| qkv: torch.Tensor, *, heads: int, dim_head: int | |
| ) -> torch.Tensor: | |
| """Stacked [q; k; v] rows -> the per-head (q, k, v) interleave the fused | |
| source to_qkv projection stores.""" | |
| rest_shape = qkv.shape[1:] | |
| return ( | |
| qkv.reshape(3, heads, dim_head, *rest_shape) | |
| .transpose(0, 1) | |
| .reshape(3 * heads * dim_head, *rest_shape) | |
| ) | |
| # Family-level renames from the Diffusers export back to the source form. | |
| # Fused to_qkv and the SwiGLU half order are handled separately. | |
| _DECODER_RENAMES = ( | |
| ("decoder.proj_in.", "decoder.x_embedder."), | |
| (".attn.to_out.0.", ".attn.to_out."), | |
| (".ff.net.2.", ".ff.w2."), | |
| ) | |
| def _rename_encoder(name: str) -> str: | |
| # encoder.down_blocks.N.resnets.M.* -> encoder.down.N.block.M.* | |
| # encoder.down_blocks.N.downsamplers.0.* -> encoder.down.N.downsample.* | |
| parts = name.split(".") | |
| if len(parts) >= 4 and parts[0] == "encoder" and parts[1] == "down_blocks": | |
| block = parts[2] | |
| if parts[3] == "resnets": | |
| tail = parts[5:] | |
| if tail and tail[0] == "conv_shortcut": | |
| tail[0] = "nin_shortcut" | |
| return ".".join(["encoder", "down", block, "block", parts[4], *tail]) | |
| if parts[3] == "downsamplers": | |
| return ".".join(["encoder", "down", block, "downsample", *parts[5:]]) | |
| return name | |
| def _convert_video_vae(*, source_dir: str, output_dir: str) -> None: | |
| vae_dir = os.path.join(source_dir, "vae") | |
| with open(os.path.join(vae_dir, "config.json")) as f: | |
| config = json.load(f) | |
| heads = int(config["decoder_num_attention_heads"]) | |
| dim_head = int(config["decoder_attention_head_dim"]) | |
| with open( | |
| os.path.join(vae_dir, "diffusion_pytorch_model.safetensors.index.json") | |
| ) as f: | |
| weight_map = json.load(f)["weight_map"] | |
| handles: dict[str, object] = {} | |
| def load(name: str) -> torch.Tensor: | |
| shard = weight_map[name] | |
| if shard not in handles: | |
| handles[shard] = safe_open( | |
| os.path.join(vae_dir, shard), framework="pt", device="cpu" | |
| ) | |
| return handles[shard].get_tensor(name) | |
| converted: dict[str, torch.Tensor] = {} | |
| pending: dict[str, dict[str, torch.Tensor]] = defaultdict(dict) | |
| for name in weight_map: | |
| tensor = load(name) | |
| if ".attn.to_q." in name or ".attn.to_k." in name or ".attn.to_v." in name: | |
| slot = name.split(".attn.to_")[1][0] | |
| fused_name = ( | |
| name.replace(".attn.to_q.", ".attn.to_qkv.") | |
| .replace(".attn.to_k.", ".attn.to_qkv.") | |
| .replace(".attn.to_v.", ".attn.to_qkv.") | |
| ) | |
| pending[fused_name][slot] = tensor | |
| if len(pending[fused_name]) == 3: | |
| stacked = torch.cat( | |
| [pending[fused_name][s] for s in ("q", "k", "v")], dim=0 | |
| ) | |
| converted[fused_name] = _interleave_qkv_rows( | |
| stacked, heads=heads, dim_head=dim_head | |
| ) | |
| del pending[fused_name] | |
| continue | |
| if ".ff.net.0.proj." in name: | |
| # Diffusers SwiGLU stores [value, gate]; the source gated FF | |
| # consumes [gate, value]. | |
| value, gate = tensor.chunk(2, dim=0) | |
| tensor = torch.cat((gate, value), dim=0) | |
| name = name.replace(".ff.net.0.proj.", ".ff.w1.") | |
| else: | |
| for old, new in _DECODER_RENAMES: | |
| name = name.replace(old, new) | |
| name = _rename_encoder(name) | |
| converted[name] = tensor | |
| if pending: | |
| raise ValueError( | |
| "Incomplete fused QKV groups in the Diffusers VAE export: " | |
| + ", ".join(sorted(pending)) | |
| ) | |
| # The export drops decoder.mask_token; the base checkpoint stores it as | |
| # all zeros and inference never reads it. | |
| converted["decoder.mask_token"] = torch.zeros( | |
| (1, 1, heads * dim_head), dtype=torch.float32 | |
| ) | |
| target_dir = os.path.join(output_dir, "video_vae", "source") | |
| os.makedirs(target_dir, exist_ok=True) | |
| source_file = os.path.join(target_dir, "model.safetensors") | |
| save_file( | |
| {name: tensor.contiguous() for name, tensor in converted.items()}, | |
| source_file, | |
| ) | |
| # The snapshot-completeness check globs weights at the component root; | |
| # H3's loader still selects source/model.safetensors. Same inode. | |
| root_file = os.path.join(output_dir, "video_vae", "model.safetensors") | |
| if os.path.lexists(root_file): | |
| os.remove(root_file) | |
| os.link(source_file, root_file) | |
| def _write_patched_component_config( | |
| *, source_path: str, target_path: str, class_name: str | |
| ) -> None: | |
| """Copy a component config with the SGLang-native ``_class_name``. | |
| The Diffusers export names its own classes; SGLang's component loader | |
| resolves ``_class_name`` through its model registry, which registers the | |
| native wrappers (matching the base H3 release configs). | |
| """ | |
| with open(source_path) as f: | |
| config = json.load(f) | |
| config["_class_name"] = class_name | |
| if os.path.lexists(target_path): | |
| os.remove(target_path) | |
| os.makedirs(os.path.dirname(target_path), exist_ok=True) | |
| with open(target_path, "w") as f: | |
| json.dump(config, f, indent=2, sort_keys=True) | |
| def materialize( | |
| *, overlay_dir: str, source_dir: str, output_dir: str, manifest: dict | |
| ) -> None: | |
| _write_rope_inv_freq(source_dir=source_dir, output_dir=output_dir) | |
| _convert_video_vae(source_dir=source_dir, output_dir=output_dir) | |
| _write_patched_component_config( | |
| source_path=os.path.join(source_dir, "vae", "config.json"), | |
| target_path=os.path.join(output_dir, "video_vae", "config.json"), | |
| class_name="MiniMaxH3VideoVAE", | |
| ) | |
| _write_patched_component_config( | |
| source_path=os.path.join(source_dir, "audio_vae", "config.json"), | |
| target_path=os.path.join(output_dir, "audio_vae", "config.json"), | |
| class_name="MiniMaxH3AudioVAE", | |
| ) | |