[diffusion] refactor: move format-specific weight loading hooks (quant-related) to a dedicated file (#21366)

This commit is contained in:
Mick
2026-03-27 09:58:49 +08:00
committed by GitHub
parent e1ee68d0fc
commit d7c4c57ace
6 changed files with 476 additions and 201 deletions
@@ -4,12 +4,13 @@ from __future__ import annotations
import os import os
import re import re
from dataclasses import dataclass from dataclasses import dataclass, replace
from typing import Any from typing import Any
import torch import torch
from sglang.multimodal_gen.runtime.layers.quantization.configs.nunchaku_config import ( from sglang.multimodal_gen.runtime.layers.quantization.configs.nunchaku_config import (
NunchakuConfig,
is_nunchaku_available, is_nunchaku_available,
) )
from sglang.multimodal_gen.runtime.platforms import current_platform from sglang.multimodal_gen.runtime.platforms import current_platform
@@ -19,6 +20,14 @@ from sglang.multimodal_gen.utils import StoreBoolean
logger = init_logger(__name__) logger = init_logger(__name__)
@dataclass
class NunchakuArgsResolution:
"""Normalized runtime settings derived from Nunchaku CLI-facing args."""
transformer_weights_path: str | None = None
nunchaku_config: NunchakuConfig | None = None
@dataclass @dataclass
class NunchakuSVDQuantArgs: class NunchakuSVDQuantArgs:
"""CLI-facing configuration for Nunchaku (SVDQuant) inference. """CLI-facing configuration for Nunchaku (SVDQuant) inference.
@@ -33,20 +42,22 @@ class NunchakuSVDQuantArgs:
quantization_rank: int | None = None quantization_rank: int | None = None
quantization_act_unsigned: bool = False quantization_act_unsigned: bool = False
def _adjust_config(self) -> None: def _infer_from_weights_path(self) -> tuple[bool, str | None, int | None]:
"""infer precision and rank from filename if not provided""" """Infer whether SVDQuant is enabled and parse precision/rank from filename."""
if self.transformer_weights_path and not self.enable_svdquant:
filename = os.path.basename(self.transformer_weights_path)
if re.search(r"svdq-(int4|fp4)_r(\d+)", filename):
self.enable_svdquant = True
if not self.enable_svdquant or not self.transformer_weights_path:
return
inferred_precision = None inferred_precision = None
inferred_rank = None inferred_rank = None
enable_svdquant = self.enable_svdquant
if not self.transformer_weights_path:
return enable_svdquant, inferred_precision, inferred_rank
filename = os.path.basename(self.transformer_weights_path) filename = os.path.basename(self.transformer_weights_path)
if not enable_svdquant and re.search(r"svdq-(int4|fp4)_r(\d+)", filename):
enable_svdquant = True
if not enable_svdquant:
return enable_svdquant, inferred_precision, inferred_rank
# Expected pattern: svdq-{precision}_r{rank}-... # Expected pattern: svdq-{precision}_r{rank}-...
# e.g., svdq-int4_r32-qwen-image.safetensors # e.g., svdq-int4_r32-qwen-image.safetensors
match = re.search(r"svdq-(int4|fp4)_r(\d+)", filename) match = re.search(r"svdq-(int4|fp4)_r(\d+)", filename)
@@ -56,26 +67,39 @@ class NunchakuSVDQuantArgs:
inferred_precision = "nvfp4" if p_str == "fp4" else "int4" inferred_precision = "nvfp4" if p_str == "fp4" else "int4"
inferred_rank = int(r_str) inferred_rank = int(r_str)
if self.quantization_precision is None: return enable_svdquant, inferred_precision, inferred_rank
self.quantization_precision = inferred_precision or "int4"
def _normalized(self) -> "NunchakuSVDQuantArgs":
enable_svdquant, inferred_precision, inferred_rank = (
self._infer_from_weights_path()
)
normalized = replace(
self,
enable_svdquant=enable_svdquant,
quantization_precision=(
self.quantization_precision or inferred_precision or "int4"
),
quantization_rank=self.quantization_rank or inferred_rank or 32,
)
if self.quantization_precision is None and inferred_precision:
if inferred_precision: if inferred_precision:
logger.info( logger.info(
f"inferred --quantization-precision: {self.quantization_precision} " f"inferred --quantization-precision: {normalized.quantization_precision} "
f"from --transformer-weights-path: {self.transformer_weights_path}" f"from --transformer-weights-path: {self.transformer_weights_path}"
) )
if self.quantization_rank is None: if self.quantization_rank is None and inferred_rank:
self.quantization_rank = inferred_rank or 32
if inferred_rank: if inferred_rank:
logger.info( logger.info(
f"inferred --quantization-rank: {self.quantization_rank} " f"inferred --quantization-rank: {normalized.quantization_rank} "
f"from --transformer-weights-path: {self.transformer_weights_path}" f"from --transformer-weights-path: {self.transformer_weights_path}"
) )
def validate(self) -> None: return normalized
# TODO: warn if the served model doesn't support nunchaku
self._adjust_config()
def _validate(self) -> None:
# TODO: warn if the served model doesn't support nunchaku
if not self.enable_svdquant: if not self.enable_svdquant:
return return
@@ -98,7 +122,6 @@ class NunchakuSVDQuantArgs:
if unsupported: if unsupported:
raise ValueError( raise ValueError(
"Nunchaku SVDQuant is currently only supported on Ampere (SM8x) or SM12x GPUs; " "Nunchaku SVDQuant is currently only supported on Ampere (SM8x) or SM12x GPUs; "
"Hopper (SM90) is not supported. "
f"Unsupported devices: {', '.join(unsupported)}. " f"Unsupported devices: {', '.join(unsupported)}. "
"Disable it with --enable-svdquant false." "Disable it with --enable-svdquant false."
) )
@@ -124,6 +147,26 @@ class NunchakuSVDQuantArgs:
f"Invalid --quantization-rank: {self.quantization_rank}. Must be > 0" f"Invalid --quantization-rank: {self.quantization_rank}. Must be > 0"
) )
def resolve_runtime_config(self) -> NunchakuArgsResolution:
normalized = self._normalized()
normalized._validate()
if not normalized.enable_svdquant or not normalized.transformer_weights_path:
return NunchakuArgsResolution(
transformer_weights_path=normalized.transformer_weights_path,
nunchaku_config=None,
)
return NunchakuArgsResolution(
transformer_weights_path=normalized.transformer_weights_path,
nunchaku_config=NunchakuConfig(
precision=normalized.quantization_precision,
rank=normalized.quantization_rank,
act_unsigned=normalized.quantization_act_unsigned,
transformer_weights_path=normalized.transformer_weights_path,
),
)
@staticmethod @staticmethod
def add_cli_args(parser) -> None: def add_cli_args(parser) -> None:
parser.add_argument( parser.add_argument(
@@ -1,38 +1,24 @@
import json
import logging import logging
import os from typing import Any
from typing import Any, Dict, List, Optional
import torch import torch
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
from sglang.multimodal_gen.runtime.layers.quantization.configs.nunchaku_config import (
NunchakuConfig,
_patch_nunchaku_scales,
)
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import ( from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
ComponentLoader, ComponentLoader,
) )
from sglang.multimodal_gen.runtime.loader.fsdp_load import maybe_load_fsdp_model from sglang.multimodal_gen.runtime.loader.fsdp_load import maybe_load_fsdp_model
from sglang.multimodal_gen.runtime.loader.utils import ( from sglang.multimodal_gen.runtime.loader.transformer_load_utils import (
_list_safetensors_files, resolve_transformer_quant_load_spec,
_normalize_component_type, resolve_transformer_safetensors_to_load,
) )
from sglang.multimodal_gen.runtime.loader.utils import _normalize_component_type
from sglang.multimodal_gen.runtime.models.registry import ModelRegistry from sglang.multimodal_gen.runtime.models.registry import ModelRegistry
from sglang.multimodal_gen.runtime.server_args import ServerArgs from sglang.multimodal_gen.runtime.server_args import ServerArgs
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import ( from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import (
get_diffusers_component_config, get_diffusers_component_config,
maybe_download_model,
) )
from sglang.multimodal_gen.runtime.utils.logging_utils import get_log_level, init_logger from sglang.multimodal_gen.runtime.utils.logging_utils import get_log_level, init_logger
from sglang.multimodal_gen.runtime.utils.quantization_utils import (
build_nvfp4_config_from_safetensors_list,
get_metadata_from_safetensors_file,
get_quant_config,
get_quant_config_from_safetensors_metadata,
)
from sglang.multimodal_gen.utils import PRECISION_TO_TYPE
from sglang.srt.layers.quantization import QuantizationConfig
from sglang.srt.utils import is_npu from sglang.srt.utils import is_npu
_is_npu = is_npu() _is_npu = is_npu()
@@ -46,97 +32,6 @@ class TransformerLoader(ComponentLoader):
component_names = ["transformer", "audio_dit", "video_dit"] component_names = ["transformer", "audio_dit", "video_dit"]
expected_library = "diffusers" expected_library = "diffusers"
def get_list_of_safetensors_to_load(
self, server_args: ServerArgs, component_model_path: str
) -> list[str]:
"""
get list of safetensors to load.
If --transformer-weights-path is provided, load weights from that path
instead of the base model's component directory.
"""
quantized_path = server_args.transformer_weights_path
if quantized_path:
quantized_path = maybe_download_model(quantized_path)
logger.info("using quantized transformer weights from: %s", quantized_path)
if os.path.isfile(quantized_path) and quantized_path.endswith(
".safetensors"
):
safetensors_list = [quantized_path]
else:
safetensors_list = _list_safetensors_files(quantized_path)
else:
safetensors_list = _list_safetensors_files(component_model_path)
if not safetensors_list:
raise ValueError(
f"no safetensors files found in "
f"{quantized_path or component_model_path}"
)
return safetensors_list
def _resolve_quant_config(
self,
hf_config: Dict[str, List[str]],
server_args: ServerArgs,
safetensors_list: list[str],
component_model_path: str,
) -> Optional[QuantizationConfig]:
# priority: model config.json → safetensors metadata → quantization config (nvfp4, nunchaku, ...)
quant_config = get_quant_config(hf_config, component_model_path)
if quant_config is None and server_args.transformer_weights_path:
# try to read quantization_config from the safetensors metadata header
for safetensors_file in safetensors_list:
quant_config = get_quant_config_from_safetensors_metadata(
safetensors_file
)
if quant_config:
return quant_config
# fallback: handle nvfp4 per-layer format metadata
# ({"format_version": ..., "layers": {"name": {"format": "nvfp4"}, ...}})
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:
return quant_config
return quant_config
def _resolve_target_param_dtype(
self,
quant_config: Optional[dict],
nunchaku_config: Optional[NunchakuConfig],
model_cls,
server_args: ServerArgs,
) -> Optional[torch.dtype]:
if quant_config is not None or nunchaku_config is not None:
# TODO: improve the condition
# respect dtype from checkpoint
param_dtype = None
else:
param_dtype = PRECISION_TO_TYPE[server_args.pipeline_config.dit_precision]
if nunchaku_config is not None:
nunchaku_config.model_cls = model_cls
# verify that the nunchaku checkpoint matches the selected model class
original_dit_cls_name = json.loads(
get_metadata_from_safetensors_file(
nunchaku_config.transformer_weights_path
).get("config")
)["_class_name"]
specified_dit_cls_name = str(model_cls.__name__)
if original_dit_cls_name != specified_dit_cls_name:
raise Exception(
f"Class name of DiT specified in nunchaku transformer_weights_path: {original_dit_cls_name} does not match that of specified DiT name: {specified_dit_cls_name}"
)
return param_dtype
def load_customized( def load_customized(
self, component_model_path: str, server_args: ServerArgs, component_name: str self, component_model_path: str, server_args: ServerArgs, component_name: str
): ):
@@ -144,16 +39,11 @@ class TransformerLoader(ComponentLoader):
# 1. hf config # 1. hf config
config = get_diffusers_component_config(component_path=component_model_path) config = get_diffusers_component_config(component_path=component_model_path)
# 2. quant config safetensors_list = resolve_transformer_safetensors_to_load(
safetensors_list = self.get_list_of_safetensors_to_load(
server_args, component_model_path server_args, component_model_path
) )
quant_config = self._resolve_quant_config( # 2. dit config
config, server_args, safetensors_list, component_model_path
)
# 3. dit config
# Config from Diffusers supersedes sgl_diffusion's model config # Config from Diffusers supersedes sgl_diffusion's model config
component_name = _normalize_component_type(component_name) component_name = _normalize_component_type(component_name)
server_args.model_paths[component_name] = component_model_path server_args.model_paths[component_name] = component_model_path
@@ -169,9 +59,13 @@ class TransformerLoader(ComponentLoader):
cls_name = config.pop("_class_name") cls_name = config.pop("_class_name")
model_cls, _ = ModelRegistry.resolve_model_cls(cls_name) model_cls, _ = ModelRegistry.resolve_model_cls(cls_name)
nunchaku_config = server_args.nunchaku_config quant_spec = resolve_transformer_quant_load_spec(
param_dtype = self._resolve_target_param_dtype( hf_config=config,
quant_config, nunchaku_config, model_cls, server_args server_args=server_args,
safetensors_list=safetensors_list,
component_model_path=component_model_path,
model_cls=model_cls,
cls_name=cls_name,
) )
logger.info( logger.info(
@@ -179,14 +73,13 @@ class TransformerLoader(ComponentLoader):
cls_name, cls_name,
len(safetensors_list), len(safetensors_list),
f": {safetensors_list}" if get_log_level() == logging.DEBUG else "", f": {safetensors_list}" if get_log_level() == logging.DEBUG else "",
param_dtype, quant_spec.param_dtype,
) )
# prepare init_param # prepare init_param
init_params: dict[str, Any] = { init_params: dict[str, Any] = {
"config": dit_config, "config": dit_config,
"hf_config": config, "hf_config": config,
"quant_config": (quant_config if quant_config else nunchaku_config), "quant_config": quant_spec.runtime_quant_config,
} }
if ( if (
init_params["quant_config"] is None init_params["quant_config"] is None
@@ -209,23 +102,28 @@ class TransformerLoader(ComponentLoader):
cpu_offload=server_args.dit_cpu_offload, cpu_offload=server_args.dit_cpu_offload,
pin_cpu_memory=server_args.pin_cpu_memory, pin_cpu_memory=server_args.pin_cpu_memory,
fsdp_inference=server_args.use_fsdp_inference, fsdp_inference=server_args.use_fsdp_inference,
# TODO(will): make these configurable param_dtype=quant_spec.param_dtype,
param_dtype=param_dtype,
reduce_dtype=torch.float32, reduce_dtype=torch.float32,
output_dtype=None, output_dtype=None,
strict=False, strict=False,
) )
if nunchaku_config is not None: # post-hooks (e.g., patch scales (nunchaku))
_patch_nunchaku_scales(model, safetensors_list) for post_load_hook in quant_spec.post_load_hooks:
post_load_hook(model)
total_params = sum(p.numel() for p in model.parameters()) total_params = sum(p.numel() for p in model.parameters())
logger.info("Loaded model with %.2fB parameters", total_params / 1e9) logger.info("Loaded model with %.2fB parameters", total_params / 1e9)
# considering the existent of mixed-precision models (e.g., nunchaku) # considering the existent of mixed-precision models (e.g., nunchaku)
if next(model.parameters()).dtype != param_dtype and param_dtype: if (
next(model.parameters()).dtype != quant_spec.param_dtype
and quant_spec.param_dtype
):
logger.warning( logger.warning(
f"Model dtype does not match expected param dtype, {next(model.parameters()).dtype} vs {param_dtype}" "Model dtype does not match expected param dtype, %s vs %s",
next(model.parameters()).dtype,
quant_spec.param_dtype,
) )
return model return model
@@ -0,0 +1,306 @@
"""Helpers and adapters for transformer quantized checkpoint loading.
This module keeps format-specific loading quirks out of `TransformerLoader`.
The loader should stay focused on the generic load flow, while special cases
such as Nunchaku validation, NVFP4 fallback adjustments, and post-load patching
are handled here behind a small helper/adapter layer.
"""
import json
import os
from dataclasses import dataclass, field
from functools import partial
from typing import Callable, Optional
import torch
from torch import nn
from sglang.multimodal_gen.runtime.layers.quantization.configs.nunchaku_config import (
NunchakuConfig,
_patch_nunchaku_scales,
)
from sglang.multimodal_gen.runtime.loader.utils import _list_safetensors_files
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 maybe_download_model
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
from sglang.multimodal_gen.runtime.utils.quantization_utils import (
build_nvfp4_config_from_safetensors_list,
get_metadata_from_safetensors_file,
get_quant_config,
get_quant_config_from_safetensors_metadata,
)
from sglang.multimodal_gen.utils import PRECISION_TO_TYPE
from sglang.srt.layers.quantization import QuantizationConfig
logger = init_logger(__name__)
PostLoadHook = Callable[[nn.Module], None]
@dataclass
class TransformerQuantLoadSpec:
"""Resolved loading plan for a transformer checkpoint."""
safetensors_list: list[str]
quant_config: Optional[QuantizationConfig]
nunchaku_config: Optional[NunchakuConfig]
param_dtype: Optional[torch.dtype]
post_load_hooks: list[PostLoadHook] = field(default_factory=list)
@property
def runtime_quant_config(self) -> Optional[object]:
if self.quant_config is not None:
return self.quant_config
return self.nunchaku_config
class _TransformerQuantAdapter:
def prepare(self) -> None:
"""initialize"""
pass
def get_post_load_hooks(self) -> list[PostLoadHook]:
"""post - fsdp load - hook"""
return []
class _NunchakuQuantAdapter(_TransformerQuantAdapter):
"""Adapter for Nunchaku checkpoints"""
def __init__(
self,
*,
nunchaku_config: NunchakuConfig,
model_cls: type[nn.Module],
safetensors_list: list[str],
) -> None:
self.nunchaku_config = nunchaku_config
self.model_cls = model_cls
self.safetensors_list = safetensors_list
@staticmethod
def _validate_nunchaku_checkpoint_matches_model(
nunchaku_config: NunchakuConfig, model_cls: type[nn.Module]
) -> None:
metadata = get_metadata_from_safetensors_file(
nunchaku_config.transformer_weights_path
)
original_dit_cls_name = json.loads(metadata.get("config"))["_class_name"]
specified_dit_cls_name = str(model_cls.__name__)
if original_dit_cls_name != specified_dit_cls_name:
raise Exception(
f"Class name of DiT specified in nunchaku transformer_weights_path: "
f"{original_dit_cls_name} does not match that of specified DiT name: "
f"{specified_dit_cls_name}"
)
def prepare(self) -> None:
self.nunchaku_config.model_cls = self.model_cls
_NunchakuQuantAdapter._validate_nunchaku_checkpoint_matches_model(
nunchaku_config=self.nunchaku_config,
model_cls=self.model_cls,
)
def get_post_load_hooks(self) -> list[PostLoadHook]:
return [partial(_patch_nunchaku_scales, safetensors_list=self.safetensors_list)]
class _Flux2Nvfp4FallbackAdapter(_TransformerQuantAdapter):
"""Adapter for black-forest-labs/FLUX.2-dev-NVFP4"""
def __init__(
self,
*,
cls_name: str,
server_args: ServerArgs,
quant_config: Optional[QuantizationConfig],
) -> None:
self.cls_name = cls_name
self.server_args = server_args
self.quant_config = quant_config
@staticmethod
def _maybe_adjust_flux2_nvfp4_fallback_defaults(
cls_name: str,
server_args: ServerArgs,
quant_config: Optional[QuantizationConfig],
) -> None:
if cls_name != "Flux2Transformer2DModel" or quant_config is None:
return
quant_name_getter = getattr(type(quant_config), "get_name", None)
quant_name = quant_name_getter() if callable(quant_name_getter) else None
if quant_name != "modelopt_fp4":
return
use_best_perf_kit = getattr(
current_platform,
"should_use_modelopt_fp4_best_performance_kit",
None,
)
if callable(use_best_perf_kit) and use_best_perf_kit():
return
weights_path = os.path.basename(server_args.transformer_weights_path or "")
if not weights_path.endswith("-mixed.safetensors") or server_args.tp_size <= 1:
return
if server_args.dit_cpu_offload or server_args.text_encoder_cpu_offload:
server_args.dit_cpu_offload = False
server_args.text_encoder_cpu_offload = False
logger.warning(
"FLUX.2 mixed NVFP4 is using the generic ModelOpt FP4 fallback with "
"tp_size=%d; disabling dit/text-encoder CPU offload to avoid TP "
"all-gather launch failures. Override the offload flags explicitly if "
"you need the old behavior.",
server_args.tp_size,
)
def prepare(self) -> None:
_Flux2Nvfp4FallbackAdapter._maybe_adjust_flux2_nvfp4_fallback_defaults(
cls_name=self.cls_name,
server_args=self.server_args,
quant_config=self.quant_config,
)
def resolve_transformer_safetensors_to_load(
server_args: ServerArgs, component_model_path: str
) -> list[str]:
"""Resolve transformer weights from the base component path or an override."""
quantized_path = server_args.transformer_weights_path
if quantized_path:
quantized_path = maybe_download_model(quantized_path)
logger.info("using quantized transformer weights from: %s", quantized_path)
if os.path.isfile(quantized_path) and quantized_path.endswith(".safetensors"):
safetensors_list = [quantized_path]
else:
safetensors_list = _list_safetensors_files(quantized_path)
else:
safetensors_list = _list_safetensors_files(component_model_path)
if not safetensors_list:
raise ValueError(
f"no safetensors files found in {quantized_path or component_model_path}"
)
return safetensors_list
def resolve_transformer_quant_load_spec(
*,
hf_config: dict,
server_args: ServerArgs,
safetensors_list: list[str],
component_model_path: str,
model_cls: type[nn.Module],
cls_name: str,
) -> TransformerQuantLoadSpec:
quant_config = _resolve_quant_config(
hf_config=hf_config,
server_args=server_args,
safetensors_list=safetensors_list,
component_model_path=component_model_path,
)
nunchaku_config = server_args.nunchaku_config
# resolve target param dtype
param_dtype = _resolve_target_param_dtype(
quant_config=quant_config,
nunchaku_config=nunchaku_config,
server_args=server_args,
)
adapters = _build_transformer_quant_adapters(
cls_name=cls_name,
server_args=server_args,
quant_config=quant_config,
nunchaku_config=nunchaku_config,
model_cls=model_cls,
safetensors_list=safetensors_list,
)
for adapter in adapters:
adapter.prepare()
# collect post-load hooks from built adapters
post_load_hooks: list[PostLoadHook] = []
for adapter in adapters:
post_load_hooks.extend(adapter.get_post_load_hooks())
return TransformerQuantLoadSpec(
safetensors_list=safetensors_list,
quant_config=quant_config,
nunchaku_config=nunchaku_config,
param_dtype=param_dtype,
post_load_hooks=post_load_hooks,
)
def _build_transformer_quant_adapters(
*,
cls_name: str,
server_args: ServerArgs,
quant_config: Optional[QuantizationConfig],
nunchaku_config: Optional[NunchakuConfig],
model_cls: type[nn.Module],
safetensors_list: list[str],
) -> list[_TransformerQuantAdapter]:
adapters: list[_TransformerQuantAdapter] = [
_Flux2Nvfp4FallbackAdapter(
cls_name=cls_name,
server_args=server_args,
quant_config=quant_config,
)
]
if nunchaku_config is not None:
adapters.append(
_NunchakuQuantAdapter(
nunchaku_config=nunchaku_config,
model_cls=model_cls,
safetensors_list=safetensors_list,
)
)
return adapters
def _resolve_quant_config(
*,
hf_config: dict,
server_args: ServerArgs,
safetensors_list: list[str],
component_model_path: str,
) -> Optional[QuantizationConfig]:
"""
resolve quant config from checkpoints' metadata
priority: model config.json -> safetensors metadata -> format-specific fallback
"""
quant_config = get_quant_config(hf_config, component_model_path)
if quant_config is None and server_args.transformer_weights_path:
for safetensors_file in safetensors_list:
quant_config = get_quant_config_from_safetensors_metadata(safetensors_file)
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
def _resolve_target_param_dtype(
*,
quant_config: Optional[QuantizationConfig],
nunchaku_config: Optional[NunchakuConfig],
server_args: ServerArgs,
) -> Optional[torch.dtype]:
if quant_config is not None or nunchaku_config is not None:
return None
return PRECISION_TO_TYPE[server_args.pipeline_config.dit_precision]
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import glob import glob
import os import os
from dataclasses import dataclass
from functools import lru_cache from functools import lru_cache
from typing import Any, cast from typing import Any, cast
@@ -15,40 +15,85 @@ from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
logger = init_logger(__name__) logger = init_logger(__name__)
@dataclass(frozen=True)
class Flux2Nvfp4ModelResolution:
base_model_name: str
base_model_path: str
transformer_weights_path: str
_FLUX2_BASE_MODEL = "black-forest-labs/FLUX.2-dev" _FLUX2_BASE_MODEL = "black-forest-labs/FLUX.2-dev"
def _find_mixed_safetensors(local_dir: str) -> str | None:
"""Return the path to the *-mixed.safetensors file in a directory, or None."""
mixed_files = sorted(glob.glob(os.path.join(local_dir, "*-mixed.safetensors")))
return mixed_files[0] if mixed_files else None
@lru_cache(maxsize=1) @lru_cache(maxsize=1)
def _resolve_flux2_base_model_path() -> str: def _resolve_flux2_base_model_path() -> str:
# The NVFP4 repo only provides the quantized transformer weights.
# We still load model_index.json and the non-transformer components from the base repo.
return maybe_download_model(_FLUX2_BASE_MODEL, force_diffusers_model=True) return maybe_download_model(_FLUX2_BASE_MODEL, force_diffusers_model=True)
def _find_mixed_safetensors(local_dir: str) -> str | None:
mixed_files = sorted(glob.glob(os.path.join(local_dir, "*-mixed.safetensors")))
return mixed_files[0] if mixed_files else None
def _resolve_nvfp4_transformer_weights_path(
server_args: ServerArgs, model_path: str
) -> str:
if server_args.transformer_weights_path is not None:
return server_args.transformer_weights_path
local_nvfp4_path = maybe_download_model(model_path)
mixed_file = _find_mixed_safetensors(local_nvfp4_path)
if mixed_file is not None:
logger.info("Using mixed-precision NVFP4 weights: %s", mixed_file)
return mixed_file
logger.warning(
"No *-mixed.safetensors found in %s; falling back to full directory",
local_nvfp4_path,
)
return local_nvfp4_path
def resolve_flux2_nvfp4_model(
server_args: ServerArgs, model_path: str
) -> Flux2Nvfp4ModelResolution:
transformer_weights_path = _resolve_nvfp4_transformer_weights_path(
server_args, model_path
)
return Flux2Nvfp4ModelResolution(
base_model_name=_FLUX2_BASE_MODEL,
base_model_path=_resolve_flux2_base_model_path(),
transformer_weights_path=transformer_weights_path,
)
class Flux2NvfpPipeline(Flux2Pipeline): class Flux2NvfpPipeline(Flux2Pipeline):
pipeline_name = "Flux2NvfpPipeline" pipeline_name = "Flux2NvfpPipeline"
_base_model_path: str | None = None _model_resolution: Flux2Nvfp4ModelResolution | None = None
def _get_base_model_path(self) -> str: def _get_model_resolution(
if self._base_model_path is None: self, server_args: ServerArgs | None = None
self._base_model_path = _resolve_flux2_base_model_path() ) -> Flux2Nvfp4ModelResolution:
return self._base_model_path if self._model_resolution is None:
if server_args is None:
raise ValueError(
"server_args is required to resolve FLUX.2 NVFP4 paths"
)
self._model_resolution = resolve_flux2_nvfp4_model(
server_args, self.model_path
)
return self._model_resolution
def _load_config(self) -> dict[str, Any]: def _load_config(self) -> dict[str, Any]:
base_model_path = self._get_base_model_path() model_resolution = self._get_model_resolution(self.server_args)
logger.info("Model path: %s", self.model_path) logger.info("Model path: %s", self.model_path)
logger.info( logger.info(
"Using base model '%s' at %s for config and non-transformer components", "Using base model '%s' at %s for config and non-transformer components",
_FLUX2_BASE_MODEL, model_resolution.base_model_name,
base_model_path, model_resolution.base_model_path,
) )
config = verify_model_config_and_directory(base_model_path) config = verify_model_config_and_directory(model_resolution.base_model_path)
return cast(dict[str, Any], config) return cast(dict[str, Any], config)
def _resolve_component_path( def _resolve_component_path(
@@ -63,7 +108,7 @@ class Flux2NvfpPipeline(Flux2Pipeline):
# transformer weights: ...FLUX.2-dev-NVFP4/.../flux2-dev-nvfp4-mixed.safetensors # transformer weights: ...FLUX.2-dev-NVFP4/.../flux2-dev-nvfp4-mixed.safetensors
# text_encoder path: ...FLUX.2-dev/.../text_encoder # text_encoder path: ...FLUX.2-dev/.../text_encoder
component_model_path = os.path.join( component_model_path = os.path.join(
self._get_base_model_path(), load_module_name self._get_model_resolution(server_args).base_model_path, load_module_name
) )
logger.debug("Resolved component path: %s", component_model_path) logger.debug("Resolved component path: %s", component_model_path)
return component_model_path return component_model_path
@@ -73,21 +118,11 @@ class Flux2NvfpPipeline(Flux2Pipeline):
server_args: ServerArgs, server_args: ServerArgs,
loaded_modules: dict | None = None, loaded_modules: dict | None = None,
) -> dict: ) -> dict:
if server_args.transformer_weights_path is None: model_resolution = self._get_model_resolution(server_args)
local_nvfp4_path = maybe_download_model(self.model_path) server_args.transformer_weights_path = model_resolution.transformer_weights_path
mixed_file = _find_mixed_safetensors(local_nvfp4_path)
if mixed_file:
logger.info("Using mixed-precision NVFP4 weights: %s", mixed_file)
server_args.transformer_weights_path = mixed_file
else:
logger.warning(
"No *-mixed.safetensors found in %s; falling back to full directory",
local_nvfp4_path,
)
server_args.transformer_weights_path = local_nvfp4_path
logger.info( logger.info(
"NVFP4 transformer weights: %s", server_args.transformer_weights_path "NVFP4 transformer weights: %s",
model_resolution.transformer_weights_path,
) )
return super().load_modules(server_args, loaded_modules) return super().load_modules(server_args, loaded_modules)
@@ -189,7 +189,7 @@ class CudaPlatformBase(Platform):
logger.warning( logger.warning(
"best performance kit (comfy-kitchen) is not installed. " "best performance kit (comfy-kitchen) is not installed. "
"Blackwell NVFP4 will fall back to the generic ModelOpt FP4 path. " "Blackwell NVFP4 will fall back to the generic ModelOpt FP4 path. "
"Install it with `pip install comfy-kitchen`." "Install it with `pip install comfy-kitchen[cublas]`."
) )
@classmethod @classmethod
@@ -22,7 +22,7 @@ import yaml
from sglang.multimodal_gen import envs from sglang.multimodal_gen import envs
from sglang.multimodal_gen.configs.models.encoders import T5Config from sglang.multimodal_gen.configs.models.encoders import T5Config
from sglang.multimodal_gen.configs.pipeline_configs.base import PipelineConfig from sglang.multimodal_gen.configs.pipeline_configs.base import PipelineConfig
from sglang.multimodal_gen.configs.quantization import NunchakuSVDQuantArgs from sglang.multimodal_gen.configs.quantization.nunchaku import NunchakuSVDQuantArgs
from sglang.multimodal_gen.runtime.layers.quantization.configs.nunchaku_config import ( from sglang.multimodal_gen.runtime.layers.quantization.configs.nunchaku_config import (
NunchakuConfig, NunchakuConfig,
) )
@@ -275,27 +275,20 @@ class ServerArgs:
self.input_save_path = None self.input_save_path = None
def _adjust_quant_config(self): def _adjust_quant_config(self):
"""validate and adjust""" """
resolve, validate and adjust quantization config
handles only nunchaku for now
"""
# nunchaku
ncfg = self.nunchaku_config ncfg = self.nunchaku_config
if ncfg is None or isinstance(ncfg, NunchakuConfig): if ncfg is None or isinstance(ncfg, NunchakuConfig):
return return
ncfg.validate()
# propagate the path to server_args resolution = ncfg.resolve_runtime_config()
if ncfg.transformer_weights_path: if resolution.transformer_weights_path:
self.transformer_weights_path = ncfg.transformer_weights_path self.transformer_weights_path = resolution.transformer_weights_path
self.nunchaku_config = resolution.nunchaku_config
if not ncfg.enable_svdquant or not ncfg.transformer_weights_path:
self.nunchaku_config = None
else:
self.nunchaku_config = NunchakuConfig(
precision=self.nunchaku_config.quantization_precision,
rank=self.nunchaku_config.quantization_rank,
act_unsigned=self.nunchaku_config.quantization_act_unsigned,
transformer_weights_path=self.nunchaku_config.transformer_weights_path,
)
def adjust_pipeline_config(self): def adjust_pipeline_config(self):
# enable parallel folding when SP is enabled # enable parallel folding when SP is enabled