[diffusion] feat: add composable component weight path cli (#36078)

This commit is contained in:
Mick
2026-08-24 09:31:08 +08:00
committed by GitHub
parent 1c1c9d9b4e
commit fee00a41db
3 changed files with 105 additions and 38 deletions
+26 -13
View File
@@ -122,20 +122,33 @@ For frame interpolation and upscaling, see [Post-Processing](./post_processing).
### Quantization ### Quantization
For a pre-quantized main transformer checkpoint, prefer:
- `--model-path` for the base pipeline
- `--transformer-path` for a quantized `transformers` transformer component folder
- `--transformer-weights-path` for a quantized safetensors file, directory,
repo, or a supported GGUF transformer file
- `--quantization` to override the quantization method used by the transformer loader
- `--quantization-ignored-layers` transformer layer name patterns to keep unquantized during online quantization (e.g. `attention.to_`)
Component checkpoint paths are selected separately, so changing DiT precision Component checkpoint paths are selected separately, so changing DiT precision
never silently changes prompt embeddings. Use never silently changes prompt embeddings. The component-scoped forms are the
`--component-paths.<component> {MODEL}` or its shorter canonical interface for any component key from `model_index.json` or a native
`--<component>-path {MODEL}` form for any component key from `model_index.json` pipeline's registered module name:
or a native pipeline's registered module name.
| Intent | Canonical option | Convenience alias | Behavior |
| --- | --- | --- | --- |
| Replace a component | `--component-paths.<component> {MODEL}` | `--<component>-path {MODEL}` | Load the replacement component's configuration and weights |
| Replace only its weights | `--component-weights-paths.<component> {WEIGHTS}` | `--<component>-weights-path {WEIGHTS}` | Retain the base component configuration and replace its weights |
For example, pair a replacement text-encoder configuration with a separate
single-file checkpoint as follows:
```bash
--component-paths.text_encoder Qwen/Qwen3-VL-4B-Instruct \
--component-weights-paths.text_encoder \
Comfy-Org/Krea-2/text_encoders/qwen3vl_4b_fp8_scaled.safetensors
```
The transformer-specific `--transformer-weights-path` spelling remains
supported for the primary DiT. Do not mechanically replace it with
`--component-weights-paths.transformer`: the component-scoped form requires an
actual component name, which is pipeline-specific. Use `--quantization` only
to override the method inferred by the transformer loader, and
`--quantization-ignored-layers` to keep matching transformer layers
unquantized during online quantization.
For a native text encoder: For a native text encoder:
- `--component-paths.text_encoder {MODEL}` replaces the text-encoder checkpoint; `--text-encoder-path {MODEL}` is its shorter alias - `--component-paths.text_encoder {MODEL}` replaces the text-encoder checkpoint; `--text-encoder-path {MODEL}` is its shorter alias
@@ -2760,38 +2760,38 @@ class ServerArgs(DisaggServerArgsMixin):
) )
@staticmethod @staticmethod
def _extract_component_paths( def _extract_dynamic_component_map(
unknown_args: list[str], unknown_args: list[str],
*,
option_prefixes: tuple[str, ...],
alias_suffix: str,
) -> tuple[dict[str, str], list[str]]: ) -> tuple[dict[str, str], list[str]]:
""" component_values: dict[str, str] = {}
Extract dynamic component path args from unrecognised CLI args.
Supported forms:
- ``--<component>-path /path/to/component``
- ``--component-paths.<component> /path/to/component`` (expanded from config)
"""
component_paths: dict[str, str] = {}
remaining: list[str] = [] remaining: list[str] = []
i = 0 i = 0
while i < len(unknown_args): while i < len(unknown_args):
arg = unknown_args[i] arg = unknown_args[i]
key_part = arg.split("=", 1)[0] if "=" in arg else arg key_part = arg.split("=", 1)[0] if "=" in arg else arg
component = None component = None
if key_part.startswith("--component-paths."): for option_prefix in option_prefixes:
component = key_part[len("--component-paths.") :].replace("-", "_") if key_part.startswith(option_prefix):
elif key_part.startswith("--component_paths."): component = key_part[len(option_prefix) :].replace("-", "_")
component = key_part[len("--component_paths.") :].replace("-", "_") break
elif key_part.startswith("--") and key_part.endswith("-path"): if (
component = key_part[2:-5].replace("-", "_") component is None
and key_part.startswith("--")
and key_part.endswith(alias_suffix)
):
component = key_part[2 : -len(alias_suffix)].replace("-", "_")
if component is not None: if component is not None:
if "=" in arg: if "=" in arg:
component_paths[component] = arg.split("=", 1)[1] component_values[component] = arg.split("=", 1)[1]
elif i + 1 < len(unknown_args) and not unknown_args[i + 1].startswith( elif i + 1 < len(unknown_args) and not unknown_args[i + 1].startswith(
"-" "-"
): ):
i += 1 i += 1
component_paths[component] = unknown_args[i] component_values[component] = unknown_args[i]
else: else:
remaining.append(arg) remaining.append(arg)
i += 1 i += 1
@@ -2800,11 +2800,37 @@ class ServerArgs(DisaggServerArgsMixin):
remaining.append(arg) remaining.append(arg)
i += 1 i += 1
# canonicalize and validate return {
for component, path in component_paths.items(): component: os.path.expanduser(value)
path = os.path.expanduser(path) for component, value in component_values.items()
component_paths[component] = path }, remaining
return component_paths, remaining
@classmethod
def _extract_component_paths(
cls,
unknown_args: list[str],
) -> tuple[dict[str, str], list[str]]:
"""Extract dynamic component configuration paths and aliases."""
return cls._extract_dynamic_component_map(
unknown_args,
option_prefixes=("--component-paths.", "--component_paths."),
alias_suffix="-path",
)
@classmethod
def _extract_component_weights_paths(
cls,
unknown_args: list[str],
) -> tuple[dict[str, str], list[str]]:
"""Extract dynamic component weight-file paths and aliases."""
return cls._extract_dynamic_component_map(
unknown_args,
option_prefixes=(
"--component-weights-paths.",
"--component_weights_paths.",
),
alias_suffix="-weights-path",
)
@staticmethod @staticmethod
def _extract_component_attention_backends( def _extract_component_attention_backends(
@@ -2853,8 +2879,11 @@ class ServerArgs(DisaggServerArgsMixin):
if unknown_args is None: if unknown_args is None:
unknown_args = [] unknown_args = []
# extract dynamic --<component>-path from unknown args # Extract the more specific weights suffix before the generic path alias.
dynamic_paths, remaining = cls._extract_component_paths(unknown_args) dynamic_weights_paths, remaining = cls._extract_component_weights_paths(
unknown_args
)
dynamic_paths, remaining = cls._extract_component_paths(remaining)
dynamic_attention_backends, remaining = ( dynamic_attention_backends, remaining = (
cls._extract_component_attention_backends(remaining) cls._extract_component_attention_backends(remaining)
) )
@@ -2880,6 +2909,11 @@ class ServerArgs(DisaggServerArgsMixin):
existing.update(dynamic_paths) existing.update(dynamic_paths)
provided_args["component_paths"] = existing provided_args["component_paths"] = existing
explicit_arg_names.add("component_paths") explicit_arg_names.add("component_paths")
if dynamic_weights_paths:
existing = dict(provided_args.get("component_weights_paths") or {})
existing.update(dynamic_weights_paths)
provided_args["component_weights_paths"] = existing
explicit_arg_names.add("component_weights_paths")
if dynamic_attention_backends: if dynamic_attention_backends:
existing = cls._parse_component_attention_backend_map( existing = cls._parse_component_attention_backend_map(
provided_args.get("component_attention_backends") provided_args.get("component_attention_backends")
@@ -464,7 +464,16 @@ class TestServerArgsPathExpansion(unittest.TestCase):
) )
with tempfile.NamedTemporaryFile("w", suffix=".json") as config_file: with tempfile.NamedTemporaryFile("w", suffix=".json") as config_file:
json.dump({"model_path": "/from/config", "num_gpus": 2}, config_file) json.dump(
{
"model_path": "/from/config",
"num_gpus": 2,
"component_weights_paths": {
"transformer": "owner/repo/transformer.safetensors"
},
},
config_file,
)
config_file.flush() config_file.flush()
parser = FlexibleArgumentParser() parser = FlexibleArgumentParser()
add_multimodal_gen_serve_args(parser) add_multimodal_gen_serve_args(parser)
@@ -475,6 +484,9 @@ class TestServerArgsPathExpansion(unittest.TestCase):
"/from/cli", "/from/cli",
"--vae-path", "--vae-path",
"/custom/vae", "/custom/vae",
"--component-weights-paths.text_encoder",
"owner/repo/text_encoder.safetensors",
"--image-encoder-weights-path=/custom/image_encoder.safetensors",
"--component-attention-backends.transformer", "--component-attention-backends.transformer",
"fa3", "fa3",
] ]
@@ -505,6 +517,14 @@ class TestServerArgsPathExpansion(unittest.TestCase):
self.assertEqual("/from/cli", server_args.model_path) self.assertEqual("/from/cli", server_args.model_path)
self.assertEqual(2, server_args.num_gpus) self.assertEqual(2, server_args.num_gpus)
self.assertEqual("/custom/vae", server_args.component_paths["vae"]) self.assertEqual("/custom/vae", server_args.component_paths["vae"])
self.assertEqual(
{
"transformer": "owner/repo/transformer.safetensors",
"text_encoder": "owner/repo/text_encoder.safetensors",
"image_encoder": "/custom/image_encoder.safetensors",
},
server_args.component_weights_paths,
)
self.assertEqual( self.assertEqual(
{"transformer": "fa"}, {"transformer": "fa"},
server_args.component_attention_backends, server_args.component_attention_backends,