94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
311 lines
11 KiB
Python
311 lines
11 KiB
Python
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
|
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
"""Utilities for selecting and loading models."""
|
|
|
|
import contextlib
|
|
import glob
|
|
import os
|
|
import re
|
|
from collections import defaultdict
|
|
from collections.abc import Callable, Iterator
|
|
from typing import Any, Dict, Type
|
|
|
|
import torch
|
|
from torch import nn
|
|
|
|
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
_QUANTIZED_DTYPES = {
|
|
torch.uint8,
|
|
torch.float8_e4m3fn,
|
|
torch.float8_e5m2,
|
|
torch.int8,
|
|
}
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def set_default_torch_dtype(dtype: torch.dtype):
|
|
"""Sets the default torch dtype to the given dtype."""
|
|
old_dtype = torch.get_default_dtype()
|
|
torch.set_default_dtype(dtype)
|
|
try:
|
|
yield
|
|
finally:
|
|
torch.set_default_dtype(old_dtype)
|
|
|
|
|
|
def get_param_names_mapping(
|
|
mapping_dict: dict[str, str | tuple[str, int, int]],
|
|
) -> Callable[[str], tuple[str, Any, Any]]:
|
|
"""
|
|
Creates a mapping function that transforms parameter names using regex patterns.
|
|
|
|
Args:
|
|
mapping_dict (Dict[str, str]): Dictionary mapping regex patterns to replacement patterns
|
|
|
|
Returns:
|
|
Callable[[str], str]: A function that maps parameter names from source to target format
|
|
"""
|
|
|
|
def mapping_fn(name: str) -> tuple[str, Any, Any]:
|
|
# support chained conversions, e.g.:
|
|
# transformer.xxx.lora_down -> xxx.lora_down -> xxx.proj_down
|
|
merge_index = None
|
|
total_split_params = None
|
|
max_steps = max(8, len(mapping_dict) * 2)
|
|
applied_patterns: set[str] = set()
|
|
visited_names: set[str] = {name}
|
|
|
|
for _ in range(max_steps):
|
|
transformed = False
|
|
for pattern, replacement in mapping_dict.items():
|
|
# avoid re-applying the same rule on its own output
|
|
if pattern in applied_patterns:
|
|
continue
|
|
if re.match(pattern, name) is None:
|
|
continue
|
|
|
|
curr_merge_index = None
|
|
curr_total_split_params = None
|
|
if isinstance(replacement, tuple):
|
|
curr_merge_index = replacement[1]
|
|
curr_total_split_params = replacement[2]
|
|
replacement = replacement[0]
|
|
|
|
new_name = re.sub(pattern, replacement, name)
|
|
|
|
if new_name != name:
|
|
if curr_merge_index is not None:
|
|
merge_index = curr_merge_index
|
|
total_split_params = curr_total_split_params
|
|
|
|
name = new_name
|
|
applied_patterns.add(pattern)
|
|
if name in visited_names:
|
|
transformed = False
|
|
break
|
|
visited_names.add(name)
|
|
transformed = True
|
|
break
|
|
|
|
if not transformed:
|
|
break
|
|
|
|
return name, merge_index, total_split_params
|
|
|
|
return mapping_fn
|
|
|
|
|
|
def hf_to_custom_state_dict(
|
|
hf_param_sd: dict[str, torch.Tensor] | Iterator[tuple[str, torch.Tensor]],
|
|
param_names_mapping: Callable[[str], tuple[str, Any, Any]],
|
|
valid_target_names: set[str] | None = None,
|
|
) -> tuple[dict[str, torch.Tensor], dict[str, tuple[str, Any, Any]]]:
|
|
"""
|
|
Converts a Hugging Face parameter state dictionary to a custom parameter state dictionary.
|
|
|
|
Args:
|
|
hf_param_sd (Dict[str, torch.Tensor]): The Hugging Face parameter state dictionary
|
|
param_names_mapping (Callable[[str], tuple[str, Any, Any]]): A function that maps parameter names from source to target format
|
|
|
|
Returns:
|
|
custom_param_sd (Dict[str, torch.Tensor]): The custom formatted parameter state dict
|
|
reverse_param_names_mapping (Dict[str, Tuple[str, Any, Any]]): Maps back from custom to hf
|
|
"""
|
|
custom_param_sd = {}
|
|
to_merge_params = defaultdict(dict) # type: ignore
|
|
reverse_param_names_mapping = {}
|
|
if isinstance(hf_param_sd, dict):
|
|
hf_param_sd = hf_param_sd.items() # type: ignore
|
|
for source_param_name, full_tensor in hf_param_sd: # type: ignore
|
|
target_param_name, merge_index, num_params_to_merge = param_names_mapping(
|
|
source_param_name
|
|
)
|
|
if (
|
|
valid_target_names is not None
|
|
and target_param_name != source_param_name
|
|
and source_param_name in valid_target_names
|
|
and target_param_name not in valid_target_names
|
|
):
|
|
target_param_name = source_param_name
|
|
merge_index = None
|
|
num_params_to_merge = None
|
|
if target_param_name == "" or target_param_name is None: # type: ignore[comparison-overlap]
|
|
continue
|
|
reverse_param_names_mapping[target_param_name] = (
|
|
source_param_name,
|
|
merge_index,
|
|
num_params_to_merge,
|
|
)
|
|
if merge_index is not None:
|
|
to_merge_params[target_param_name][merge_index] = full_tensor
|
|
if len(to_merge_params[target_param_name]) == num_params_to_merge:
|
|
# cat at output dim according to the merge_index order
|
|
sorted_tensors = [
|
|
to_merge_params[target_param_name][i]
|
|
for i in range(num_params_to_merge)
|
|
]
|
|
full_tensor = torch.cat(sorted_tensors, dim=0)
|
|
del to_merge_params[target_param_name]
|
|
else:
|
|
continue
|
|
existing_tensor = custom_param_sd.get(target_param_name)
|
|
if existing_tensor is not None and existing_tensor.dtype != full_tensor.dtype:
|
|
existing_is_quantized = existing_tensor.dtype in _QUANTIZED_DTYPES
|
|
current_is_quantized = full_tensor.dtype in _QUANTIZED_DTYPES
|
|
if existing_is_quantized and not current_is_quantized:
|
|
logger.debug(
|
|
"Keeping quantized duplicate for %s: existing=%s new=%s",
|
|
target_param_name,
|
|
existing_tensor.dtype,
|
|
full_tensor.dtype,
|
|
)
|
|
continue
|
|
if current_is_quantized and not existing_is_quantized:
|
|
logger.debug(
|
|
"Replacing non-quantized duplicate for %s: existing=%s new=%s",
|
|
target_param_name,
|
|
existing_tensor.dtype,
|
|
full_tensor.dtype,
|
|
)
|
|
custom_param_sd[target_param_name] = full_tensor
|
|
return custom_param_sd, reverse_param_names_mapping
|
|
|
|
|
|
class skip_init_modules:
|
|
def __enter__(self):
|
|
# Save originals
|
|
self._orig_reset = {}
|
|
for cls in (nn.Linear, nn.Conv1d, nn.Conv2d, nn.Conv3d, nn.Embedding):
|
|
self._orig_reset[cls] = cls.reset_parameters
|
|
cls.reset_parameters = lambda self: None # skip init
|
|
from transformers.modeling_utils import PreTrainedModel
|
|
|
|
self._pretrained_model_cls = PreTrainedModel
|
|
self._orig_post_init = PreTrainedModel.post_init
|
|
PreTrainedModel.post_init = lambda self: None
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
# restore originals
|
|
for cls, orig in self._orig_reset.items():
|
|
cls.reset_parameters = orig
|
|
self._pretrained_model_cls.post_init = self._orig_post_init
|
|
|
|
|
|
def _normalize_component_type(module_type: str) -> str:
|
|
"""Normalize module types like 'text_encoder_2' -> 'text_encoder'."""
|
|
return re.sub(r"_\d+$", "", module_type)
|
|
|
|
|
|
def _clean_hf_config_inplace(model_config: dict) -> None:
|
|
"""Remove common extraneous HF fields if present."""
|
|
for key in (
|
|
"_name_or_path",
|
|
"transformers_version",
|
|
"model_type",
|
|
"tokenizer_class",
|
|
"torch_dtype",
|
|
):
|
|
model_config.pop(key, None)
|
|
|
|
|
|
def _try_redownload_missing_shards(model_path: str, missing: list[str]) -> bool:
|
|
"""Try to re-download missing safetensors shards from HuggingFace Hub.
|
|
|
|
Parses the repo_id and revision from the HF cache path structure
|
|
(models--{org}--{repo}/snapshots/{revision}) and calls hf_hub_download
|
|
for each missing shard. Returns True if all shards were recovered.
|
|
"""
|
|
try:
|
|
from huggingface_hub import hf_hub_download
|
|
|
|
match = re.search(
|
|
r"models--([^/\\]+)--([^/\\]+)[/\\]snapshots[/\\]([^/\\]+)", model_path
|
|
)
|
|
if not match:
|
|
return False
|
|
|
|
repo_id = f"{match.group(1)}/{match.group(2)}"
|
|
revision = match.group(3)
|
|
logger.warning(
|
|
"Incomplete checkpoint for %s (revision %.8s) — missing shards: %s. "
|
|
"Attempting auto-repair via HuggingFace Hub...",
|
|
repo_id,
|
|
revision,
|
|
missing,
|
|
)
|
|
for shard in missing:
|
|
hf_hub_download(repo_id=repo_id, filename=shard, revision=revision)
|
|
logger.info("Auto-repair succeeded for %s.", repo_id)
|
|
return True
|
|
except Exception as e:
|
|
logger.warning("Auto-repair failed: %s", e)
|
|
return False
|
|
|
|
|
|
def _list_safetensors_files(model_path: str) -> list[str]:
|
|
"""List all .safetensors files under a directory.
|
|
|
|
If a safetensors index file is present, verifies that every shard listed
|
|
in the index actually exists on disk. Missing shards are first repaired
|
|
automatically via HuggingFace Hub (if the path is an HF cache entry);
|
|
if repair fails a clear RuntimeError is raised.
|
|
"""
|
|
found = sorted(glob.glob(os.path.join(str(model_path), "*.safetensors")))
|
|
|
|
index_path = os.path.join(
|
|
str(model_path), "diffusion_pytorch_model.safetensors.index.json"
|
|
)
|
|
if os.path.exists(index_path):
|
|
import json
|
|
|
|
with open(index_path) as f:
|
|
index = json.load(f)
|
|
expected_shards = sorted(set(index.get("weight_map", {}).values()))
|
|
found_basenames = {os.path.basename(p) for p in found}
|
|
missing = [s for s in expected_shards if s not in found_basenames]
|
|
if missing:
|
|
repaired = _try_redownload_missing_shards(model_path, missing)
|
|
if repaired:
|
|
found = sorted(
|
|
glob.glob(os.path.join(str(model_path), "*.safetensors"))
|
|
)
|
|
else:
|
|
raise RuntimeError(
|
|
f"Checkpoint at '{model_path}' is incomplete — the following "
|
|
f"shard(s) listed in the index are missing from disk: "
|
|
f"{missing}. Re-download the checkpoint (e.g. "
|
|
f"`huggingface-cli download {os.path.basename(model_path)}`)."
|
|
)
|
|
|
|
return found
|
|
|
|
|
|
BYTES_PER_GB = 1024**3
|
|
|
|
|
|
def get_memory_usage_of_component(module) -> float | None:
|
|
"""
|
|
returned value is in GB, rounded to 2 decimal digits
|
|
"""
|
|
if not isinstance(module, nn.Module):
|
|
return None
|
|
if hasattr(module, "get_memory_footprint"):
|
|
usage = module.get_memory_footprint() / BYTES_PER_GB
|
|
else:
|
|
# manually
|
|
param_size = sum(p.numel() * p.element_size() for p in module.parameters())
|
|
buffer_size = sum(b.numel() * b.element_size() for b in module.buffers())
|
|
|
|
total_size_bytes = param_size + buffer_size
|
|
usage = total_size_bytes / (1024**3)
|
|
|
|
return round(usage, 2)
|
|
|
|
|
|
# component name -> ComponentLoader class
|
|
component_name_to_loader_cls: Dict[str, Type[Any]] = {}
|