diff --git a/backend/backend.proto b/backend/backend.proto index 5d926b644e41..5639ab8ae654 100644 --- a/backend/backend.proto +++ b/backend/backend.proto @@ -498,6 +498,7 @@ message ModelOptions { // applied verbatim to the backend's engine constructor (e.g. vLLM AsyncEngineArgs). // Unknown keys produce an error at LoadModel time. string EngineArgs = 73; + string OriginalConfigFile = 76; // Proxy carries the cloud-proxy backend's per-model configuration. // Empty for non-proxy backends. diff --git a/backend/python/diffusers/backend.py b/backend/python/diffusers/backend.py index cb43c88c085b..bb15a2c3f45d 100755 --- a/backend/python/diffusers/backend.py +++ b/backend/python/diffusers/backend.py @@ -35,6 +35,7 @@ get_available_pipelines, load_diffusers_pipeline, ) +from load_options import single_file_load_kwargs # Import specific items still needed for special cases and safety checker from diffusers import DiffusionPipeline, ControlNetModel @@ -464,6 +465,9 @@ def _load_pipeline(self, request, model_ref, from_single_file, local_only, torch # Build kwargs for dynamic loading load_kwargs = {"torch_dtype": torchType} + load_kwargs.update( + single_file_load_kwargs(request.OriginalConfigFile, from_single_file) + ) # Add variant if not loading from single file if not from_single_file and variant: diff --git a/backend/python/diffusers/load_options.py b/backend/python/diffusers/load_options.py new file mode 100644 index 000000000000..fba08fc53974 --- /dev/null +++ b/backend/python/diffusers/load_options.py @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: MIT + + +def single_file_load_kwargs(original_config_file: str, from_single_file: bool) -> dict: + if from_single_file and original_config_file: + return {"original_config_file": original_config_file} + return {} diff --git a/backend/python/diffusers/test_load_options.py b/backend/python/diffusers/test_load_options.py new file mode 100644 index 000000000000..286b388366f2 --- /dev/null +++ b/backend/python/diffusers/test_load_options.py @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: MIT + +import unittest + +from backend.python.diffusers.load_options import single_file_load_kwargs + + +class SingleFileLoadKwargsTest(unittest.TestCase): + def test_includes_original_config_for_single_file(self): + self.assertEqual( + single_file_load_kwargs("configs/v1-inference.yaml", True), + {"original_config_file": "configs/v1-inference.yaml"}, + ) + + def test_omits_original_config_for_pretrained_model(self): + self.assertEqual( + single_file_load_kwargs("configs/v1-inference.yaml", False), + {}, + ) + + def test_omits_empty_original_config(self): + self.assertEqual(single_file_load_kwargs("", True), {}) + + +if __name__ == "__main__": + unittest.main() diff --git a/core/backend/options.go b/core/backend/options.go index c7525fee8bd8..df33ca9d2866 100644 --- a/core/backend/options.go +++ b/core/backend/options.go @@ -425,6 +425,7 @@ func grpcModelOpts(c config.ModelConfig, modelPath string) *pb.ModelOptions { EnableScore: c.HasUsecases(config.FLAG_SCORE), CLIPSkip: int32(c.Diffusers.ClipSkip), ControlNet: c.Diffusers.ControlNet, + OriginalConfigFile: c.Diffusers.OriginalConfigFile, ContextSize: int32(ctxSize), Seed: getSeed(c), NBatch: int32(b), diff --git a/core/backend/options_internal_test.go b/core/backend/options_internal_test.go index 19e62504c58c..18e081fb1ef1 100644 --- a/core/backend/options_internal_test.go +++ b/core/backend/options_internal_test.go @@ -44,6 +44,21 @@ var _ = Describe("grpcModelOpts EngineArgs", func() { }) }) +var _ = Describe("grpcModelOpts Diffusers options", func() { + It("forwards original_config_file without rewriting it", func() { + threads := 1 + cfg := config.ModelConfig{ + Threads: &threads, + Diffusers: config.Diffusers{ + OriginalConfigFile: "configs/v1-inference.yaml", + }, + } + + opts := grpcModelOpts(cfg, "/tmp/models") + Expect(opts.OriginalConfigFile).To(Equal("configs/v1-inference.yaml")) + }) +}) + // Guards the DisableReasoning -> enable_thinking metadata conversion that the // per-request reasoning_effort feature (issue #10072) relies on: the request // merge sets ReasoningConfig.DisableReasoning, and gRPCPredictOpts is where it diff --git a/core/config/meta/registry.go b/core/config/meta/registry.go index f386bd1a5fef..efcc96ab0bf0 100644 --- a/core/config/meta/registry.go +++ b/core/config/meta/registry.go @@ -793,11 +793,19 @@ func DefaultRegistry() map[string]FieldMetaOverride { Options: DiffusersSchedulerOptions, Order: 81, }, + "diffusers.original_config_file": { + Section: "diffusers", + Label: "Original Config File", + Description: "Original model configuration file used when loading a single-file checkpoint", + Component: "input", + Advanced: true, + Order: 82, + }, "diffusers.cuda": { Section: "diffusers", Label: "CUDA", Description: "Enable CUDA for diffusers", - Order: 82, + Order: 83, }, // --- PII filtering (per-model) --- diff --git a/core/config/model_config.go b/core/config/model_config.go index 21bc42facc63..351e92f5afb9 100644 --- a/core/config/model_config.go +++ b/core/config/model_config.go @@ -1104,15 +1104,16 @@ type GRPC struct { // @Description Diffusers configuration type Diffusers struct { - CUDA bool `yaml:"cuda,omitempty" json:"cuda,omitempty"` - PipelineType string `yaml:"pipeline_type,omitempty" json:"pipeline_type,omitempty"` - SchedulerType string `yaml:"scheduler_type,omitempty" json:"scheduler_type,omitempty"` - EnableParameters string `yaml:"enable_parameters,omitempty" json:"enable_parameters,omitempty"` // A list of comma separated parameters to specify - IMG2IMG bool `yaml:"img2img,omitempty" json:"img2img,omitempty"` // Image to Image Diffuser - ClipSkip int `yaml:"clip_skip,omitempty" json:"clip_skip,omitempty"` // Skip every N frames - ClipModel string `yaml:"clip_model,omitempty" json:"clip_model,omitempty"` // Clip model to use - ClipSubFolder string `yaml:"clip_subfolder,omitempty" json:"clip_subfolder,omitempty"` // Subfolder to use for clip model - ControlNet string `yaml:"control_net,omitempty" json:"control_net,omitempty"` + CUDA bool `yaml:"cuda,omitempty" json:"cuda,omitempty"` + PipelineType string `yaml:"pipeline_type,omitempty" json:"pipeline_type,omitempty"` + SchedulerType string `yaml:"scheduler_type,omitempty" json:"scheduler_type,omitempty"` + OriginalConfigFile string `yaml:"original_config_file,omitempty" json:"original_config_file,omitempty"` + EnableParameters string `yaml:"enable_parameters,omitempty" json:"enable_parameters,omitempty"` // A list of comma separated parameters to specify + IMG2IMG bool `yaml:"img2img,omitempty" json:"img2img,omitempty"` // Image to Image Diffuser + ClipSkip int `yaml:"clip_skip,omitempty" json:"clip_skip,omitempty"` // Skip every N frames + ClipModel string `yaml:"clip_model,omitempty" json:"clip_model,omitempty"` // Clip model to use + ClipSubFolder string `yaml:"clip_subfolder,omitempty" json:"clip_subfolder,omitempty"` // Subfolder to use for clip model + ControlNet string `yaml:"control_net,omitempty" json:"control_net,omitempty"` } // @Description LLMConfig is a struct that holds the configuration that are generic for most of the LLM backends. diff --git a/docs/content/advanced/model-configuration.md b/docs/content/advanced/model-configuration.md index a162a6225319..91e78f01f918 100644 --- a/docs/content/advanced/model-configuration.md +++ b/docs/content/advanced/model-configuration.md @@ -715,6 +715,7 @@ For image generation models using the `diffusers` backend: | `diffusers.cuda` | bool | Enable CUDA for diffusers | | `diffusers.pipeline_type` | string | Pipeline type (e.g., `stable-diffusion`, `stable-diffusion-xl`) | | `diffusers.scheduler_type` | string | Scheduler type (e.g., `euler`, `ddpm`) | +| `diffusers.original_config_file` | string | Local path or URL to the original configuration for loading a single-file checkpoint | | `diffusers.enable_parameters` | string | Comma-separated parameters to enable | | `diffusers.cfg_scale` | float32 | Classifier-free guidance scale | | `diffusers.img2img` | bool | Enable image-to-image transformation | diff --git a/docs/content/features/image-generation.md b/docs/content/features/image-generation.md index aca97b2363ed..b6cf9718a06a 100644 --- a/docs/content/features/image-generation.md +++ b/docs/content/features/image-generation.md @@ -224,6 +224,18 @@ diffusers: cfg_scale: 8 ``` +For an offline single-file checkpoint that needs its original Diffusers configuration, keep both files in the mounted models directory: + +```yaml +name: offline-stable-diffusion +parameters: + model: model.safetensors +backend: diffusers +diffusers: + pipeline_type: StableDiffusionPipeline + original_config_file: /models/v1-inference.yaml +``` + #### Configuration parameters The following parameters are available in the configuration file: