diff --git a/docs/docs/sglang-diffusion/api/cli.mdx b/docs/docs/sglang-diffusion/api/cli.mdx index 90e46ad5a..6d947bb96 100644 --- a/docs/docs/sglang-diffusion/api/cli.mdx +++ b/docs/docs/sglang-diffusion/api/cli.mdx @@ -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. {MODEL}` or its shorter -`---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. {MODEL}` | `---path {MODEL}` | Load the replacement component's configuration and weights | +| Replace only its weights | `--component-weights-paths. {WEIGHTS}` | `---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 diff --git a/python/sglang/multimodal_gen/runtime/server_args/server_args.py b/python/sglang/multimodal_gen/runtime/server_args/server_args.py index ca8821472..654430f43 100644 --- a/python/sglang/multimodal_gen/runtime/server_args/server_args.py +++ b/python/sglang/multimodal_gen/runtime/server_args/server_args.py @@ -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: - - ``---path /path/to/component`` - - ``--component-paths. /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 ---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") diff --git a/python/sglang/multimodal_gen/test/unit/test_server_args.py b/python/sglang/multimodal_gen/test/unit/test_server_args.py index 9fd2aa7d6..e8dc31a0d 100644 --- a/python/sglang/multimodal_gen/test/unit/test_server_args.py +++ b/python/sglang/multimodal_gen/test/unit/test_server_args.py @@ -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,