[diffusion] feat: delegate recognized quantized components to transformers (#36902)
This commit is contained in:
@@ -19,6 +19,7 @@ from transformers import (
|
||||
AutoTokenizer,
|
||||
PretrainedConfig,
|
||||
)
|
||||
from transformers.quantizers import AutoHfQuantizer
|
||||
|
||||
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
|
||||
from sglang.multimodal_gen.runtime.layers.attention.selector import (
|
||||
@@ -32,6 +33,7 @@ from sglang.multimodal_gen.runtime.loader.utils import (
|
||||
get_memory_usage_of_component,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_residency import (
|
||||
RESIDENT,
|
||||
ComponentResidencyError,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_residency_strategies import (
|
||||
@@ -65,15 +67,15 @@ class NativeComponentLoaderRequired(RuntimeError):
|
||||
"""The customized loader must defer to the native library loader."""
|
||||
|
||||
|
||||
def uses_native_transformers_bnb4(config: object, component_name: str) -> bool:
|
||||
"""Validate a serialized BnB4 checkpoint owned by Transformers."""
|
||||
def uses_native_transformers_quantization(config: object, component_name: str) -> bool:
|
||||
"""Validate quantization metadata that Transformers can restore itself."""
|
||||
try:
|
||||
quant_spec = resolve_checkpoint_quant_spec(config)
|
||||
except (TypeError, ValueError) as error:
|
||||
raise ComponentCheckpointUnsupportedError(
|
||||
f"Cannot parse checkpoint quantization for {component_name!r}: {error}"
|
||||
) from error
|
||||
if quant_spec is None or quant_spec.declared_method != "bitsandbytes":
|
||||
if quant_spec is None:
|
||||
return False
|
||||
if quant_spec.source != "quantization_config":
|
||||
raise ComponentCheckpointUnsupportedError(
|
||||
@@ -82,16 +84,18 @@ def uses_native_transformers_bnb4(config: object, component_name: str) -> bool:
|
||||
f"got metadata from {quant_spec.source!r}"
|
||||
)
|
||||
|
||||
load_in_4bit = quant_spec.config.get(
|
||||
"load_in_4bit", quant_spec.config.get("_load_in_4bit")
|
||||
)
|
||||
load_in_8bit = quant_spec.config.get(
|
||||
"load_in_8bit", quant_spec.config.get("_load_in_8bit", False)
|
||||
)
|
||||
if load_in_4bit is not True or load_in_8bit is True:
|
||||
try:
|
||||
supported = AutoHfQuantizer.supports_quant_method(dict(quant_spec.config))
|
||||
except (TypeError, ValueError) as error:
|
||||
raise ComponentCheckpointUnsupportedError(
|
||||
f"Transformers-managed {component_name!r} quantization supports only "
|
||||
"serialized BitsAndBytes 4-bit checkpoints"
|
||||
f"Cannot configure Transformers-managed quantization for "
|
||||
f"{component_name!r}: {error}"
|
||||
) from error
|
||||
if not supported:
|
||||
method = quant_spec.declared_method or "unspecified"
|
||||
raise ComponentCheckpointUnsupportedError(
|
||||
f"Transformers does not support quant_method={method!r} declared by "
|
||||
f"{component_name!r}"
|
||||
)
|
||||
return True
|
||||
|
||||
@@ -147,6 +151,7 @@ class ComponentLoader(ABC):
|
||||
def __init__(self, device=None) -> None:
|
||||
self.device = device
|
||||
self.component_architecture: str | None = None
|
||||
self._native_load_manages_placement = False
|
||||
|
||||
@staticmethod
|
||||
def target_device(component_starts_on_cpu: bool) -> torch.device:
|
||||
@@ -230,6 +235,7 @@ class ComponentLoader(ABC):
|
||||
If all of the above methods failed, an error will be thrown
|
||||
|
||||
"""
|
||||
self._native_load_manages_placement = False
|
||||
component_quantization = server_args.component_quantizations.get(component_name)
|
||||
if (
|
||||
component_quantization is not None
|
||||
@@ -317,7 +323,10 @@ class ComponentLoader(ABC):
|
||||
else:
|
||||
if isinstance(component, nn.Module):
|
||||
component = component.eval()
|
||||
if not is_fsdp_managed_module(component):
|
||||
if (
|
||||
not is_fsdp_managed_module(component)
|
||||
and not self._native_load_manages_placement
|
||||
):
|
||||
component = component.to(
|
||||
self.target_device(
|
||||
server_args.should_start_component_on_cpu(component_name)
|
||||
@@ -356,16 +365,38 @@ class ComponentLoader(ABC):
|
||||
load_kwargs["torch_dtype"] = precision
|
||||
|
||||
if transformers_or_diffusers == "transformers":
|
||||
self._native_load_manages_placement = False
|
||||
config = get_hf_config(
|
||||
component_model_path,
|
||||
trust_remote_code=server_args.trust_remote_code,
|
||||
revision=server_args.revision,
|
||||
)
|
||||
if uses_native_transformers_bnb4(config, component_name or "component"):
|
||||
server_args.require_component_resident(
|
||||
component_name or "component",
|
||||
feature_name="Transformers bitsandbytes component",
|
||||
if uses_native_transformers_quantization(
|
||||
config, component_name or "component"
|
||||
):
|
||||
resolved_component_name = component_name or "component"
|
||||
explicit_residency = server_args.explicit_residency_mode(
|
||||
resolved_component_name
|
||||
)
|
||||
if explicit_residency is not None and explicit_residency != RESIDENT:
|
||||
raise ComponentCheckpointUnsupportedError(
|
||||
"Transformers-managed quantized component "
|
||||
f"{resolved_component_name!r} requires resident placement; "
|
||||
f"got explicit mode {explicit_residency!r}"
|
||||
)
|
||||
server_args.require_component_resident(
|
||||
resolved_component_name,
|
||||
feature_name="Transformers quantized component",
|
||||
)
|
||||
if server_args.should_use_fsdp_for_component(resolved_component_name):
|
||||
raise ComponentCheckpointUnsupportedError(
|
||||
"Transformers-managed quantized components do not support "
|
||||
"SGLang FSDP loading"
|
||||
)
|
||||
load_kwargs["device_map"] = {
|
||||
"": self.target_device(component_starts_on_cpu=False)
|
||||
}
|
||||
self._native_load_manages_placement = True
|
||||
model_class = self.resolve_native_transformers_model_class(config)
|
||||
return model_class.from_pretrained(
|
||||
component_model_path,
|
||||
|
||||
+25
-8
@@ -58,7 +58,7 @@ from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader imp
|
||||
ComponentCheckpointUnsupportedError,
|
||||
ComponentLoader,
|
||||
NativeComponentLoaderRequired,
|
||||
uses_native_transformers_bnb4,
|
||||
uses_native_transformers_quantization,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.loader.gguf_weights import (
|
||||
gguf_weights_iterator,
|
||||
@@ -135,15 +135,23 @@ _TRANSFORMERS_ENCODER_ONLY_CLASSES = {
|
||||
}
|
||||
|
||||
|
||||
def _delegate_standard_bnb4_to_transformers(
|
||||
def _delegate_quantized_checkpoint_to_transformers(
|
||||
component_config: dict,
|
||||
component_name: str,
|
||||
*,
|
||||
methods: frozenset[str] | None = None,
|
||||
) -> None:
|
||||
"""Use Transformers when it owns a standard serialized BnB4 checkpoint."""
|
||||
if uses_native_transformers_bnb4(component_config, component_name):
|
||||
"""Use Transformers when it owns the checkpoint's serialized format."""
|
||||
quant_spec = resolve_checkpoint_quant_spec(component_config)
|
||||
if quant_spec is None or (
|
||||
methods is not None and quant_spec.declared_method not in methods
|
||||
):
|
||||
return
|
||||
if uses_native_transformers_quantization(component_config, component_name):
|
||||
method = quant_spec.declared_method or "unspecified"
|
||||
raise NativeComponentLoaderRequired(
|
||||
f"{component_name!r} delegates serialized bitsandbytes checkpoint "
|
||||
"loading to Transformers"
|
||||
f"{component_name!r} delegates serialized quant_method={method!r} "
|
||||
"checkpoint loading to Transformers"
|
||||
)
|
||||
|
||||
|
||||
@@ -268,9 +276,10 @@ def _configure_encoder_quantization(
|
||||
# themselves; running the generic lifecycle as well would process twice.
|
||||
return
|
||||
|
||||
_delegate_standard_bnb4_to_transformers(
|
||||
_delegate_quantized_checkpoint_to_transformers(
|
||||
component_config,
|
||||
component_name,
|
||||
methods=frozenset({"bitsandbytes"}),
|
||||
)
|
||||
try:
|
||||
quant_config = _get_encoder_quant_config(
|
||||
@@ -280,6 +289,10 @@ def _configure_encoder_quantization(
|
||||
model_cls,
|
||||
)
|
||||
except (KeyError, NotImplementedError, TypeError, ValueError) as error:
|
||||
_delegate_quantized_checkpoint_to_transformers(
|
||||
component_config,
|
||||
component_name,
|
||||
)
|
||||
raise ComponentCheckpointUnsupportedError(
|
||||
f"Cannot configure checkpoint quantization for {component_name!r}: {error}"
|
||||
) from error
|
||||
@@ -305,6 +318,10 @@ def _configure_encoder_quantization(
|
||||
)
|
||||
quant_config = model_config.quant_config
|
||||
if quant_config is None:
|
||||
_delegate_quantized_checkpoint_to_transformers(
|
||||
component_config,
|
||||
component_name,
|
||||
)
|
||||
return
|
||||
if not issubclass(model_cls, EncoderTensorParallelMixin):
|
||||
raise ComponentCheckpointUnsupportedError(
|
||||
@@ -327,7 +344,7 @@ def _resolve_and_configure_encoder_quantization(
|
||||
try:
|
||||
model_cls, _ = ModelRegistry.resolve_model_cls(architectures)
|
||||
except Exception as resolution_error:
|
||||
_delegate_standard_bnb4_to_transformers(
|
||||
_delegate_quantized_checkpoint_to_transformers(
|
||||
component_config,
|
||||
component_name,
|
||||
)
|
||||
|
||||
@@ -3,10 +3,12 @@ from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
|
||||
from sglang.multimodal_gen.configs.models.encoders.clip import CLIPVisionConfig
|
||||
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
|
||||
ComponentCheckpointUnsupportedError,
|
||||
NativeComponentLoaderRequired,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.loader.component_loaders.image_encoder_loader import (
|
||||
ImageEncoderLoader,
|
||||
@@ -14,6 +16,9 @@ from sglang.multimodal_gen.runtime.loader.component_loaders.image_encoder_loader
|
||||
from sglang.multimodal_gen.runtime.loader.component_loaders.text_encoder_loader import (
|
||||
_configure_encoder_quantization,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_residency import (
|
||||
COMPONENT_OFFLOAD,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.encoders.clip import CLIPVisionModel
|
||||
from sglang.srt.layers.quantization.fp8 import Fp8Config as SRTFp8Config
|
||||
|
||||
@@ -76,8 +81,26 @@ class TestImageEncoderQuantizationAdmission(unittest.TestCase):
|
||||
{"qkv_proj": ["q_proj", "k_proj", "v_proj"]},
|
||||
)
|
||||
|
||||
def test_unknown_quantized_architecture_does_not_fall_back(self):
|
||||
def test_unknown_transformers_quantized_architecture_falls_back(self):
|
||||
config = self._component_config("UnknownVisionModel", quantized=True)
|
||||
with self._config_patch(config):
|
||||
self._load()
|
||||
self.load_native.assert_called_once()
|
||||
|
||||
def test_native_only_quantized_architecture_does_not_fall_back(self):
|
||||
self.server_args.pipeline_config.native_only_components = ("image_encoder",)
|
||||
config = self._component_config("UnknownVisionModel", quantized=True)
|
||||
with self._config_patch(config), self.assertRaises(
|
||||
NativeComponentLoaderRequired
|
||||
):
|
||||
self._load()
|
||||
self.load_native.assert_not_called()
|
||||
|
||||
def test_unknown_unsupported_quantized_architecture_does_not_fall_back(self):
|
||||
config = {
|
||||
"architectures": ["UnknownVisionModel"],
|
||||
"quantization_config": {"quant_method": "not-a-format"},
|
||||
}
|
||||
with self._config_patch(config), self.assertRaises(
|
||||
ComponentCheckpointUnsupportedError
|
||||
):
|
||||
@@ -107,7 +130,9 @@ class TestImageEncoderNativeLoading(unittest.TestCase):
|
||||
)
|
||||
server_args = SimpleNamespace(
|
||||
pipeline_config=SimpleNamespace(image_encoder_precision="bf16"),
|
||||
explicit_residency_mode=mock.Mock(return_value=None),
|
||||
require_component_resident=mock.Mock(),
|
||||
should_use_fsdp_for_component=mock.Mock(return_value=False),
|
||||
revision=None,
|
||||
trust_remote_code=False,
|
||||
)
|
||||
@@ -121,6 +146,10 @@ class TestImageEncoderNativeLoading(unittest.TestCase):
|
||||
loader,
|
||||
"resolve_native_transformers_model_class",
|
||||
return_value=model_class,
|
||||
), mock.patch.object(
|
||||
loader,
|
||||
"target_device",
|
||||
return_value=torch.device("cuda:0"),
|
||||
):
|
||||
component = loader.load_native(
|
||||
"/model/image_encoder",
|
||||
@@ -132,7 +161,7 @@ class TestImageEncoderNativeLoading(unittest.TestCase):
|
||||
self.assertIs(component, loaded_encoder)
|
||||
server_args.require_component_resident.assert_called_once_with(
|
||||
"image_encoder",
|
||||
feature_name="Transformers bitsandbytes component",
|
||||
feature_name="Transformers quantized component",
|
||||
)
|
||||
model_class.from_pretrained.assert_called_once_with(
|
||||
"/model/image_encoder",
|
||||
@@ -140,4 +169,111 @@ class TestImageEncoderNativeLoading(unittest.TestCase):
|
||||
trust_remote_code=False,
|
||||
revision=None,
|
||||
torch_dtype=torch.bfloat16,
|
||||
device_map={"": torch.device("cuda:0")},
|
||||
)
|
||||
|
||||
def test_explicit_offload_is_rejected_before_transformers_load(self):
|
||||
component_config = SimpleNamespace(
|
||||
is_encoder_decoder=False,
|
||||
architectures=["ThirdPartyVisionModel"],
|
||||
quantization_config={"quant_method": "fp8"},
|
||||
)
|
||||
model_class = SimpleNamespace(from_pretrained=mock.Mock())
|
||||
server_args = SimpleNamespace(
|
||||
pipeline_config=SimpleNamespace(image_encoder_precision="bf16"),
|
||||
explicit_residency_mode=mock.Mock(return_value=COMPONENT_OFFLOAD),
|
||||
require_component_resident=mock.Mock(),
|
||||
should_use_fsdp_for_component=mock.Mock(return_value=False),
|
||||
revision=None,
|
||||
trust_remote_code=False,
|
||||
)
|
||||
loader = ImageEncoderLoader()
|
||||
|
||||
with mock.patch(
|
||||
"sglang.multimodal_gen.runtime.loader.component_loaders."
|
||||
"component_loader.get_hf_config",
|
||||
return_value=component_config,
|
||||
), mock.patch.object(
|
||||
loader,
|
||||
"resolve_native_transformers_model_class",
|
||||
return_value=model_class,
|
||||
), self.assertRaisesRegex(
|
||||
ComponentCheckpointUnsupportedError, "requires resident placement"
|
||||
):
|
||||
loader.load_native(
|
||||
"/model/image_encoder",
|
||||
server_args,
|
||||
"transformers",
|
||||
"image_encoder",
|
||||
)
|
||||
|
||||
model_class.from_pretrained.assert_not_called()
|
||||
server_args.require_component_resident.assert_not_called()
|
||||
|
||||
def test_full_loader_does_not_move_quantized_component_again(self):
|
||||
class RejectMoveModule(nn.Module):
|
||||
def to(self, *args, **kwargs):
|
||||
raise AssertionError("quantized component must not be moved again")
|
||||
|
||||
component_config = SimpleNamespace(
|
||||
is_encoder_decoder=False,
|
||||
architectures=["ThirdPartyVisionModel"],
|
||||
quantization_config={"quant_method": "fp8"},
|
||||
)
|
||||
loaded_encoder = RejectMoveModule()
|
||||
model_class = SimpleNamespace(
|
||||
from_pretrained=mock.Mock(return_value=loaded_encoder)
|
||||
)
|
||||
server_args = SimpleNamespace(
|
||||
component_quantizations={},
|
||||
pipeline_config=SimpleNamespace(
|
||||
image_encoder_precision="bf16",
|
||||
native_only_components=(),
|
||||
),
|
||||
resolve_component_attention_backend=lambda _name: (None, None),
|
||||
explicit_residency_mode=lambda _name: None,
|
||||
require_component_resident=mock.Mock(),
|
||||
should_use_fsdp_for_component=lambda _name: False,
|
||||
should_start_component_on_cpu=lambda _name: False,
|
||||
revision=None,
|
||||
trust_remote_code=False,
|
||||
)
|
||||
loader = ImageEncoderLoader()
|
||||
|
||||
with mock.patch.object(
|
||||
loader,
|
||||
"load_customized",
|
||||
side_effect=NativeComponentLoaderRequired("use Transformers"),
|
||||
), mock.patch.object(
|
||||
loader,
|
||||
"resolve_native_transformers_model_class",
|
||||
return_value=model_class,
|
||||
), mock.patch.object(
|
||||
loader,
|
||||
"target_device",
|
||||
return_value=torch.device("cuda:0"),
|
||||
), mock.patch(
|
||||
"sglang.multimodal_gen.runtime.loader.component_loaders."
|
||||
"component_loader.get_hf_config",
|
||||
return_value=component_config,
|
||||
), mock.patch(
|
||||
"sglang.multimodal_gen.runtime.loader.component_loaders."
|
||||
"component_loader.current_platform.get_available_gpu_memory",
|
||||
return_value=10.0,
|
||||
), mock.patch(
|
||||
"sglang.multimodal_gen.runtime.loader.component_loaders."
|
||||
"component_loader.get_memory_usage_of_component",
|
||||
return_value=0.0,
|
||||
), mock.patch(
|
||||
"sglang.multimodal_gen.runtime.loader.component_loaders."
|
||||
"component_loader.format_component_residency",
|
||||
return_value="resident",
|
||||
):
|
||||
component, _ = loader.load(
|
||||
"/model/image_encoder",
|
||||
server_args,
|
||||
"image_encoder",
|
||||
"transformers",
|
||||
)
|
||||
|
||||
self.assertIs(component, loaded_encoder)
|
||||
|
||||
@@ -151,7 +151,9 @@ class TestTextEncoderClassResolution(unittest.TestCase):
|
||||
)
|
||||
server_args = SimpleNamespace(
|
||||
pipeline_config=SimpleNamespace(text_encoder_precisions=["bf16"]),
|
||||
explicit_residency_mode=mock.Mock(return_value=None),
|
||||
require_component_resident=mock.Mock(),
|
||||
should_use_fsdp_for_component=mock.Mock(return_value=False),
|
||||
revision=None,
|
||||
trust_remote_code=False,
|
||||
)
|
||||
@@ -162,16 +164,21 @@ class TestTextEncoderClassResolution(unittest.TestCase):
|
||||
}
|
||||
}
|
||||
|
||||
loader = TextEncoderLoader()
|
||||
with mock.patch.object(
|
||||
TextEncoderLoader,
|
||||
"resolve_native_transformers_model_class",
|
||||
return_value=transformers_model_class,
|
||||
), mock.patch.object(
|
||||
loader,
|
||||
"target_device",
|
||||
return_value=torch.device("cuda:0"),
|
||||
), mock.patch(
|
||||
"sglang.multimodal_gen.runtime.loader.component_loaders."
|
||||
"component_loader.get_hf_config",
|
||||
return_value=component_config,
|
||||
):
|
||||
encoder = TextEncoderLoader().load_native(
|
||||
encoder = loader.load_native(
|
||||
"/model/text_encoder",
|
||||
server_args,
|
||||
"transformers",
|
||||
@@ -181,7 +188,7 @@ class TestTextEncoderClassResolution(unittest.TestCase):
|
||||
self.assertIs(encoder, loaded_encoder)
|
||||
server_args.require_component_resident.assert_called_once_with(
|
||||
"text_encoder",
|
||||
feature_name="Transformers bitsandbytes component",
|
||||
feature_name="Transformers quantized component",
|
||||
)
|
||||
transformers_model_class.from_pretrained.assert_called_once_with(
|
||||
"/model/text_encoder",
|
||||
@@ -189,6 +196,7 @@ class TestTextEncoderClassResolution(unittest.TestCase):
|
||||
trust_remote_code=False,
|
||||
revision=None,
|
||||
torch_dtype=torch.bfloat16,
|
||||
device_map={"": torch.device("cuda:0")},
|
||||
)
|
||||
|
||||
|
||||
@@ -712,7 +720,7 @@ class TestTextEncoderQuantization(unittest.TestCase):
|
||||
):
|
||||
with self.subTest(architecture=architecture), self.assertRaisesRegex(
|
||||
NativeComponentLoaderRequired,
|
||||
"delegates serialized bitsandbytes checkpoint loading to Transformers",
|
||||
"delegates serialized quant_method='bitsandbytes' checkpoint",
|
||||
):
|
||||
_resolve_and_configure_encoder_quantization(
|
||||
SimpleNamespace(architectures=[architecture], quant_config=None),
|
||||
@@ -742,10 +750,10 @@ class TestTextEncoderQuantization(unittest.TestCase):
|
||||
"text_encoder",
|
||||
)
|
||||
|
||||
def test_rejects_bitsandbytes_8bit(self):
|
||||
def test_bitsandbytes_8bit_delegates_to_transformers(self):
|
||||
with self.assertRaisesRegex(
|
||||
ComponentCheckpointUnsupportedError,
|
||||
"supports only serialized BitsAndBytes 4-bit checkpoints",
|
||||
NativeComponentLoaderRequired,
|
||||
"delegates serialized quant_method='bitsandbytes' checkpoint",
|
||||
):
|
||||
_resolve_and_configure_encoder_quantization(
|
||||
SimpleNamespace(
|
||||
@@ -763,6 +771,26 @@ class TestTextEncoderQuantization(unittest.TestCase):
|
||||
"text_encoder",
|
||||
)
|
||||
|
||||
def test_unknown_fp8_architecture_delegates_to_transformers(self):
|
||||
with self.assertRaisesRegex(
|
||||
NativeComponentLoaderRequired,
|
||||
"delegates serialized quant_method='fp8' checkpoint",
|
||||
):
|
||||
_resolve_and_configure_encoder_quantization(
|
||||
SimpleNamespace(
|
||||
architectures=["ThirdPartyTextEncoder"], quant_config=None
|
||||
),
|
||||
{
|
||||
"quantization_config": {
|
||||
"quant_method": "fp8",
|
||||
"activation_scheme": "dynamic",
|
||||
}
|
||||
},
|
||||
"/model/text_encoder",
|
||||
"/model/text_encoder",
|
||||
"text_encoder",
|
||||
)
|
||||
|
||||
def test_model_managed_quantization_bypasses_generic_lifecycle(self):
|
||||
model_config = SimpleNamespace(quant_config=None)
|
||||
with mock.patch.object(
|
||||
|
||||
Reference in New Issue
Block a user