[CI] Add per-job uv venv isolation and upgrade CI version to Cuda 13 (#23119)
Co-authored-by: Kangyan Zhou <zky314343421@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Alison Shao <a.shao@wustl.edu> Co-authored-by: Mick <mickjagger19@icloud.com>
This commit is contained in:
co-authored by
Kangyan Zhou
Claude Opus 4.7
Alison Shao
Mick
parent
03828f4205
commit
6ecd6f84db
+17
-10
@@ -22,7 +22,7 @@ dependencies = [
|
||||
"blobfile==3.0.0",
|
||||
"build",
|
||||
"compressed-tensors",
|
||||
"cuda-python==12.9",
|
||||
"cuda-python>=13.0",
|
||||
"decord2 ; sys_platform == 'linux' and (platform_machine == 'aarch64' or platform_machine == 'arm64' or platform_machine == 'armv7l')",
|
||||
"datasets",
|
||||
"einops",
|
||||
@@ -37,7 +37,7 @@ dependencies = [
|
||||
"ninja",
|
||||
"easydict", # Required by remote model code (e.g. DeepSeek-OCR) loaded via trust_remote_code; validated by transformers 5.4+ check_imports
|
||||
"numpy",
|
||||
"nvidia-cutlass-dsl>=4.4.1",
|
||||
"nvidia-cutlass-dsl==4.4.2",
|
||||
"nvidia-ml-py",
|
||||
"openai-harmony==0.0.4",
|
||||
"openai==2.6.1",
|
||||
@@ -58,14 +58,14 @@ dependencies = [
|
||||
"scipy",
|
||||
"sentencepiece",
|
||||
"setproctitle",
|
||||
"flash-attn-4>=4.0.0b4",
|
||||
"flash-attn-4>=4.0.0b9",
|
||||
"sglang-kernel==0.4.1",
|
||||
"soundfile==0.13.1",
|
||||
"tiktoken",
|
||||
"timm==1.0.16",
|
||||
"torch_memory_saver==0.0.9",
|
||||
"torch==2.9.1",
|
||||
"torchao==0.9.0",
|
||||
"torchao==0.17.0",
|
||||
"torchaudio==2.9.1",
|
||||
"torchcodec==0.9.1 ; sys_platform != 'linux' or (sys_platform == 'linux' and platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l')", # torchcodec 0.9.1 for torch 2.9.x. Not available on Linux ARM.
|
||||
"av ; sys_platform == 'linux' and (platform_machine == 'aarch64' or platform_machine == 'arm64' or platform_machine == 'armv7l')",
|
||||
@@ -87,15 +87,21 @@ url = "https://pypi.org/simple"
|
||||
default = true
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "torch-cu129"
|
||||
url = "https://download.pytorch.org/whl/cu129"
|
||||
name = "torch-cu130"
|
||||
url = "https://download.pytorch.org/whl/cu130"
|
||||
explicit = true
|
||||
|
||||
# To be removed after pypi sglang-kernel uses cu130
|
||||
[[tool.uv.index]]
|
||||
name = "sglang-kernel-cu130"
|
||||
url = "https://docs.sglang.ai/whl/cu130/"
|
||||
explicit = true
|
||||
|
||||
[tool.uv.sources]
|
||||
torch = [
|
||||
{ index = "pypi", marker = "platform_machine == 'x86_64'"},
|
||||
{ index = "torch-cu129", marker = "platform_machine == 'aarch64'"},
|
||||
]
|
||||
torch = { index = "torch-cu130" }
|
||||
torchvision = { index = "torch-cu130" }
|
||||
torchaudio = { index = "torch-cu130" }
|
||||
sglang-kernel = { index = "sglang-kernel-cu130" }
|
||||
|
||||
[project.optional-dependencies]
|
||||
checkpoint-engine = ["checkpoint-engine==0.1.2"]
|
||||
@@ -107,6 +113,7 @@ diffusion = [
|
||||
"imageio==2.36.0",
|
||||
"imageio-ffmpeg==0.5.1",
|
||||
"moviepy>=2.0.0",
|
||||
"nvidia-modelopt",
|
||||
"opencv-python-headless==4.10.0.84",
|
||||
"remote-pdb==2.1.0",
|
||||
"st_attn==0.0.7 ; platform_machine != 'aarch64' and platform_machine != 'arm64'",
|
||||
|
||||
@@ -249,8 +249,8 @@ def compare_results(jit_out, sgl_out, dtype):
|
||||
assert not torch.isnan(sgl_out).any(), "NaN in SGL results"
|
||||
|
||||
# Compare results
|
||||
atol = 1e-2 if dtype != torch.float32 else 1e-5
|
||||
rtol = 1e-2 if dtype != torch.float32 else 1e-5
|
||||
atol = 4e-2 if dtype != torch.float32 else 1e-5
|
||||
rtol = 4e-2 if dtype != torch.float32 else 1e-5
|
||||
|
||||
torch.testing.assert_close(jit_out, sgl_out, atol=atol, rtol=rtol)
|
||||
|
||||
|
||||
@@ -38,7 +38,29 @@ class WanVideoArchConfig(DiTArchConfig):
|
||||
}
|
||||
)
|
||||
|
||||
reverse_param_names_mapping: dict = field(default_factory=lambda: {})
|
||||
reverse_param_names_mapping: dict = field(
|
||||
default_factory=lambda: {
|
||||
r"^patch_embedding\.proj\.(.*)$": r"patch_embedding.\1",
|
||||
r"^condition_embedder\.text_embedder\.fc_in\.(.*)$": r"condition_embedder.text_embedder.linear_1.\1",
|
||||
r"^condition_embedder\.text_embedder\.fc_out\.(.*)$": r"condition_embedder.text_embedder.linear_2.\1",
|
||||
r"^condition_embedder\.time_embedder\.mlp\.fc_in\.(.*)$": r"condition_embedder.time_embedder.linear_1.\1",
|
||||
r"^condition_embedder\.time_embedder\.mlp\.fc_out\.(.*)$": r"condition_embedder.time_embedder.linear_2.\1",
|
||||
r"^condition_embedder\.time_modulation\.linear\.(.*)$": r"condition_embedder.time_proj.\1",
|
||||
r"^condition_embedder\.image_embedder\.ff\.fc_in\.(.*)$": r"condition_embedder.image_embedder.ff.net.0.proj.\1",
|
||||
r"^condition_embedder\.image_embedder\.ff\.fc_out\.(.*)$": r"condition_embedder.image_embedder.ff.net.2.\1",
|
||||
r"^blocks\.(\d+)\.to_q\.(.*)$": r"blocks.\1.attn1.to_q.\2",
|
||||
r"^blocks\.(\d+)\.to_k\.(.*)$": r"blocks.\1.attn1.to_k.\2",
|
||||
r"^blocks\.(\d+)\.to_v\.(.*)$": r"blocks.\1.attn1.to_v.\2",
|
||||
r"^blocks\.(\d+)\.to_out\.(.*)$": r"blocks.\1.attn1.to_out.0.\2",
|
||||
r"^blocks\.(\d+)\.norm_q\.(.*)$": r"blocks.\1.attn1.norm_q.\2",
|
||||
r"^blocks\.(\d+)\.norm_k\.(.*)$": r"blocks.\1.attn1.norm_k.\2",
|
||||
r"^blocks\.(\d+)\.attn1\.local_attn\.proj_l\.(.*)$": r"blocks.\1.attn1.attn_op.local_attn.proj_l.\2",
|
||||
r"^blocks\.(\d+)\.attn2\.to_out\.(.*)$": r"blocks.\1.attn2.to_out.0.\2",
|
||||
r"^blocks\.(\d+)\.ffn\.fc_in\.(.*)$": r"blocks.\1.ffn.net.0.proj.\2",
|
||||
r"^blocks\.(\d+)\.ffn\.fc_out\.(.*)$": r"blocks.\1.ffn.net.2.\2",
|
||||
r"^blocks\.(\d+)\.self_attn_residual_norm\.norm\.(.*)$": r"blocks.\1.norm2.\2",
|
||||
}
|
||||
)
|
||||
|
||||
# Some LoRA adapters use the original official layer names instead of hf layer names,
|
||||
# so apply this before the param_names_mapping
|
||||
|
||||
@@ -462,6 +462,7 @@ class ModelOptFp4LinearMethod(LinearMethodBase):
|
||||
data=torch.empty(len(output_partition_sizes), dtype=torch.float32),
|
||||
weight_loader=weight_loader,
|
||||
)
|
||||
set_weight_attrs(weight_scale_2, {"missing_param_init": "ones"})
|
||||
layer.register_parameter("weight_scale_2", weight_scale_2)
|
||||
|
||||
weight_scale = ModelWeightParameter(
|
||||
|
||||
@@ -23,7 +23,10 @@ from sglang.multimodal_gen.runtime.loader.utils import (
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||
from sglang.multimodal_gen.runtime.server_args import ServerArgs
|
||||
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import get_hf_config
|
||||
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import (
|
||||
get_hf_config,
|
||||
prepare_diffusers_component_path_for_loading,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
logger = init_logger(__name__)
|
||||
@@ -164,6 +167,9 @@ class ComponentLoader(ABC):
|
||||
elif transformers_or_diffusers == "diffusers":
|
||||
from diffusers import AutoModel
|
||||
|
||||
component_model_path = prepare_diffusers_component_path_for_loading(
|
||||
component_model_path
|
||||
)
|
||||
return AutoModel.from_pretrained(
|
||||
component_model_path,
|
||||
revision=server_args.revision,
|
||||
|
||||
@@ -313,7 +313,9 @@ def load_model_from_full_model_state_dict(
|
||||
|
||||
# map names from checkpoint to customized names
|
||||
custom_param_sd, reverse_param_names_mapping = hf_to_custom_state_dict(
|
||||
full_sd_iterator, param_names_mapping
|
||||
full_sd_iterator,
|
||||
param_names_mapping,
|
||||
valid_target_names=set(meta_sd.keys()),
|
||||
) # type: ignore
|
||||
|
||||
is_fsdp_model = isinstance(model, FSDPModule) or any(
|
||||
|
||||
@@ -43,6 +43,63 @@ _PRECISION_VARIANT_SUFFIX_RE = re.compile(
|
||||
_MIXED_SAFETENSORS_RE = re.compile(r".*-mixed(?:-\d+-of-\d+)?\.safetensors$")
|
||||
|
||||
|
||||
def _get_quant_config_name(config: Optional[QuantizationConfig]) -> Optional[str]:
|
||||
if config is None:
|
||||
return None
|
||||
quant_name_getter = getattr(type(config), "get_name", None)
|
||||
return quant_name_getter() if callable(quant_name_getter) else None
|
||||
|
||||
|
||||
def _merge_modelopt_fp4_configs(
|
||||
existing_config: Optional[QuantizationConfig],
|
||||
inferred_config: Optional[QuantizationConfig],
|
||||
) -> Optional[QuantizationConfig]:
|
||||
"""Prefer safetensors-inferred NVFP4 layout over stale config.json ignores.
|
||||
|
||||
Some ModelOpt NVFP4 transformer repos ship a flat `quantization_config` in
|
||||
`config.json`, but its `ignore` list can lag behind the actual checkpoint
|
||||
contents. The safetensors shards are the source of truth for which modules
|
||||
remain BF16 fallbacks, so when we can infer an NVFP4 config from the shards
|
||||
we should use its exclude list while preserving explicit repo-level knobs
|
||||
such as `swap_weight_nibbles`.
|
||||
"""
|
||||
if inferred_config is None:
|
||||
return existing_config
|
||||
|
||||
if _get_quant_config_name(inferred_config) != "modelopt_fp4":
|
||||
return existing_config or inferred_config
|
||||
|
||||
if existing_config is None:
|
||||
return inferred_config
|
||||
|
||||
if _get_quant_config_name(existing_config) != "modelopt_fp4":
|
||||
return existing_config
|
||||
|
||||
existing_excludes = getattr(existing_config, "exclude_modules", []) or []
|
||||
inferred_excludes = getattr(inferred_config, "exclude_modules", []) or []
|
||||
if inferred_excludes != existing_excludes:
|
||||
logger.warning(
|
||||
"Overriding ModelOpt NVFP4 exclude_modules from config.json with "
|
||||
"safetensors-inferred layout (%d -> %d entries).",
|
||||
len(existing_excludes),
|
||||
len(inferred_excludes),
|
||||
)
|
||||
|
||||
inferred_config.packed_modules_mapping = getattr(
|
||||
existing_config, "packed_modules_mapping", {}
|
||||
)
|
||||
inferred_config.swap_weight_nibbles = getattr(
|
||||
existing_config, "swap_weight_nibbles", True
|
||||
)
|
||||
inferred_config.checkpoint_uses_packed_qkv = getattr(
|
||||
inferred_config, "checkpoint_uses_packed_qkv", False
|
||||
) or getattr(existing_config, "checkpoint_uses_packed_qkv", False)
|
||||
if getattr(inferred_config, "group_size", None) is None:
|
||||
inferred_config.group_size = getattr(existing_config, "group_size", None)
|
||||
|
||||
return inferred_config
|
||||
|
||||
|
||||
@dataclass
|
||||
class TransformerQuantLoadSpec:
|
||||
"""Resolved loading plan for a transformer checkpoint."""
|
||||
@@ -422,13 +479,33 @@ def _resolve_quant_config(
|
||||
resolve quant config from checkpoints' metadata
|
||||
priority: model config.json -> safetensors metadata -> format-specific fallback
|
||||
"""
|
||||
arch_config = server_args.pipeline_config.dit_config.arch_config
|
||||
param_names_mapping_dict = arch_config.param_names_mapping
|
||||
reverse_param_names_mapping_dict = getattr(
|
||||
arch_config, "reverse_param_names_mapping", None
|
||||
)
|
||||
|
||||
quant_config = get_quant_config(hf_config, component_model_path)
|
||||
quant_config_name = _get_quant_config_name(quant_config)
|
||||
inferred_nvfp4_config = None
|
||||
if quant_config is None or quant_config_name == "modelopt_fp4":
|
||||
fallback_group_size = None
|
||||
if quant_config_name == "modelopt_fp4":
|
||||
fallback_group_size = getattr(quant_config, "group_size", None)
|
||||
inferred_nvfp4_config = build_nvfp4_config_from_safetensors_list(
|
||||
safetensors_list,
|
||||
param_names_mapping_dict,
|
||||
reverse_param_names_mapping_dict,
|
||||
fallback_group_size,
|
||||
)
|
||||
quant_config = _merge_modelopt_fp4_configs(quant_config, inferred_nvfp4_config)
|
||||
if quant_config is not None or not server_args.transformer_weights_path:
|
||||
return quant_config
|
||||
|
||||
quant_config = _resolve_quant_config_from_transformer_override(
|
||||
server_args.transformer_weights_path
|
||||
)
|
||||
quant_config = _merge_modelopt_fp4_configs(quant_config, inferred_nvfp4_config)
|
||||
if quant_config is not None:
|
||||
return quant_config
|
||||
|
||||
@@ -437,16 +514,7 @@ def _resolve_quant_config(
|
||||
if quant_config is not None:
|
||||
return quant_config
|
||||
|
||||
param_names_mapping_dict = (
|
||||
server_args.pipeline_config.dit_config.arch_config.param_names_mapping
|
||||
)
|
||||
quant_config = build_nvfp4_config_from_safetensors_list(
|
||||
safetensors_list, param_names_mapping_dict
|
||||
)
|
||||
if quant_config is not None:
|
||||
return quant_config
|
||||
|
||||
return quant_config
|
||||
return inferred_nvfp4_config
|
||||
|
||||
|
||||
def _resolve_target_param_dtype(
|
||||
|
||||
@@ -102,6 +102,7 @@ def get_param_names_mapping(
|
||||
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.
|
||||
@@ -123,6 +124,15 @@ def hf_to_custom_state_dict(
|
||||
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] = (
|
||||
|
||||
@@ -48,6 +48,9 @@ from sglang.multimodal_gen.runtime.utils.model_overlay import (
|
||||
maybe_load_overlay_model_index,
|
||||
maybe_resolve_overlay_model_path,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.utils.quantization_utils import (
|
||||
normalize_flat_modelopt_quant_config,
|
||||
)
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.utils import is_in_ci
|
||||
|
||||
@@ -311,13 +314,50 @@ def load_dict(file_path):
|
||||
) from e
|
||||
|
||||
|
||||
def prepare_diffusers_component_path_for_loading(component_path: str) -> str:
|
||||
"""Download component repos if needed and patch legacy flat ModelOpt configs."""
|
||||
local_component_path = (
|
||||
maybe_download_model(component_path)
|
||||
if not os.path.exists(component_path)
|
||||
else component_path
|
||||
)
|
||||
config_path = os.path.join(local_component_path, "config.json")
|
||||
if not os.path.exists(config_path):
|
||||
return local_component_path
|
||||
|
||||
with get_lock(config_path):
|
||||
try:
|
||||
with open(config_path, encoding="utf-8") as f:
|
||||
config = cast(dict[str, Any], json.load(f))
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to read component config %s: %s", config_path, exc)
|
||||
return local_component_path
|
||||
|
||||
quant_config = config.get("quantization_config")
|
||||
normalized_quant_config = normalize_flat_modelopt_quant_config(quant_config)
|
||||
if normalized_quant_config == quant_config:
|
||||
return local_component_path
|
||||
|
||||
config["quantization_config"] = normalized_quant_config
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, indent=2, sort_keys=True)
|
||||
f.write("\n")
|
||||
logger.warning(
|
||||
"Patched legacy flat ModelOpt quantization_config at %s with quant_type=%s "
|
||||
"for diffusers compatibility.",
|
||||
config_path,
|
||||
normalized_quant_config.get("quant_type"),
|
||||
)
|
||||
|
||||
return local_component_path
|
||||
|
||||
|
||||
def get_diffusers_component_config(
|
||||
component_path: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Gets a configuration of a submodule for the given diffusers model."""
|
||||
# Download from HuggingFace Hub if path doesn't exist locally
|
||||
if not os.path.exists(component_path):
|
||||
component_path = maybe_download_model(component_path)
|
||||
component_path = prepare_diffusers_component_path_for_loading(component_path)
|
||||
|
||||
config_names = ["generation_config.json"]
|
||||
# By default, we load config.json, but scheduler_config.json for scheduler
|
||||
|
||||
@@ -3,9 +3,8 @@ import json
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import torch
|
||||
from safetensors import safe_open
|
||||
|
||||
from sglang.multimodal_gen.runtime.layers.quantization import (
|
||||
@@ -17,7 +16,59 @@ from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
def normalize_flat_modelopt_quant_config(
|
||||
quant_cfg: dict[str, Any] | None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Fill required diffusers fields for flat ModelOpt component configs."""
|
||||
if not isinstance(quant_cfg, dict) or quant_cfg.get("quant_method") != "modelopt":
|
||||
return quant_cfg
|
||||
|
||||
quant_algo = str(
|
||||
quant_cfg.get("quant_algo")
|
||||
or quant_cfg.get("quantization", {}).get("quant_algo")
|
||||
or ""
|
||||
).upper()
|
||||
if not quant_algo:
|
||||
return quant_cfg
|
||||
|
||||
normalized = dict(quant_cfg)
|
||||
normalized.setdefault("quant_type", quant_algo)
|
||||
return normalized
|
||||
|
||||
|
||||
def _infer_nvfp4_group_size_from_tensors(weight, scale) -> Optional[int]:
|
||||
"""Infer NVFP4 group_size from serialized weight/scale tensor shapes."""
|
||||
weight_shape = tuple(getattr(weight, "shape", ()))
|
||||
scale_shape = tuple(getattr(scale, "shape", ()))
|
||||
if len(weight_shape) < 2:
|
||||
return None
|
||||
|
||||
input_size = int(weight_shape[1]) * 2
|
||||
if input_size <= 0:
|
||||
return None
|
||||
|
||||
candidate_num_groups: list[int] = []
|
||||
if len(scale_shape) >= 2:
|
||||
candidate_num_groups.append(int(scale_shape[-1]))
|
||||
elif len(scale_shape) == 1:
|
||||
scale_len = int(scale_shape[0])
|
||||
if scale_len == int(weight_shape[0]):
|
||||
candidate_num_groups.append(1)
|
||||
candidate_num_groups.append(scale_len)
|
||||
else:
|
||||
candidate_num_groups.append(1)
|
||||
|
||||
for num_groups in candidate_num_groups:
|
||||
if num_groups <= 0:
|
||||
continue
|
||||
if input_size % num_groups == 0:
|
||||
return input_size // num_groups
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_quant_method_name(quant_cfg: dict) -> str:
|
||||
quant_cfg = normalize_flat_modelopt_quant_config(quant_cfg) or quant_cfg
|
||||
quant_method = quant_cfg.get("quant_method")
|
||||
if quant_method != "modelopt":
|
||||
return quant_method
|
||||
@@ -79,7 +130,9 @@ def get_quant_config(
|
||||
if "quantization_config" not in model_config:
|
||||
return None
|
||||
|
||||
hf_quant_config = model_config["quantization_config"]
|
||||
hf_quant_config = normalize_flat_modelopt_quant_config(
|
||||
model_config["quantization_config"]
|
||||
)
|
||||
if hf_quant_config is not None and not isinstance(hf_quant_config, dict):
|
||||
hf_quant_config = hf_quant_config.to_dict()
|
||||
quant_cls = _load_quant_cls(hf_quant_config)
|
||||
@@ -210,6 +263,8 @@ def get_metadata_from_safetensors_file(file_path: str):
|
||||
def _build_nvfp4_config_from_safetensors_files(
|
||||
file_paths: list[str],
|
||||
param_names_mapping_dict: Optional[dict] = None,
|
||||
reverse_param_names_mapping_dict: Optional[dict] = None,
|
||||
fallback_group_size: Optional[int] = None,
|
||||
) -> Optional[QuantizationConfig]:
|
||||
"""Build a single NVFP4 config by aggregating metadata across multiple files.
|
||||
|
||||
@@ -220,7 +275,7 @@ def _build_nvfp4_config_from_safetensors_files(
|
||||
group_size = None
|
||||
quantized_bfl_modules: set[str] = set()
|
||||
non_quantized_bfl_modules: set[str] = set()
|
||||
files_with_nvfp4_metadata: list[str] = []
|
||||
files_with_nvfp4_signal: list[str] = []
|
||||
checkpoint_uses_packed_qkv = False
|
||||
packed_qkv_pattern = re.compile(
|
||||
r"^(double_blocks\.\d+\.(img|txt)_attn\.qkv|single_blocks\.\d+\.linear1)\."
|
||||
@@ -228,79 +283,142 @@ def _build_nvfp4_config_from_safetensors_files(
|
||||
|
||||
for file_path in file_paths:
|
||||
metadata = get_metadata_from_safetensors_file(file_path)
|
||||
if not metadata:
|
||||
continue
|
||||
quant_config_dict = None
|
||||
metadata_signals_nvfp4 = False
|
||||
if metadata:
|
||||
quant_config_str = metadata.get("_quantization_metadata")
|
||||
if quant_config_str:
|
||||
try:
|
||||
quant_config_dict = json.loads(quant_config_str)
|
||||
except json.JSONDecodeError:
|
||||
quant_config_dict = None
|
||||
else:
|
||||
quant_algo = str(quant_config_dict.get("quant_algo", "")).upper()
|
||||
quant_type = str(quant_config_dict.get("quant_type", "")).upper()
|
||||
metadata_signals_nvfp4 = (
|
||||
"NVFP4" in quant_algo
|
||||
or "FP4" in quant_algo
|
||||
or "NVFP4" in quant_type
|
||||
)
|
||||
|
||||
quant_config_str = metadata.get("_quantization_metadata")
|
||||
if not quant_config_str:
|
||||
continue
|
||||
|
||||
quant_config_dict = json.loads(quant_config_str)
|
||||
file_quantized_modules: set[str] = set()
|
||||
if (
|
||||
"format_version" not in quant_config_dict
|
||||
or "layers" not in quant_config_dict
|
||||
quant_config_dict is not None
|
||||
and "format_version" in quant_config_dict
|
||||
and "layers" in quant_config_dict
|
||||
):
|
||||
continue
|
||||
|
||||
layers = quant_config_dict.get("layers", {})
|
||||
file_quantized_modules = {
|
||||
layer_name
|
||||
for layer_name, layer_cfg in layers.items()
|
||||
if isinstance(layer_cfg, dict) and layer_cfg.get("format") == "nvfp4"
|
||||
}
|
||||
if not file_quantized_modules:
|
||||
continue
|
||||
|
||||
files_with_nvfp4_metadata.append(file_path)
|
||||
quantized_bfl_modules.update(file_quantized_modules)
|
||||
layers = quant_config_dict.get("layers", {})
|
||||
file_quantized_modules.update(
|
||||
layer_name
|
||||
for layer_name, layer_cfg in layers.items()
|
||||
if isinstance(layer_cfg, dict) and layer_cfg.get("format") == "nvfp4"
|
||||
)
|
||||
|
||||
with safe_open(file_path, framework="pt", device="cpu") as f:
|
||||
all_keys = set(f.keys())
|
||||
if any(packed_qkv_pattern.match(k) for k in all_keys):
|
||||
checkpoint_uses_packed_qkv = True
|
||||
|
||||
# Some ModelOpt NVFP4 exports only store a flat config.json plus
|
||||
# per-file metadata without the diffusers `layers` section. Infer
|
||||
# quantized modules directly from tensor families in that case:
|
||||
# quantized modules ship `.weight` + `.weight_scale`, while BF16
|
||||
# fallbacks only ship `.weight`.
|
||||
file_quantized_modules.update(
|
||||
key[: -len(".weight_scale")]
|
||||
for key in all_keys
|
||||
if key.endswith(".weight_scale")
|
||||
and f"{key[: -len('.weight_scale')]}.weight" in all_keys
|
||||
)
|
||||
|
||||
if file_quantized_modules or metadata_signals_nvfp4:
|
||||
files_with_nvfp4_signal.append(file_path)
|
||||
quantized_bfl_modules.update(file_quantized_modules)
|
||||
|
||||
if group_size is None:
|
||||
for layer_name in file_quantized_modules:
|
||||
for layer_name in sorted(file_quantized_modules):
|
||||
weight_key = f"{layer_name}.weight"
|
||||
scale_key = f"{layer_name}.weight_scale"
|
||||
if weight_key in all_keys and scale_key in all_keys:
|
||||
w = f.get_tensor(weight_key)
|
||||
s = f.get_tensor(scale_key)
|
||||
input_size = w.shape[1] * 2
|
||||
group_size = input_size // s.shape[1]
|
||||
break
|
||||
group_size = _infer_nvfp4_group_size_from_tensors(w, s)
|
||||
if group_size is not None:
|
||||
break
|
||||
|
||||
for k in sorted(all_keys):
|
||||
if not k.endswith(".weight"):
|
||||
continue
|
||||
t = f.get_tensor(k)
|
||||
if t.dtype != torch.uint8:
|
||||
non_quantized_bfl_modules.add(k[: -len(".weight")])
|
||||
module_name = k[: -len(".weight")]
|
||||
if module_name not in file_quantized_modules:
|
||||
non_quantized_bfl_modules.add(module_name)
|
||||
|
||||
if not files_with_nvfp4_metadata:
|
||||
if not files_with_nvfp4_signal:
|
||||
return None
|
||||
|
||||
if (
|
||||
group_size is not None
|
||||
and fallback_group_size is not None
|
||||
and group_size != fallback_group_size
|
||||
):
|
||||
logger.warning(
|
||||
"NVFP4 group_size inferred from safetensors (%d) does not match config (%d); "
|
||||
"preferring safetensors.",
|
||||
group_size,
|
||||
fallback_group_size,
|
||||
)
|
||||
|
||||
if group_size is None and fallback_group_size is not None:
|
||||
logger.info(
|
||||
"Falling back to config-derived NVFP4 group_size=%d for %s",
|
||||
fallback_group_size,
|
||||
", ".join(files_with_nvfp4_signal),
|
||||
)
|
||||
group_size = fallback_group_size
|
||||
|
||||
if group_size is None:
|
||||
logger.warning(
|
||||
"Could not infer group_size from NVFP4 safetensors: %s",
|
||||
", ".join(files_with_nvfp4_metadata),
|
||||
", ".join(files_with_nvfp4_signal),
|
||||
)
|
||||
return None
|
||||
|
||||
exclude_bfl_modules = sorted(non_quantized_bfl_modules - quantized_bfl_modules)
|
||||
|
||||
exclude_modules = []
|
||||
if param_names_mapping_dict:
|
||||
mapping_fn = None
|
||||
reverse_mapping_fn = None
|
||||
if param_names_mapping_dict or reverse_param_names_mapping_dict:
|
||||
from sglang.multimodal_gen.runtime.loader.utils import get_param_names_mapping
|
||||
|
||||
mapping_fn = get_param_names_mapping(param_names_mapping_dict)
|
||||
for module_bfl in exclude_bfl_modules:
|
||||
mapped, _, _ = mapping_fn(f"{module_bfl}.weight")
|
||||
exclude_modules.append(
|
||||
mapped[: -len(".weight")] if mapped.endswith(".weight") else mapped
|
||||
if param_names_mapping_dict:
|
||||
mapping_fn = get_param_names_mapping(param_names_mapping_dict)
|
||||
if reverse_param_names_mapping_dict:
|
||||
reverse_mapping_fn = get_param_names_mapping(
|
||||
reverse_param_names_mapping_dict
|
||||
)
|
||||
else:
|
||||
exclude_modules = exclude_bfl_modules
|
||||
|
||||
for module_bfl in exclude_bfl_modules:
|
||||
raw_weight_name = f"{module_bfl}.weight"
|
||||
if mapping_fn is not None:
|
||||
mapped, _, _ = mapping_fn(raw_weight_name)
|
||||
if mapped != raw_weight_name:
|
||||
exclude_modules.append(module_bfl)
|
||||
continue
|
||||
|
||||
if reverse_mapping_fn is not None:
|
||||
reverse_mapped, _, _ = reverse_mapping_fn(raw_weight_name)
|
||||
if reverse_mapped != raw_weight_name:
|
||||
exclude_modules.append(
|
||||
reverse_mapped[: -len(".weight")]
|
||||
if reverse_mapped.endswith(".weight")
|
||||
else reverse_mapped
|
||||
)
|
||||
continue
|
||||
|
||||
exclude_modules.append(module_bfl)
|
||||
|
||||
exclude_modules = sorted(set(exclude_modules))
|
||||
|
||||
try:
|
||||
quant_cls = get_quantization_config("modelopt_fp4")
|
||||
@@ -314,7 +432,7 @@ def _build_nvfp4_config_from_safetensors_files(
|
||||
)
|
||||
logger.info(
|
||||
"Built NVFP4 quant config from %d safetensors: group_size=%d, %d excluded modules, packed_qkv=%s",
|
||||
len(files_with_nvfp4_metadata),
|
||||
len(files_with_nvfp4_signal),
|
||||
group_size,
|
||||
len(exclude_modules),
|
||||
checkpoint_uses_packed_qkv,
|
||||
@@ -323,7 +441,7 @@ def _build_nvfp4_config_from_safetensors_files(
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to build NVFP4 config from %s: %s",
|
||||
", ".join(files_with_nvfp4_metadata),
|
||||
", ".join(files_with_nvfp4_signal),
|
||||
e,
|
||||
)
|
||||
return None
|
||||
@@ -332,17 +450,27 @@ def _build_nvfp4_config_from_safetensors_files(
|
||||
def build_nvfp4_config_from_safetensors(
|
||||
file_path: str,
|
||||
param_names_mapping_dict: Optional[dict] = None,
|
||||
reverse_param_names_mapping_dict: Optional[dict] = None,
|
||||
fallback_group_size: Optional[int] = None,
|
||||
) -> Optional[QuantizationConfig]:
|
||||
"""Backward-compatible wrapper for a single safetensors file."""
|
||||
return _build_nvfp4_config_from_safetensors_files(
|
||||
[file_path], param_names_mapping_dict
|
||||
[file_path],
|
||||
param_names_mapping_dict,
|
||||
reverse_param_names_mapping_dict,
|
||||
fallback_group_size,
|
||||
)
|
||||
|
||||
|
||||
def build_nvfp4_config_from_safetensors_list(
|
||||
file_paths: list[str],
|
||||
param_names_mapping_dict: Optional[dict] = None,
|
||||
reverse_param_names_mapping_dict: Optional[dict] = None,
|
||||
fallback_group_size: Optional[int] = None,
|
||||
) -> Optional[QuantizationConfig]:
|
||||
return _build_nvfp4_config_from_safetensors_files(
|
||||
file_paths, param_names_mapping_dict
|
||||
file_paths,
|
||||
param_names_mapping_dict,
|
||||
reverse_param_names_mapping_dict,
|
||||
fallback_group_size,
|
||||
)
|
||||
|
||||
@@ -35,6 +35,10 @@ import torch
|
||||
from safetensors import safe_open
|
||||
from safetensors.torch import load_file, save_file
|
||||
|
||||
from sglang.multimodal_gen.runtime.utils.quantization_utils import (
|
||||
normalize_flat_modelopt_quant_config,
|
||||
)
|
||||
|
||||
INDEX_FILENAMES = [
|
||||
"model.safetensors.index.json",
|
||||
"diffusion_pytorch_model.safetensors.index.json",
|
||||
@@ -467,6 +471,10 @@ def build_modelopt_fp8_transformer(
|
||||
effective_quant_config = json.loads(json.dumps(quant_config))
|
||||
if not quant_algo:
|
||||
effective_quant_config["quant_algo"] = "FP8"
|
||||
effective_quant_config = (
|
||||
normalize_flat_modelopt_quant_config(effective_quant_config)
|
||||
or effective_quant_config
|
||||
)
|
||||
|
||||
auto_ignore_modules = sorted(
|
||||
{
|
||||
|
||||
@@ -264,7 +264,17 @@ def gpu_p2p_access_check(src: int, tgt: int) -> bool:
|
||||
path = os.path.join(
|
||||
SGLANG_CACHE_ROOT, f"gpu_p2p_access_cache_for_{cuda_visible_devices}.json"
|
||||
)
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
cache_dir = os.path.dirname(path)
|
||||
try:
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
except (FileExistsError, NotADirectoryError):
|
||||
if not os.path.isdir(cache_dir):
|
||||
# Path exists as a file (stale cache/lock). Remove and retry.
|
||||
try:
|
||||
os.remove(cache_dir)
|
||||
except OSError:
|
||||
pass
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
from sglang.srt.distributed.parallel_state import get_world_group
|
||||
|
||||
if (not is_distributed or get_world_group().local_rank == 0) and (
|
||||
|
||||
@@ -79,6 +79,7 @@ from sglang.srt.server_args import get_global_server_args
|
||||
from sglang.srt.utils import (
|
||||
LazyValue,
|
||||
add_prefix,
|
||||
get_cuda_version,
|
||||
is_blackwell_supported,
|
||||
is_cuda,
|
||||
is_flashinfer_available,
|
||||
@@ -96,7 +97,7 @@ _is_tinygemm_supported = (
|
||||
and (is_sm90_supported() or is_blackwell_supported())
|
||||
)
|
||||
|
||||
if _is_tinygemm_supported:
|
||||
if _is_tinygemm_supported and get_cuda_version()[0] < 13:
|
||||
try:
|
||||
from flashinfer.gemm import tinygemm_bf16
|
||||
except ImportError:
|
||||
@@ -104,6 +105,7 @@ if _is_tinygemm_supported:
|
||||
_is_tinygemm_supported = False
|
||||
else:
|
||||
tinygemm_bf16 = None
|
||||
_is_tinygemm_supported = False
|
||||
|
||||
|
||||
class GptOssConfig(PretrainedConfig):
|
||||
|
||||
@@ -75,7 +75,9 @@ def bench_kineto(
|
||||
)
|
||||
profiler = (
|
||||
torch.profiler.profile(
|
||||
activities=[torch.profiler.ProfilerActivity.CUDA], schedule=schedule
|
||||
activities=[torch.profiler.ProfilerActivity.CUDA],
|
||||
schedule=schedule,
|
||||
acc_events=True,
|
||||
)
|
||||
if not using_nsys
|
||||
else nullcontext()
|
||||
@@ -88,8 +90,8 @@ def bench_kineto(
|
||||
flush_l2_size, dtype=torch.int, device="cuda"
|
||||
).zero_()
|
||||
fn()
|
||||
|
||||
if not using_nsys:
|
||||
torch.cuda.synchronize()
|
||||
profiler.step()
|
||||
|
||||
# Return 1 if using Nsight Systems
|
||||
@@ -106,6 +108,22 @@ def bench_kineto(
|
||||
)
|
||||
kernel_names = (kernel_names,) if isinstance(kernel_names, str) else kernel_names
|
||||
assert all([isinstance(name, str) for name in kernel_names])
|
||||
# Check if profiler captured any events (can be empty with some CUDA versions)
|
||||
non_empty_lines = [l for l in prof_lines if l.strip() and not l.startswith("-")]
|
||||
if len(non_empty_lines) <= 1:
|
||||
print(
|
||||
"WARNING: Profiler returned empty table — falling back to wall-clock timing"
|
||||
)
|
||||
import time
|
||||
|
||||
torch.cuda.synchronize()
|
||||
start = time.perf_counter()
|
||||
for _ in range(num_tests):
|
||||
fn()
|
||||
torch.cuda.synchronize()
|
||||
elapsed = (time.perf_counter() - start) / num_tests
|
||||
return tuple([elapsed] * len(kernel_names)) if is_tuple else elapsed
|
||||
|
||||
if not with_multiple_kernels:
|
||||
for name in kernel_names:
|
||||
assert (
|
||||
|
||||
@@ -116,10 +116,12 @@ CI_MULTI_LORA_MODELS = [
|
||||
LoRAAdaptor(
|
||||
name="winddude/wizardLM-LlaMA-LoRA-7B",
|
||||
prefill_tolerance=1e-1,
|
||||
rouge_l_tolerance=0.9,
|
||||
),
|
||||
LoRAAdaptor(
|
||||
name="RuterNorway/Llama-2-7b-chat-norwegian-LoRa",
|
||||
prefill_tolerance=3e-1,
|
||||
rouge_l_tolerance=0.9,
|
||||
),
|
||||
],
|
||||
max_loras_per_batch=2,
|
||||
@@ -670,8 +672,7 @@ def create_multiple_batch_test_samples(
|
||||
prompts: List[str], lora_adapter_paths: List[str]
|
||||
):
|
||||
random.seed(42)
|
||||
from sglang.multimodal_gen.runtime.utils.common import get_bool_env_var
|
||||
from sglang.srt.utils.common import is_hip
|
||||
from sglang.srt.utils.common import get_bool_env_var, is_hip
|
||||
|
||||
_use_aiter = get_bool_env_var("SGLANG_USE_AITER") and is_hip()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user