diff --git a/backend/backend.proto b/backend/backend.proto index c658fb53c..94c160583 100644 --- a/backend/backend.proto +++ b/backend/backend.proto @@ -499,6 +499,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; // EnvVars carries environment variables to be passed to the backend process. map EnvVars = 76; diff --git a/backend/python/diffusers/backend.py b/backend/python/diffusers/backend.py index 312754a34..d226a0ac8 100755 --- a/backend/python/diffusers/backend.py +++ b/backend/python/diffusers/backend.py @@ -35,6 +35,7 @@ from diffusers_dynamic_loader import ( 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 @@ -479,6 +480,9 @@ class BackendServicer(backend_pb2_grpc.BackendServicer): # 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 000000000..fba08fc53 --- /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 000000000..286b38836 --- /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 815328378..3bec4a892 100644 --- a/core/backend/options.go +++ b/core/backend/options.go @@ -456,6 +456,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 19e62504c..18e081fb1 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/model_config.go b/core/config/model_config.go index ce8a4d1bf..3612ff972 100644 --- a/core/config/model_config.go +++ b/core/config/model_config.go @@ -1244,15 +1244,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 eb70cf736..c24fd2327 100644 --- a/docs/content/advanced/model-configuration.md +++ b/docs/content/advanced/model-configuration.md @@ -757,6 +757,7 @@ For image generation models using the `diffusers` backend: | `diffusers.cuda` | bool | Force CUDA. By default the backend auto-detects and uses CUDA when a compatible GPU is present (ROCm builds included). Pin the CPU with `options: ["device:cpu"]` | | `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 1df6058b4..e690486b6 100644 --- a/docs/content/features/image-generation.md +++ b/docs/content/features/image-generation.md @@ -248,6 +248,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: