[diffusion] feat: support mixed w4a4 and int8 checkpoints (#36040)
This commit is contained in:
@@ -311,6 +311,8 @@ current `comfy-kitchen`, then pass a full or pruned FL2VA / Ref2VA file such as
|
|||||||
`Merserk/MiniMax-H3-INT4-ConvRot/minimax_h3_fl2va_pruned_int4_convrot.safetensors`
|
`Merserk/MiniMax-H3-INT4-ConvRot/minimax_h3_fl2va_pruned_int4_convrot.safetensors`
|
||||||
to `--transformer-weights-path`. The packed weights stay INT4 and the runtime
|
to `--transformer-weights-path`. The packed weights stay INT4 and the runtime
|
||||||
honors each layer's activation mode; omit `--quantization`.
|
honors each layer's activation mode; omit `--quantization`.
|
||||||
|
Mixed exports use the same command: SGLang dispatches each marked layer to its
|
||||||
|
serialized W4A4 or INT8 ConvRot kernel instead of applying one global method.
|
||||||
|
|
||||||
### Advanced: precomputed AdaLN cache
|
### Advanced: precomputed AdaLN cache
|
||||||
|
|
||||||
|
|||||||
@@ -202,11 +202,11 @@ backend.
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>comfy-w4a4-convrot</code></td>
|
<td><code>comfy-w4a4-convrot</code></td>
|
||||||
<td>Safetensors with serialized <code>convrot_w4a4</code> layer metadata</td>
|
<td>Safetensors with serialized <code>convrot_w4a4</code> metadata, optionally mixed with <code>int8_tensorwise</code> layers</td>
|
||||||
<td><code>--transformer-weights-path</code> or <code>--component-paths.text_encoder</code></td>
|
<td><code>--transformer-weights-path</code> or <code>--component-paths.text_encoder</code></td>
|
||||||
<td>Native DiTs and encoders with matching parameter mappings; MiniMax-H3 FL2VA / Ref2VA DiTs and Qwen3-VL encoder layouts are recognized</td>
|
<td>Native DiTs and encoders with matching parameter mappings; MiniMax-H3 FL2VA / Ref2VA DiTs and Qwen3-VL encoder layouts are recognized</td>
|
||||||
<td><code>comfy-kitchen</code></td>
|
<td><code>comfy-kitchen</code></td>
|
||||||
<td>Auto-detected; omit <code>--quantization</code>. Packed INT4 weights use the checkpoint's W4A4 ConvRot kernel and <code>linear_dtype</code>. CUDA requires SM75+; TP must preserve the 64-element quantization and ConvRot group boundaries. Offload is supported and FSDP is not.</td>
|
<td>Auto-detected; omit <code>--quantization</code>. Each layer dispatches to its serialized W4A4 or INT8 ConvRot kernel. CUDA requires SM75+; TP must preserve each format's quantization and ConvRot group boundaries. Offload is supported and FSDP is not.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>quanto-int8</code></td>
|
<td><code>quanto-int8</code></td>
|
||||||
|
|||||||
+27
-3
@@ -15,6 +15,9 @@ from sglang.multimodal_gen.runtime.layers.quantization.configs.base_config impor
|
|||||||
QuantizationConfig,
|
QuantizationConfig,
|
||||||
QuantizeMethodBase,
|
QuantizeMethodBase,
|
||||||
)
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.layers.quantization.configs.kitchen_int8_config import (
|
||||||
|
KitchenInt8Config,
|
||||||
|
)
|
||||||
from sglang.multimodal_gen.runtime.layers.quantization.kitchen_w4a4 import (
|
from sglang.multimodal_gen.runtime.layers.quantization.kitchen_w4a4 import (
|
||||||
KitchenW4A4LinearMethod,
|
KitchenW4A4LinearMethod,
|
||||||
)
|
)
|
||||||
@@ -26,7 +29,7 @@ _SUPPORTED_LINEAR_DTYPES = ("int4", "int8")
|
|||||||
|
|
||||||
|
|
||||||
class KitchenW4A4Config(QuantizationConfig):
|
class KitchenW4A4Config(QuantizationConfig):
|
||||||
"""Dispatch linears carrying serialized ``convrot_w4a4`` markers."""
|
"""Dispatch serialized W4A4 linears and their optional INT8 companions."""
|
||||||
|
|
||||||
def __init__(self, layer_markers: dict[str, dict[str, Any]]) -> None:
|
def __init__(self, layer_markers: dict[str, dict[str, Any]]) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -46,12 +49,23 @@ class KitchenW4A4Config(QuantizationConfig):
|
|||||||
self.layer_markers = layer_markers
|
self.layer_markers = layer_markers
|
||||||
self.checkpoint_uses_native_qkv_layout = True
|
self.checkpoint_uses_native_qkv_layout = True
|
||||||
self.selected: list[str] = []
|
self.selected: list[str] = []
|
||||||
|
int8_markers = {
|
||||||
|
prefix: marker
|
||||||
|
for prefix, marker in layer_markers.items()
|
||||||
|
if marker.get("format") == "int8_tensorwise"
|
||||||
|
}
|
||||||
|
self._int8_config = (
|
||||||
|
KitchenInt8Config(layer_markers=int8_markers) if int8_markers else None
|
||||||
|
)
|
||||||
|
|
||||||
for prefix, marker in layer_markers.items():
|
for prefix, marker in layer_markers.items():
|
||||||
if marker.get("format") != "convrot_w4a4":
|
marker_format = marker.get("format")
|
||||||
|
if marker_format == "int8_tensorwise":
|
||||||
|
continue
|
||||||
|
if marker_format != "convrot_w4a4":
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Unsupported Comfy W4A4 format for {prefix!r}: "
|
f"Unsupported Comfy W4A4 format for {prefix!r}: "
|
||||||
f"{marker.get('format')!r}"
|
f"{marker_format!r}"
|
||||||
)
|
)
|
||||||
self._parse_marker(prefix, marker)
|
self._parse_marker(prefix, marker)
|
||||||
|
|
||||||
@@ -86,6 +100,11 @@ class KitchenW4A4Config(QuantizationConfig):
|
|||||||
marker = self.layer_markers.get(prefix)
|
marker = self.layer_markers.get(prefix)
|
||||||
if marker is None:
|
if marker is None:
|
||||||
return UnquantizedLinearMethod()
|
return UnquantizedLinearMethod()
|
||||||
|
if marker.get("format") == "int8_tensorwise":
|
||||||
|
assert self._int8_config is not None
|
||||||
|
method = self._int8_config.get_quant_method(layer, prefix)
|
||||||
|
self.selected.append(prefix)
|
||||||
|
return method
|
||||||
|
|
||||||
convrot_group_size, linear_dtype = self._parse_marker(prefix, marker)
|
convrot_group_size, linear_dtype = self._parse_marker(prefix, marker)
|
||||||
if not self._supports_input_size(layer.input_size, convrot_group_size):
|
if not self._supports_input_size(layer.input_size, convrot_group_size):
|
||||||
@@ -130,6 +149,11 @@ class KitchenW4A4Config(QuantizationConfig):
|
|||||||
marker = self.layer_markers.get(prefix)
|
marker = self.layer_markers.get(prefix)
|
||||||
if marker is None:
|
if marker is None:
|
||||||
return True
|
return True
|
||||||
|
if marker.get("format") == "int8_tensorwise":
|
||||||
|
assert self._int8_config is not None
|
||||||
|
return self._int8_config.supports_input_partition(
|
||||||
|
prefix, input_size_per_partition
|
||||||
|
)
|
||||||
convrot_group_size, _ = self._parse_marker(prefix, marker)
|
convrot_group_size, _ = self._parse_marker(prefix, marker)
|
||||||
return self._supports_input_size(input_size_per_partition, convrot_group_size)
|
return self._supports_input_size(input_size_per_partition, convrot_group_size)
|
||||||
|
|
||||||
|
|||||||
@@ -288,6 +288,8 @@ def resolve_comfy_checkpoint_quantization(
|
|||||||
return KitchenW4A8Config(layer_markers)
|
return KitchenW4A8Config(layer_markers)
|
||||||
if formats == ["convrot_w4a4"]:
|
if formats == ["convrot_w4a4"]:
|
||||||
return KitchenW4A4Config(layer_markers)
|
return KitchenW4A4Config(layer_markers)
|
||||||
|
if formats == ["convrot_w4a4", "int8_tensorwise"]:
|
||||||
|
return KitchenW4A4Config(layer_markers)
|
||||||
if formats == ["float8_e4m3fn"]:
|
if formats == ["float8_e4m3fn"]:
|
||||||
return ComfyFp8Config(layer_markers)
|
return ComfyFp8Config(layer_markers)
|
||||||
if formats == ["mxfp8"]:
|
if formats == ["mxfp8"]:
|
||||||
|
|||||||
@@ -519,6 +519,53 @@ class TestTransformerQuantHelpers(unittest.TestCase):
|
|||||||
self.assertEqual(layer.weight_scale.shape, (3,))
|
self.assertEqual(layer.weight_scale.shape, (3,))
|
||||||
self.assertEqual(layer.weight_scale.dtype, torch.float32)
|
self.assertEqual(layer.weight_scale.dtype, torch.float32)
|
||||||
|
|
||||||
|
def test_mixed_w4a4_int8_dispatches_each_serialized_layer(self):
|
||||||
|
markers = {
|
||||||
|
"w4a4": {
|
||||||
|
"format": "convrot_w4a4",
|
||||||
|
"convrot_groupsize": 256,
|
||||||
|
"linear_dtype": "int8",
|
||||||
|
},
|
||||||
|
"int8": {
|
||||||
|
"format": "int8_tensorwise",
|
||||||
|
"convrot": True,
|
||||||
|
"convrot_groupsize": 256,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"sglang.multimodal_gen.runtime.layers.quantization.kitchen_w4a4."
|
||||||
|
"convrot_w4a4_linear",
|
||||||
|
new=object(),
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"sglang.multimodal_gen.runtime.layers.quantization.kitchen_int8."
|
||||||
|
"_load_comfy_kitchen"
|
||||||
|
),
|
||||||
|
):
|
||||||
|
config = resolve_minimax_h3_checkpoint_quantization(markers)
|
||||||
|
w4a4 = ReplicatedLinear(
|
||||||
|
256,
|
||||||
|
3,
|
||||||
|
bias=False,
|
||||||
|
params_dtype=torch.bfloat16,
|
||||||
|
quant_config=config,
|
||||||
|
prefix="w4a4",
|
||||||
|
)
|
||||||
|
int8 = ReplicatedLinear(
|
||||||
|
256,
|
||||||
|
3,
|
||||||
|
bias=False,
|
||||||
|
params_dtype=torch.bfloat16,
|
||||||
|
quant_config=config,
|
||||||
|
prefix="int8",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsInstance(config, KitchenW4A4Config)
|
||||||
|
self.assertEqual(w4a4.weight.shape, (3, 128))
|
||||||
|
self.assertEqual(int8.weight.shape, (3, 256))
|
||||||
|
self.assertEqual(set(config.selected), {"w4a4", "int8"})
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
"sglang.multimodal_gen.runtime.layers.quantization.kitchen_int8."
|
"sglang.multimodal_gen.runtime.layers.quantization.kitchen_int8."
|
||||||
"_load_comfy_kitchen"
|
"_load_comfy_kitchen"
|
||||||
|
|||||||
Reference in New Issue
Block a user