[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:
Baizhou Zhang
2026-04-19 05:32:36 -07:00
committed by GitHub
co-authored by Kangyan Zhou Claude Opus 4.7 Alison Shao Mick
parent 03828f4205
commit 6ecd6f84db
39 changed files with 892 additions and 239 deletions
+17 -10
View File
@@ -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 (
+3 -1
View File
@@ -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):
+20 -2
View File
@@ -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 (
+3 -2
View File
@@ -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()