[diffusion] feat: add composable component weight path cli (#36078)
This commit is contained in:
@@ -122,20 +122,33 @@ For frame interpolation and upscaling, see [Post-Processing](./post_processing).
|
||||
|
||||
### 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
|
||||
never silently changes prompt embeddings. Use
|
||||
`--component-paths.<component> {MODEL}` or its shorter
|
||||
`--<component>-path {MODEL}` form for any component key from `model_index.json`
|
||||
or a native pipeline's registered module name.
|
||||
never silently changes prompt embeddings. The component-scoped forms are the
|
||||
canonical interface for any component key from `model_index.json` 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:
|
||||
|
||||
- `--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
|
||||
def _extract_component_paths(
|
||||
def _extract_dynamic_component_map(
|
||||
unknown_args: list[str],
|
||||
*,
|
||||
option_prefixes: tuple[str, ...],
|
||||
alias_suffix: str,
|
||||
) -> tuple[dict[str, str], list[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] = {}
|
||||
component_values: dict[str, str] = {}
|
||||
remaining: list[str] = []
|
||||
i = 0
|
||||
while i < len(unknown_args):
|
||||
arg = unknown_args[i]
|
||||
key_part = arg.split("=", 1)[0] if "=" in arg else arg
|
||||
component = None
|
||||
if key_part.startswith("--component-paths."):
|
||||
component = key_part[len("--component-paths.") :].replace("-", "_")
|
||||
elif key_part.startswith("--component_paths."):
|
||||
component = key_part[len("--component_paths.") :].replace("-", "_")
|
||||
elif key_part.startswith("--") and key_part.endswith("-path"):
|
||||
component = key_part[2:-5].replace("-", "_")
|
||||
for option_prefix in option_prefixes:
|
||||
if key_part.startswith(option_prefix):
|
||||
component = key_part[len(option_prefix) :].replace("-", "_")
|
||||
break
|
||||
if (
|
||||
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 "=" 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(
|
||||
"-"
|
||||
):
|
||||
i += 1
|
||||
component_paths[component] = unknown_args[i]
|
||||
component_values[component] = unknown_args[i]
|
||||
else:
|
||||
remaining.append(arg)
|
||||
i += 1
|
||||
@@ -2800,11 +2800,37 @@ class ServerArgs(DisaggServerArgsMixin):
|
||||
remaining.append(arg)
|
||||
i += 1
|
||||
|
||||
# canonicalize and validate
|
||||
for component, path in component_paths.items():
|
||||
path = os.path.expanduser(path)
|
||||
component_paths[component] = path
|
||||
return component_paths, remaining
|
||||
return {
|
||||
component: os.path.expanduser(value)
|
||||
for component, value in component_values.items()
|
||||
}, 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
|
||||
def _extract_component_attention_backends(
|
||||
@@ -2853,8 +2879,11 @@ class ServerArgs(DisaggServerArgsMixin):
|
||||
if unknown_args is None:
|
||||
unknown_args = []
|
||||
|
||||
# extract dynamic --<component>-path from unknown args
|
||||
dynamic_paths, remaining = cls._extract_component_paths(unknown_args)
|
||||
# Extract the more specific weights suffix before the generic path alias.
|
||||
dynamic_weights_paths, remaining = cls._extract_component_weights_paths(
|
||||
unknown_args
|
||||
)
|
||||
dynamic_paths, remaining = cls._extract_component_paths(remaining)
|
||||
dynamic_attention_backends, remaining = (
|
||||
cls._extract_component_attention_backends(remaining)
|
||||
)
|
||||
@@ -2880,6 +2909,11 @@ class ServerArgs(DisaggServerArgsMixin):
|
||||
existing.update(dynamic_paths)
|
||||
provided_args["component_paths"] = existing
|
||||
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:
|
||||
existing = cls._parse_component_attention_backend_map(
|
||||
provided_args.get("component_attention_backends")
|
||||
|
||||
@@ -464,7 +464,16 @@ class TestServerArgsPathExpansion(unittest.TestCase):
|
||||
)
|
||||
|
||||
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()
|
||||
parser = FlexibleArgumentParser()
|
||||
add_multimodal_gen_serve_args(parser)
|
||||
@@ -475,6 +484,9 @@ class TestServerArgsPathExpansion(unittest.TestCase):
|
||||
"/from/cli",
|
||||
"--vae-path",
|
||||
"/custom/vae",
|
||||
"--component-weights-paths.text_encoder",
|
||||
"owner/repo/text_encoder.safetensors",
|
||||
"--image-encoder-weights-path=/custom/image_encoder.safetensors",
|
||||
"--component-attention-backends.transformer",
|
||||
"fa3",
|
||||
]
|
||||
@@ -505,6 +517,14 @@ class TestServerArgsPathExpansion(unittest.TestCase):
|
||||
self.assertEqual("/from/cli", server_args.model_path)
|
||||
self.assertEqual(2, server_args.num_gpus)
|
||||
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(
|
||||
{"transformer": "fa"},
|
||||
server_args.component_attention_backends,
|
||||
|
||||
Reference in New Issue
Block a user