diff --git a/docs/image_preprocessing.md b/docs/image_preprocessing.md index 8ec236e7..c097e5a9 100644 --- a/docs/image_preprocessing.md +++ b/docs/image_preprocessing.md @@ -100,10 +100,13 @@ for individual references, identity images, or video control frames. --image-preprocess "target=control-frame,index=2,canny=true" ``` -Init and mask sources must have the same dimensions. The mask inherits the -init crop, resize, and padding coordinates, while retaining its own filter, -padding value, and Canny setting. Conflicting mask geometry is rejected. An -omitted mask remains absent until the original pipeline creates its default mask. +If init and mask source dimensions differ, the mask is first center-cropped to +the init source aspect ratio and resized to the init source dimensions using +nearest-exact interpolation. The mask then inherits the init crop, resize, and +padding coordinates, while retaining its own filter, padding value, and Canny +setting for that transform. Explicit mask geometry is evaluated after this +alignment; conflicting geometry is rejected. An omitted mask remains absent +until the original pipeline creates its default mask. ## Downstream behavior diff --git a/examples/common/media_io.cpp b/examples/common/media_io.cpp index 6c03a2a1..e878c889 100644 --- a/examples/common/media_io.cpp +++ b/examples/common/media_io.cpp @@ -1499,7 +1499,7 @@ bool load_wav_from_memory(const uint8_t* data, const size_t remaining = size - chunk_data_pos; // safe: chunk_data_pos <= size // Chunk sizes come from the payload, so compare against the remaining bytes // instead of adding them to offsets (avoids size_t wrap on 32-bit builds). - const bool data_fits = static_cast(chunk_size) <= remaining; + const bool data_fits = static_cast(chunk_size) <= remaining; if (std::memcmp(data + pos, "fmt ", 4) == 0) { if (chunk_size < 16 || !data_fits) { diff --git a/examples/server/README.md b/examples/server/README.md index 1bb49a59..8d8f687c 100644 --- a/examples/server/README.md +++ b/examples/server/README.md @@ -89,8 +89,8 @@ FLUX 1/2/3, Chroma, Qwen Image and editing variants; Z-Image, Anima, ERNIE, LENS, PiD and other supported image families; AnimateDiff, Wan, LTX, HunyuanVideo, LingBot Video and MiniMax-H3. Model-specific text encoder labels identify the expected encoder family and size. Loading a video model enables **Video Generation**; I2V also requires a -start image. The Models panel can be collapsed and remembers its open state. -Its header shows elapsed loading time from the status API and keeps the final +start image. The model button below the prompt opens model and recipe selection. +Its status summary shows elapsed loading time from the status API and keeps the final time after success or failure, including after a page refresh. Built-ins are embedded in the binary from [recipes/](recipes/), with one JSON file per recipe. Adding or diff --git a/examples/server/api.md b/examples/server/api.md index b5e0250c..02c83b5c 100644 --- a/examples/server/api.md +++ b/examples/server/api.md @@ -244,6 +244,7 @@ the same one-recipe-per-file schema: "id": "my-flux", "name": "My FLUX", "description": "FLUX.1 dev", + "size_multiple": 16, "model_url": "https://huggingface.co/black-forest-labs/FLUX.1-dev", "slots": [ {"id": "diffusion_model", "required": true}, @@ -259,7 +260,8 @@ the same one-recipe-per-file schema: IDs contain 1–128 ASCII letters, digits, dots, underscores or hyphens. `name`, `schema_version: 1` and `slots` are required; `description` and -`model_url`, `required_any`, `defaults`, `context`, `features` and `disabled_features` are optional. Each slot requires only `id` and boolean `required`. +`model_url`, `required_any`, `defaults`, `context`, `features`, `disabled_features` +and `size_multiple`, `video_constraints` are optional. Each slot requires only `id` and boolean `required`. The server supplies the default label and search directories for each slot. An optional nonempty `label` overrides the display name, for example `{"id": "diffusion_model", "label": "Low-noise diffusion model", "required": true}`. @@ -267,6 +269,45 @@ Legacy `category` fields remain accepted, but must match one of the slot's supported directories; they cannot redefine or restrict its search directories. At least one required `model` or `diffusion_model` slot is necessary. +`size_multiple` is a positive integer from 1 to 4096 that declares the width and +height increment used by the web UI. It defaults to `8` when omitted; the UI also +uses `8` before a recipe is selected. For example, `"size_multiple": 16` makes +generated size presets multiples of 16. Exact dimensions that do not match the +increment require correction before submission. This is recipe metadata, not a +generation parameter, and does not change the core model's alignment behavior. + +`video_constraints` is optional metadata for the web UI's Frames / Duration +controls. It does not override the runtime's model-specific rules. For example: + +```json +"video_constraints": { + "frame_step": 4, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 +} +``` + +Legal frame counts are `frame_step * n + frame_offset`, with integer `n >= 0`, +within the inclusive `min_frames` / `max_frames` range. All fields are optional: + +| Field | Default | Accepted values | +| --- | --- | --- | +| `frame_step` | `1` | Integer 1–4096 | +| `frame_offset` | `0` | Integer 0 to `frame_step - 1` | +| `frame_rounding` | `"floor"` | `"floor"` or `"ceil"` | +| `min_frames` | `1` | Integer 1–4096 | +| `max_frames` | `4096` | Integer 1–4096, at least `min_frames` | +| `fixed_fps` | Unset | Integer 1–240; locks the FPS control | + +The range must contain at least one legal frame count. Unknown keys are rejected. +Omitting the object adds no frame alignment in the UI. `defaults.fps` only sets +the initial FPS; it does not lock it. The UI multiplies requested duration by FPS, +aligns using the declared rounding direction, then clamps to the legal range. +Duration stays in the UI; generation requests still send only `video_frames` and +`fps`. Switching input modes preserves the aligned length. Changing FPS keeps +frames in Frames mode, and keeps the requested seconds in Duration mode. + `model_url` is an optional HTTPS Hugging Face model repository link, shown beside the recipe description. Built-ins link to the model's author/publisher, not repacks. `required_any` contains groups of declared slot IDs. At least one model in each @@ -1693,15 +1734,26 @@ When omitted, backend defaults apply to these fields: ### Frame Count Semantics -`video_frames` is the requested target length, but the current core video path internally normalizes the effective frame count to the largest `4n + 1` value that does not exceed the requested count. +`video_frames` is the requested target length. The core normalizes it according +to the loaded model: -Examples: +| Model family | Frame alignment | Fixed FPS | +| --- | --- | --- | +| Wan, HunyuanVideo, LingBot | `4n + 1`, round down | Wan 2.2 S2V: 16 | +| LTX | `8n + 1`, round down | None | +| MiniMax-H3 | `17n + 5`, round up, minimum 5 | 24 | +| AnimateDiff | Whole frames, maximum 32 | None | + +Wan examples: - `video_frames = 33` stays `33` - `video_frames = 34` becomes `33` - `video_frames = 32` becomes `29` -The completed job payload includes the actual decoded `frame_count`. +At 16 FPS, a 5-second target requests 80 frames and aligns to 77 frames for Wan, +or approximately 4.813 seconds. Audio-driven generation and temporal upscaling +can change the final length. Use the completed job's actual decoded `frame_count` +and `fps` for the final playback duration. ### Completion Result diff --git a/examples/server/frontend b/examples/server/frontend index 9157320a..fa93429c 160000 --- a/examples/server/frontend +++ b/examples/server/frontend @@ -1 +1 @@ -Subproject commit 9157320a3bdd6a345f370868a32f730c77749cb5 +Subproject commit fa93429c47c924054e6a9c14f8d7b8e06f57a0ec diff --git a/examples/server/model_catalog.cpp b/examples/server/model_catalog.cpp index 1041dea6..2617d600 100644 --- a/examples/server/model_catalog.cpp +++ b/examples/server/model_catalog.cpp @@ -369,20 +369,44 @@ json model_generation_features(const ServerRuntime& runtime) { if (runtime.models->active_recipe.value("source", "") == "command_line") return features; // Optional components only become usable once selected and loaded. - features["ip_adapter"] = features.value("ip_adapter", false) && !runtime.ctx_params->ip_adapter_path.empty(); - features["controlnet"] = features.value("controlnet", false) && !runtime.ctx_params->control_net_path.empty(); - features["high_noise"] = features.value("high_noise", false) && !runtime.ctx_params->high_noise_diffusion_model_path.empty(); - features["preview_tae"] = features.value("preview_tae", false) && !runtime.ctx_params->taesd_path.empty(); - features["ref_audios"] = features.value("ref_audios", false) && - (!runtime.ctx_params->audio_vae_path.empty() || !runtime.ctx_params->audio_encoder_path.empty()); + features["ip_adapter"] = features.value("ip_adapter", false) && !runtime.ctx_params->ip_adapter_path.empty(); + features["controlnet"] = features.value("controlnet", false) && !runtime.ctx_params->control_net_path.empty(); + features["high_noise"] = features.value("high_noise", false) && !runtime.ctx_params->high_noise_diffusion_model_path.empty(); + features["preview_tae"] = features.value("preview_tae", false) && !runtime.ctx_params->taesd_path.empty(); + features["ref_audios"] = features.value("ref_audios", false) && + (!runtime.ctx_params->audio_vae_path.empty() || !runtime.ctx_params->audio_encoder_path.empty()); features["ref_video_audios"] = features.value("ref_video_audios", false) && !runtime.ctx_params->audio_vae_path.empty(); if (!runtime.ctx_params->pulid_weights_path.empty()) features["slg"] = false; return features; } +static void validate_video_constraints(const json& constraints) { + check_keys(constraints, {"frame_step", "frame_offset", "frame_rounding", "min_frames", "max_frames", "fixed_fps"}); + for (const auto* key : {"frame_step", "frame_offset", "min_frames", "max_frames", "fixed_fps"}) { + const int minimum = std::string(key) == "frame_offset" ? 0 : 1; + const int maximum = std::string(key) == "fixed_fps" ? 240 : 4096; + if (constraints.contains(key) && + (!constraints[key].is_number_integer() || constraints[key] < minimum || constraints[key] > maximum)) { + throw std::runtime_error(std::string("video_constraints.") + key + " is outside its integer range"); + } + } + const int step = constraints.value("frame_step", 1); + const int offset = constraints.value("frame_offset", 0); + const int minimum = constraints.value("min_frames", 1); + const int maximum = constraints.value("max_frames", 4096); + if (offset >= step || minimum > maximum || + offset + std::max(0, (minimum - offset + step - 1) / step) * step > maximum) { + throw std::runtime_error("video_constraints must describe a nonempty frame range with frame_offset < frame_step"); + } + if (constraints.contains("frame_rounding") && + constraints["frame_rounding"] != "floor" && constraints["frame_rounding"] != "ceil") { + throw std::runtime_error("video_constraints.frame_rounding must be floor or ceil"); + } +} + static void validate_recipe(const json& recipe) { - check_keys(recipe, {"schema_version", "id", "name", "description", "model_url", "slots", "required_any", "defaults", "context", "features", "disabled_features"}); + check_keys(recipe, {"schema_version", "id", "name", "description", "model_url", "slots", "required_any", "defaults", "context", "features", "disabled_features", "size_multiple", "video_constraints"}); if (!recipe.at("schema_version").is_number_integer() || recipe.at("schema_version") != 1 || !valid_identifier(recipe.at("id").get()) || recipe.at("name").get().empty()) { throw std::runtime_error("invalid schema_version, id or name"); @@ -390,6 +414,13 @@ static void validate_recipe(const json& recipe) { if (recipe.contains("description") && !recipe["description"].is_string()) { throw std::runtime_error("description must be a string"); } + if (recipe.contains("size_multiple") && + (!recipe["size_multiple"].is_number_integer() || recipe["size_multiple"] < 1 || recipe["size_multiple"] > 4096)) { + throw std::runtime_error("size_multiple must be an integer from 1 to 4096"); + } + if (recipe.contains("video_constraints")) { + validate_video_constraints(recipe["video_constraints"]); + } if (recipe.contains("model_url")) { const auto url = recipe.at("model_url").get(); const std::string prefix = "https://huggingface.co/"; @@ -442,6 +473,7 @@ static void validate_recipe(const json& recipe) { static json normalize_recipe(json recipe) { validate_recipe(recipe); + recipe["size_multiple"] = recipe.value("size_multiple", 8); recipe["effective_features"] = resolve_recipe_features(recipe); for (auto& slot : recipe.at("slots")) { const auto& spec = *find_model_slot(slot.at("id").get()); @@ -605,6 +637,7 @@ void initialize_server_models(ServerRuntime& runtime) { {"description", "Uses the loaded command-line model and its generation defaults."}, {"slots", json::array()}, {"defaults", json::object()}, + {"size_multiple", 8}, {"features", features}, }; models.active_recipe["effective_features"] = resolve_recipe_features(models.active_recipe); diff --git a/examples/server/recipes/README.md b/examples/server/recipes/README.md index 5043f6d1..dcf9d69a 100644 --- a/examples/server/recipes/README.md +++ b/examples/server/recipes/README.md @@ -77,3 +77,35 @@ These are built-in catalog rules. Users may keep personal presets in `/recipes/`; they need not add those presets to the built-in list. See [the recipe schema and feature reference](../api.md#user-recipe-format) for supported fields. + +## Output dimension alignment + +Set `size_multiple` when the runtime's output dimension alignment differs from +the default of `8`. Match `GenerationRequest::align_image_size` in +[`src/pipeline/request.cpp`](../../../src/pipeline/request.cpp): the VAE scale +factor multiplied by the model definition's `diffusion_down_factor`. The scale +comes from [`VAE::get_scale_factor`](../../../src/model/vae/vae.hpp), with the +PiD override in [`diffusion_engine.cpp`](../../../src/pipeline/diffusion_engine.cpp); +model geometry is defined under [`src/model/integrations`](../../../src/model/integrations/). + +Use the runtime's output alignment, not the nominal patch size: some models pad +patches internally and crop their output back to the requested size. Pixel-space +models with an alignment of `1` explicitly use `size_multiple: 1`. MiniT2I also +has this alignment, but its separate fixed `512 × 512` output restriction still +applies. Reference-image preprocessing constraints are independent of this field. + +## Video length constraints + +Video recipes declare `video_constraints` to match `align_video_frames` in +[`diffusion_engine.cpp`](../../../src/pipeline/diffusion_engine.cpp) and the FPS +and AnimateDiff overrides in +[`request.cpp`](../../../src/pipeline/request.cpp). Wan, HunyuanVideo and LingBot +round down to `4n + 1`; LTX rounds down to `8n + 1`. MiniMax-H3 rounds up to +`17n + 5`, with at least 5 frames and fixed 24 FPS. AnimateDiff accepts whole +frames up to 32. Wan 2.2 S2V fixes FPS at 16. + +Declare fixed FPS only where the runtime enforces it; recommended FPS belongs +in `defaults.fps`. These constraints guide UI conversion and estimates, not +runtime behavior. Audio-driven output and temporal upscaling may still change +the final frame count. See the [recipe schema](../api.md#user-recipe-format) +for field defaults and validation rules. diff --git a/examples/server/recipes/animatediff-sd15.json b/examples/server/recipes/animatediff-sd15.json index a7c8338f..e7bf07d3 100644 --- a/examples/server/recipes/animatediff-sd15.json +++ b/examples/server/recipes/animatediff-sd15.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "animatediff-sd15", "name": "AnimateDiff SD 1.5", + "size_multiple": 64, "description": "Use Video Generation with an SD 1.5 checkpoint and compatible motion module.", "model_url": "https://huggingface.co/guoyww/animatediff", "slots": [ @@ -22,6 +23,13 @@ "required": false } ], + "video_constraints": { + "frame_step": 1, + "frame_offset": 0, + "frame_rounding": "floor", + "min_frames": 1, + "max_frames": 32 + }, "defaults": { "sampling_method": "euler", "video_frames": 8, diff --git a/examples/server/recipes/chroma-radiance.json b/examples/server/recipes/chroma-radiance.json index 6a0be8a5..2d35f434 100644 --- a/examples/server/recipes/chroma-radiance.json +++ b/examples/server/recipes/chroma-radiance.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "chroma-radiance", "name": "Chroma Radiance", + "size_multiple": 1, "description": "Pixel-space generation without a VAE. Also accepts the x0 and experimental patch-32 variants.", "model_url": "https://huggingface.co/lodestones/Chroma1-Radiance", "slots": [ diff --git a/examples/server/recipes/cosxl-edit.json b/examples/server/recipes/cosxl-edit.json index 49407fcc..eb794248 100644 --- a/examples/server/recipes/cosxl-edit.json +++ b/examples/server/recipes/cosxl-edit.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "cosxl-edit", "name": "CosXL Edit", + "size_multiple": 64, "description": "Supply the image to edit as a reference image.", "model_url": "https://huggingface.co/stabilityai/cosxl", "slots": [ diff --git a/examples/server/recipes/ernie-image-turbo.json b/examples/server/recipes/ernie-image-turbo.json index 5702f5ef..90ec37df 100644 --- a/examples/server/recipes/ernie-image-turbo.json +++ b/examples/server/recipes/ernie-image-turbo.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "ernie-image-turbo", "name": "ERNIE Image Turbo", + "size_multiple": 16, "model_url": "https://huggingface.co/baidu/ERNIE-Image-Turbo", "slots": [ { diff --git a/examples/server/recipes/ernie-image.json b/examples/server/recipes/ernie-image.json index a6100000..6373fed5 100644 --- a/examples/server/recipes/ernie-image.json +++ b/examples/server/recipes/ernie-image.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "ernie-image", "name": "ERNIE Image", + "size_multiple": 16, "model_url": "https://huggingface.co/baidu/ERNIE-Image", "slots": [ { diff --git a/examples/server/recipes/fibo2-dev.json b/examples/server/recipes/fibo2-dev.json index aa7e8146..061b9395 100644 --- a/examples/server/recipes/fibo2-dev.json +++ b/examples/server/recipes/fibo2-dev.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "fibo2-dev", "name": "Fibo 2.0 Dev", + "size_multiple": 16, "description": "Text-to-image generation and native reference-image editing. Select a separate vision projector for editing when it is not bundled in the text encoder.", "model_url": "https://huggingface.co/briaai/Fibo-2.0-dev", "slots": [ diff --git a/examples/server/recipes/fibo2-edit-turbo.json b/examples/server/recipes/fibo2-edit-turbo.json index b84e1cce..a78f5d36 100644 --- a/examples/server/recipes/fibo2-edit-turbo.json +++ b/examples/server/recipes/fibo2-edit-turbo.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "fibo2-edit-turbo", "name": "Fibo 2.0 Edit Turbo", + "size_multiple": 16, "description": "Dedicated reference-image editing checkpoint; supply reference images. Select a separate vision projector when it is not bundled in the text encoder.", "model_url": "https://huggingface.co/briaai/Fibo-2.0-edit-turbo", "slots": [ diff --git a/examples/server/recipes/flux2-dev.json b/examples/server/recipes/flux2-dev.json index 4d27ea16..12cf6af7 100644 --- a/examples/server/recipes/flux2-dev.json +++ b/examples/server/recipes/flux2-dev.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "flux2-dev", "name": "FLUX.2 dev", + "size_multiple": 16, "description": "Supports text-to-image and reference-image editing.", "model_url": "https://huggingface.co/black-forest-labs/FLUX.2-dev", "slots": [ diff --git a/examples/server/recipes/flux2-klein-4b.json b/examples/server/recipes/flux2-klein-4b.json index f09360cf..67281e1e 100644 --- a/examples/server/recipes/flux2-klein-4b.json +++ b/examples/server/recipes/flux2-klein-4b.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "flux2-klein-4b", "name": "FLUX.2 Klein 4B", + "size_multiple": 16, "description": "Supports reference-image editing. The FLUX.2 small decoder is an alternative VAE.", "model_url": "https://huggingface.co/black-forest-labs/FLUX.2-klein-4B", "slots": [ diff --git a/examples/server/recipes/flux2-klein-9b.json b/examples/server/recipes/flux2-klein-9b.json index ad10d6e5..1c8d50f6 100644 --- a/examples/server/recipes/flux2-klein-9b.json +++ b/examples/server/recipes/flux2-klein-9b.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "flux2-klein-9b", "name": "FLUX.2 Klein 9B", + "size_multiple": 16, "description": "Supports text-to-image and reference-image editing.", "model_url": "https://huggingface.co/black-forest-labs/FLUX.2-klein-9B", "slots": [ diff --git a/examples/server/recipes/flux2-klein-base-4b.json b/examples/server/recipes/flux2-klein-base-4b.json index 56c69f98..3fff7d96 100644 --- a/examples/server/recipes/flux2-klein-base-4b.json +++ b/examples/server/recipes/flux2-klein-base-4b.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "flux2-klein-base-4b", "name": "FLUX.2 Klein Base 4B", + "size_multiple": 16, "description": "Supports text-to-image and reference-image editing.", "model_url": "https://huggingface.co/black-forest-labs/FLUX.2-klein-base-4B", "slots": [ diff --git a/examples/server/recipes/flux2-klein-base-9b.json b/examples/server/recipes/flux2-klein-base-9b.json index e9bd53c6..b9a9eb6d 100644 --- a/examples/server/recipes/flux2-klein-base-9b.json +++ b/examples/server/recipes/flux2-klein-base-9b.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "flux2-klein-base-9b", "name": "FLUX.2 Klein Base 9B", + "size_multiple": 16, "description": "Use the 9B base transformer with Qwen 3 8B. Supports reference-image editing.", "model_url": "https://huggingface.co/black-forest-labs/FLUX.2-klein-base-9B", "slots": [ diff --git a/examples/server/recipes/flux3-klein-base.json b/examples/server/recipes/flux3-klein-base.json index 29f6f681..06789c2e 100644 --- a/examples/server/recipes/flux3-klein-base.json +++ b/examples/server/recipes/flux3-klein-base.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "flux3-klein-base", "name": "FLUX.3 Image Klein Base", + "size_multiple": 16, "model_url": "https://huggingface.co/black-forest-labs/flux-3-image-klein-base", "slots": [ { diff --git a/examples/server/recipes/flux3-klein.json b/examples/server/recipes/flux3-klein.json index 164bce12..e63314dd 100644 --- a/examples/server/recipes/flux3-klein.json +++ b/examples/server/recipes/flux3-klein.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "flux3-klein", "name": "FLUX.3 Image Klein", + "size_multiple": 16, "description": "Supports text-to-image and reference-image editing.", "model_url": "https://huggingface.co/black-forest-labs/flux-3-image-klein", "slots": [ diff --git a/examples/server/recipes/hidream-o1-dev.json b/examples/server/recipes/hidream-o1-dev.json index cf1c1790..b5c35921 100644 --- a/examples/server/recipes/hidream-o1-dev.json +++ b/examples/server/recipes/hidream-o1-dev.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "hidream-o1-dev", "name": "HiDream O1 Image dev", + "size_multiple": 1, "description": "Bundled checkpoint; also supports reference-image editing.", "model_url": "https://huggingface.co/HiDream-ai/HiDream-O1-Image-Dev", "slots": [ diff --git a/examples/server/recipes/hunyuan-video-15-i2v.json b/examples/server/recipes/hunyuan-video-15-i2v.json index a4d672f7..5dc0509f 100644 --- a/examples/server/recipes/hunyuan-video-15-i2v.json +++ b/examples/server/recipes/hunyuan-video-15-i2v.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "hunyuan-video-15-i2v", "name": "HunyuanVideo 1.5 I2V", + "size_multiple": 16, "description": "Select HunyuanVideo 1.5 I2V weights and supply a start image in Video Generation.", "model_url": "https://huggingface.co/tencent/HunyuanVideo-1.5", "slots": [ @@ -36,6 +37,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 4, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "sampling_method": "euler", "video_frames": 33, diff --git a/examples/server/recipes/hunyuan-video-15-t2v.json b/examples/server/recipes/hunyuan-video-15-t2v.json index d477b806..81aaceee 100644 --- a/examples/server/recipes/hunyuan-video-15-t2v.json +++ b/examples/server/recipes/hunyuan-video-15-t2v.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "hunyuan-video-15-t2v", "name": "HunyuanVideo 1.5 T2V", + "size_multiple": 16, "description": "Select HunyuanVideo 1.5 T2V weights and use Video Generation. Use the separate I2V recipe for I2V weights.", "model_url": "https://huggingface.co/tencent/HunyuanVideo-1.5", "slots": [ @@ -36,6 +37,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 4, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "sampling_method": "euler", "video_frames": 33, diff --git a/examples/server/recipes/ideogram4.json b/examples/server/recipes/ideogram4.json index 2d523001..133cf5f3 100644 --- a/examples/server/recipes/ideogram4.json +++ b/examples/server/recipes/ideogram4.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "ideogram4", "name": "Ideogram 4", + "size_multiple": 16, "description": "Select the matching unconditional transformer for classifier-free guidance.", "model_url": "https://huggingface.co/ideogram-ai/ideogram-4-fp8", "slots": [ diff --git a/examples/server/recipes/instruct-pix2pix.json b/examples/server/recipes/instruct-pix2pix.json index e8195091..e1f69a8a 100644 --- a/examples/server/recipes/instruct-pix2pix.json +++ b/examples/server/recipes/instruct-pix2pix.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "instruct-pix2pix", "name": "Instruct-Pix2Pix", + "size_multiple": 64, "description": "Supply the image to edit as a reference image.", "model_url": "https://huggingface.co/timbrooks/instruct-pix2pix", "slots": [ diff --git a/examples/server/recipes/iris-3b.json b/examples/server/recipes/iris-3b.json index c1b5d84f..96a617ef 100644 --- a/examples/server/recipes/iris-3b.json +++ b/examples/server/recipes/iris-3b.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "iris-3b", "name": "Iris 3B", + "size_multiple": 16, "description": "Pixel-space model; no VAE is required.", "model_url": "https://huggingface.co/speridlabs/iris-3b", "slots": [ diff --git a/examples/server/recipes/lens-turbo.json b/examples/server/recipes/lens-turbo.json index 2b87bb64..364320e7 100644 --- a/examples/server/recipes/lens-turbo.json +++ b/examples/server/recipes/lens-turbo.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "lens-turbo", "name": "LENS Turbo", + "size_multiple": 16, "model_url": "https://huggingface.co/microsoft/Lens-Turbo", "slots": [ { diff --git a/examples/server/recipes/lens.json b/examples/server/recipes/lens.json index 34165ad5..0e049c82 100644 --- a/examples/server/recipes/lens.json +++ b/examples/server/recipes/lens.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "lens", "name": "LENS", + "size_multiple": 16, "model_url": "https://huggingface.co/microsoft/Lens", "slots": [ { diff --git a/examples/server/recipes/lingbot-video-dense.json b/examples/server/recipes/lingbot-video-dense.json index 5f313a0c..3affb485 100644 --- a/examples/server/recipes/lingbot-video-dense.json +++ b/examples/server/recipes/lingbot-video-dense.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "lingbot-video-dense", "name": "LingBot Video Dense 1.3B", + "size_multiple": 16, "description": "Use Video Generation.", "model_url": "https://huggingface.co/robbyant/lingbot-video-dense-1.3b", "slots": [ @@ -30,6 +31,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 4, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "video_frames": 33, "cfg_scale": 3, diff --git a/examples/server/recipes/lingbot-video-moe.json b/examples/server/recipes/lingbot-video-moe.json index cdbce61e..55bf59f0 100644 --- a/examples/server/recipes/lingbot-video-moe.json +++ b/examples/server/recipes/lingbot-video-moe.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "lingbot-video-moe", "name": "LingBot Video MoE 30B A3B", + "size_multiple": 16, "description": "Use Video Generation.", "model_url": "https://huggingface.co/robbyant/lingbot-video-moe-30b-a3b", "slots": [ @@ -30,6 +31,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 4, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "video_frames": 33, "cfg_scale": 3, diff --git a/examples/server/recipes/llada-image-turbo.json b/examples/server/recipes/llada-image-turbo.json index 0589bb38..4965ddc9 100644 --- a/examples/server/recipes/llada-image-turbo.json +++ b/examples/server/recipes/llada-image-turbo.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "llada-image-turbo", "name": "LLaDA Image Turbo", + "size_multiple": 16, "description": "Use matching turbo text encoder and connectors. Editing needs connectors that include the SigVQ encoder.", "model_url": "https://huggingface.co/inclusionAI/LLaDA-Image-Turbo", "slots": [ diff --git a/examples/server/recipes/llada-image.json b/examples/server/recipes/llada-image.json index 27887c84..e16feda0 100644 --- a/examples/server/recipes/llada-image.json +++ b/examples/server/recipes/llada-image.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "llada-image", "name": "LLaDA Image (base)", + "size_multiple": 16, "description": "Use matching base transformer, text encoder and connectors; do not mix them with Turbo components. Editing needs connectors with SigVQ and 1024x1024 output.", "model_url": "https://huggingface.co/inclusionAI/LLaDA-Image", "slots": [ diff --git a/examples/server/recipes/ltx23-dev.json b/examples/server/recipes/ltx23-dev.json index 83bb934e..f0bf3f28 100644 --- a/examples/server/recipes/ltx23-dev.json +++ b/examples/server/recipes/ltx23-dev.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "ltx23-dev", "name": "LTX 2.3 dev", + "size_multiple": 32, "description": "Supports text, start-image and start/end-image video. Audio VAE is optional for silent video. Configure latent upscaling separately in generation settings. Optional distillation LoRAs use this recipe; select the LoRA and adjust the sampling settings separately.", "model_url": "https://huggingface.co/Lightricks/LTX-2.3", "slots": [ @@ -39,6 +40,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 8, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "sampling_method": "euler", "video_frames": 33, diff --git a/examples/server/recipes/ltx23-distilled.json b/examples/server/recipes/ltx23-distilled.json index a2a6ac33..3e4d08e9 100644 --- a/examples/server/recipes/ltx23-distilled.json +++ b/examples/server/recipes/ltx23-distilled.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "ltx23-distilled", "name": "LTX 2.3 Distilled 1.1", + "size_multiple": 32, "description": "Supports text, start-image and start/end-image video. Audio VAE is optional for silent video. Configure latent upscaling separately in generation settings.", "model_url": "https://huggingface.co/Lightricks/LTX-2.3", "slots": [ @@ -39,6 +40,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 8, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "sampling_method": "euler", "video_frames": 121, diff --git a/examples/server/recipes/ltx25-dev.json b/examples/server/recipes/ltx25-dev.json index 47efbc76..011a49ed 100644 --- a/examples/server/recipes/ltx25-dev.json +++ b/examples/server/recipes/ltx25-dev.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "ltx25-dev", "name": "LTX 2.5 dev", + "size_multiple": 32, "description": "Use the conv video VAE and LTX-specific Gemma 4 with bundled projection. Audio VAE is optional for silent video.", "model_url": "https://huggingface.co/Lightricks/LTX-2.5", "slots": [ @@ -35,6 +36,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 8, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "sampling_method": "euler", "fps": 24, diff --git a/examples/server/recipes/mage-flow-edit.json b/examples/server/recipes/mage-flow-edit.json index 82d66f6b..e0c55245 100644 --- a/examples/server/recipes/mage-flow-edit.json +++ b/examples/server/recipes/mage-flow-edit.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "mage-flow-edit", "name": "Mage Flow Edit", + "size_multiple": 16, "description": "Supply reference images. Select a separate vision projector when it is not bundled in the text encoder.", "model_url": "https://huggingface.co/microsoft/Mage-Flow-Edit", "slots": [ diff --git a/examples/server/recipes/mage-flow-turbo.json b/examples/server/recipes/mage-flow-turbo.json index 5cf9bb25..8e8a9d63 100644 --- a/examples/server/recipes/mage-flow-turbo.json +++ b/examples/server/recipes/mage-flow-turbo.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "mage-flow-turbo", "name": "Mage Flow Turbo", + "size_multiple": 16, "model_url": "https://huggingface.co/microsoft/Mage-Flow-Turbo", "slots": [ { diff --git a/examples/server/recipes/mage-flow.json b/examples/server/recipes/mage-flow.json index 4cbd1593..c2751f08 100644 --- a/examples/server/recipes/mage-flow.json +++ b/examples/server/recipes/mage-flow.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "mage-flow", "name": "Mage Flow", + "size_multiple": 16, "model_url": "https://huggingface.co/microsoft/Mage-Flow", "slots": [ { diff --git a/examples/server/recipes/ming-image.json b/examples/server/recipes/ming-image.json index 465a690e..f100581b 100644 --- a/examples/server/recipes/ming-image.json +++ b/examples/server/recipes/ming-image.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "ming-image", "name": "Ming Image 0.1", + "size_multiple": 16, "model_url": "https://huggingface.co/inclusionAI/Ming-Image-0.1-Design", "slots": [ { diff --git a/examples/server/recipes/minimax-h3-fl2va.json b/examples/server/recipes/minimax-h3-fl2va.json index 39719239..f0af22bd 100644 --- a/examples/server/recipes/minimax-h3-fl2va.json +++ b/examples/server/recipes/minimax-h3-fl2va.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "minimax-h3-fl2va", "name": "MiniMax H3 FL2VA", + "size_multiple": 32, "description": "Generate video and audio, optionally with start/end images. Supports full and pruned FL2VA weights. Audio VAE is optional for silent video.", "model_url": "https://huggingface.co/MiniMaxAI/MiniMax-H3", "slots": [ @@ -35,6 +36,13 @@ "tae" ] ], + "video_constraints": { + "frame_step": 17, + "frame_offset": 5, + "frame_rounding": "ceil", + "min_frames": 5, + "fixed_fps": 24 + }, "defaults": { "video_frames": 56, "fps": 24, diff --git a/examples/server/recipes/minimax-h3-ref2va.json b/examples/server/recipes/minimax-h3-ref2va.json index 2fb04178..9ee5f63d 100644 --- a/examples/server/recipes/minimax-h3-ref2va.json +++ b/examples/server/recipes/minimax-h3-ref2va.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "minimax-h3-ref2va", "name": "MiniMax H3 Ref2VA", + "size_multiple": 32, "description": "Supply image, video or audio references with compatible Ref2VA weights. Audio VAE is optional for silent video.", "model_url": "https://huggingface.co/MiniMaxAI/MiniMax-H3", "slots": [ @@ -35,6 +36,13 @@ "tae" ] ], + "video_constraints": { + "frame_step": 17, + "frame_offset": 5, + "frame_rounding": "ceil", + "min_frames": 5, + "fixed_fps": 24 + }, "defaults": { "video_frames": 56, "fps": 24, diff --git a/examples/server/recipes/minit2i.json b/examples/server/recipes/minit2i.json index 2c7b94eb..a82333d9 100644 --- a/examples/server/recipes/minit2i.json +++ b/examples/server/recipes/minit2i.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "minit2i", "name": "MiniT2I", + "size_multiple": 1, "description": "Pixel-space model; no VAE is required.", "model_url": "https://huggingface.co/MiniT2I/MiniT2I", "slots": [ diff --git a/examples/server/recipes/moss-image-base.json b/examples/server/recipes/moss-image-base.json index bc2cc7d9..273eeea7 100644 --- a/examples/server/recipes/moss-image-base.json +++ b/examples/server/recipes/moss-image-base.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "moss-image-base", "name": "MOSS Image Base", + "size_multiple": 16, "description": "Select the MOSS-Image-Base transformer with its matching text encoder and FLUX.2 VAE or TAE.", "model_url": "https://huggingface.co/OpenMOSS-Team/MOSS-Image-Base", "slots": [ diff --git a/examples/server/recipes/moss-image-sft.json b/examples/server/recipes/moss-image-sft.json index e0239b0b..d63dd2eb 100644 --- a/examples/server/recipes/moss-image-sft.json +++ b/examples/server/recipes/moss-image-sft.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "moss-image-sft", "name": "MOSS Image SFT", + "size_multiple": 16, "description": "Select the MOSS-Image-SFT transformer with its matching text encoder and FLUX.2 VAE or TAE.", "model_url": "https://huggingface.co/OpenMOSS-Team/MOSS-Image-SFT", "slots": [ diff --git a/examples/server/recipes/nitrosd-realism.json b/examples/server/recipes/nitrosd-realism.json index 75f2d43f..ae7345db 100644 --- a/examples/server/recipes/nitrosd-realism.json +++ b/examples/server/recipes/nitrosd-realism.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "nitrosd-realism", "name": "NitroSD Realism", + "size_multiple": 64, "model_url": "https://huggingface.co/ChenDY/NitroFusion", "slots": [ { diff --git a/examples/server/recipes/pid-15-flux2.json b/examples/server/recipes/pid-15-flux2.json index 4fb3db3a..84cc941c 100644 --- a/examples/server/recipes/pid-15-flux2.json +++ b/examples/server/recipes/pid-15-flux2.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "pid-15-flux2", "name": "PiD 1.5 (Flux.2 VAE)", + "size_multiple": 1, "description": "Supply a reference image; the first image is used as the source latent. Select a matching Flux.2 PiD checkpoint and VAE.", "model_url": "https://huggingface.co/nvidia/PiD", "slots": [ diff --git a/examples/server/recipes/pid-15-qwen-image.json b/examples/server/recipes/pid-15-qwen-image.json index 9db9e742..1415ac75 100644 --- a/examples/server/recipes/pid-15-qwen-image.json +++ b/examples/server/recipes/pid-15-qwen-image.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "pid-15-qwen-image", "name": "PiD 1.5 (Qwen Image VAE)", + "size_multiple": 1, "description": "Supply a reference image; the first image is used as the source latent. Select a matching Qwen Image PiD checkpoint and VAE. Use the Qwen Image 2D VAE.", "model_url": "https://huggingface.co/nvidia/PiD", "slots": [ diff --git a/examples/server/recipes/pid-15-sd3.json b/examples/server/recipes/pid-15-sd3.json index 3980b867..c92e9c03 100644 --- a/examples/server/recipes/pid-15-sd3.json +++ b/examples/server/recipes/pid-15-sd3.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "pid-15-sd3", "name": "PiD 1.5 (SD3 VAE)", + "size_multiple": 1, "description": "Supply a reference image; the first image is used as the source latent. Select a matching SD3 PiD checkpoint and VAE.", "model_url": "https://huggingface.co/nvidia/PiD", "slots": [ diff --git a/examples/server/recipes/pid-15.json b/examples/server/recipes/pid-15.json index f7913bee..e98d762c 100644 --- a/examples/server/recipes/pid-15.json +++ b/examples/server/recipes/pid-15.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "pid-15", "name": "PiD 1.5 (Flux / Z-Image VAE)", + "size_multiple": 1, "description": "Supply a reference image; the first image is used as the source latent. Select a Flux/Z-Image PiD checkpoint and matching Flux VAE.", "model_url": "https://huggingface.co/nvidia/PiD", "slots": [ diff --git a/examples/server/recipes/pid-flux2.json b/examples/server/recipes/pid-flux2.json index 9c802dc1..827b4f2a 100644 --- a/examples/server/recipes/pid-flux2.json +++ b/examples/server/recipes/pid-flux2.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "pid-flux2", "name": "PiD (Flux.2 VAE)", + "size_multiple": 1, "description": "Supply a reference image; the first image is used as the source latent. Select a matching Flux.2 PiD checkpoint and VAE.", "model_url": "https://huggingface.co/nvidia/PiD", "slots": [ diff --git a/examples/server/recipes/pid-qwen-image.json b/examples/server/recipes/pid-qwen-image.json index 4503b1cd..21552602 100644 --- a/examples/server/recipes/pid-qwen-image.json +++ b/examples/server/recipes/pid-qwen-image.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "pid-qwen-image", "name": "PiD (Qwen Image VAE)", + "size_multiple": 1, "description": "Supply a reference image; the first image is used as the source latent. Select a matching Qwen Image PiD checkpoint and VAE. Use the Qwen Image 2D VAE.", "model_url": "https://huggingface.co/nvidia/PiD", "slots": [ diff --git a/examples/server/recipes/pid-sd3.json b/examples/server/recipes/pid-sd3.json index 6dad2ca0..2170b8fb 100644 --- a/examples/server/recipes/pid-sd3.json +++ b/examples/server/recipes/pid-sd3.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "pid-sd3", "name": "PiD (SD3 VAE)", + "size_multiple": 1, "description": "Supply a reference image; the first image is used as the source latent. Select a matching SD3 PiD checkpoint and VAE.", "model_url": "https://huggingface.co/nvidia/PiD", "slots": [ diff --git a/examples/server/recipes/pid.json b/examples/server/recipes/pid.json index 24794496..77c02e96 100644 --- a/examples/server/recipes/pid.json +++ b/examples/server/recipes/pid.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "pid", "name": "PiD (Flux / Z-Image VAE)", + "size_multiple": 1, "description": "Supply a reference image; the first image is used as the source latent. Select a Flux/Z-Image PiD checkpoint and matching Flux VAE.", "model_url": "https://huggingface.co/nvidia/PiD", "slots": [ diff --git a/examples/server/recipes/pixart-alpha-1024.json b/examples/server/recipes/pixart-alpha-1024.json index 1144f87b..e17bf54a 100644 --- a/examples/server/recipes/pixart-alpha-1024.json +++ b/examples/server/recipes/pixart-alpha-1024.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "pixart-alpha-1024", "name": "PixArt Alpha XL 1024", + "size_multiple": 16, "description": "Use the matching PixArt Alpha transformer and SD 1.x-style VAE. Resolution/aspect-ratio micro-conditioning is not currently applied by the runtime.", "model_url": "https://huggingface.co/PixArt-alpha/PixArt-XL-2-1024-MS", "slots": [ diff --git a/examples/server/recipes/pixart-alpha-512.json b/examples/server/recipes/pixart-alpha-512.json index 804a7a0c..7f6817d0 100644 --- a/examples/server/recipes/pixart-alpha-512.json +++ b/examples/server/recipes/pixart-alpha-512.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "pixart-alpha-512", "name": "PixArt Alpha XL 512", + "size_multiple": 16, "description": "Use the matching PixArt Alpha transformer and SD 1.x-style VAE. Resolution/aspect-ratio micro-conditioning is not currently applied by the runtime.", "model_url": "https://huggingface.co/PixArt-alpha/PixArt-XL-2-512x512", "slots": [ diff --git a/examples/server/recipes/pixart-sigma.json b/examples/server/recipes/pixart-sigma.json index ed4d2f0d..dd79065c 100644 --- a/examples/server/recipes/pixart-sigma.json +++ b/examples/server/recipes/pixart-sigma.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "pixart-sigma", "name": "PixArt Sigma XL 1024", + "size_multiple": 16, "model_url": "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS", "slots": [ { diff --git a/examples/server/recipes/qwen-image-21-turbo.json b/examples/server/recipes/qwen-image-21-turbo.json index 99da90d7..5962b92a 100644 --- a/examples/server/recipes/qwen-image-21-turbo.json +++ b/examples/server/recipes/qwen-image-21-turbo.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "qwen-image-21-turbo", "name": "Qwen Image 2.1 Turbo", + "size_multiple": 32, "description": "Use the standalone Turbo diffusion weights for generation and editing with the fixed 8-step schedule. Select the Qwen Image 2.1 VAE or TAEQI2.1, and supply a vision projector when needed.", "model_url": "https://huggingface.co/Qwen/Qwen-Image-2.1-Turbo", "slots": [ diff --git a/examples/server/recipes/qwen-image-21.json b/examples/server/recipes/qwen-image-21.json index fd487e58..12c907d8 100644 --- a/examples/server/recipes/qwen-image-21.json +++ b/examples/server/recipes/qwen-image-21.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "qwen-image-21", "name": "Qwen Image 2.1", + "size_multiple": 32, "description": "Supports generation and editing. Select the full Qwen Image 2.1 VAE or TAEQI2.1. Supply a separate vision projector when needed. Select acceleration LoRAs and adjust their settings separately.", "model_url": "https://huggingface.co/Qwen/Qwen-Image-2.1", "slots": [ diff --git a/examples/server/recipes/sd-turbo.json b/examples/server/recipes/sd-turbo.json index 7723c810..cd595526 100644 --- a/examples/server/recipes/sd-turbo.json +++ b/examples/server/recipes/sd-turbo.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sd-turbo", "name": "SD Turbo", + "size_multiple": 64, "description": "Select a single-file SD-Turbo checkpoint. Uses one step with CFG disabled (scale 1 in sd.cpp); use more steps for partial image-to-image denoising.", "model_url": "https://huggingface.co/stabilityai/sd-turbo", "slots": [ diff --git a/examples/server/recipes/sd1-checkpoint.json b/examples/server/recipes/sd1-checkpoint.json index 38d23867..240bdf09 100644 --- a/examples/server/recipes/sd1-checkpoint.json +++ b/examples/server/recipes/sd1-checkpoint.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sd1-checkpoint", "name": "SD 1.x", + "size_multiple": 64, "description": "Supports SD 1.x, compatible LCM checkpoints and layer-count variants. For LCM weights, select LCM sampling and adjust steps/CFG. Tiny SD Diffusers directories must first be converted into a single-file checkpoint.", "model_url": "https://huggingface.co/CompVis/stable-diffusion-v1-4", "slots": [ diff --git a/examples/server/recipes/sd1-controlnet.json b/examples/server/recipes/sd1-controlnet.json index eeaee22f..ddd76494 100644 --- a/examples/server/recipes/sd1-controlnet.json +++ b/examples/server/recipes/sd1-controlnet.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sd1-controlnet", "name": "SD 1.5 ControlNet", + "size_multiple": 64, "description": "Supply a control image; input image and mask are optional for image-to-image or inpainting.", "model_url": "https://huggingface.co/lllyasviel/control_v11p_sd15_canny", "slots": [ diff --git a/examples/server/recipes/sd1-inpainting.json b/examples/server/recipes/sd1-inpainting.json index f9f3064f..a4c0aa2c 100644 --- a/examples/server/recipes/sd1-inpainting.json +++ b/examples/server/recipes/sd1-inpainting.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sd1-inpainting", "name": "SD 1.5 Inpainting", + "size_multiple": 64, "description": "Select an inpainting checkpoint, then supply an input image and mask.", "slots": [ { diff --git a/examples/server/recipes/sd1-ip-adapter.json b/examples/server/recipes/sd1-ip-adapter.json index 6c0ab1a6..7e14c156 100644 --- a/examples/server/recipes/sd1-ip-adapter.json +++ b/examples/server/recipes/sd1-ip-adapter.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sd1-ip-adapter", "name": "SD 1.5 IP-Adapter / Plus", + "size_multiple": 64, "description": "Select matching SD 1.5 IP-Adapter or Plus weights and CLIP Vision H, then supply an IP-Adapter image.", "model_url": "https://huggingface.co/h94/IP-Adapter", "slots": [ diff --git a/examples/server/recipes/sd2-512-checkpoint.json b/examples/server/recipes/sd2-512-checkpoint.json index 77838c28..e5df5af3 100644 --- a/examples/server/recipes/sd2-512-checkpoint.json +++ b/examples/server/recipes/sd2-512-checkpoint.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sd2-512-checkpoint", "name": "SD 2.1 512", + "size_multiple": 64, "description": "Supports SD 2.x 512 and compatible layer-count variants. Tiny SD2 Diffusers directories must first be converted into a single-file checkpoint.", "slots": [ { diff --git a/examples/server/recipes/sd2-768-checkpoint.json b/examples/server/recipes/sd2-768-checkpoint.json index 808b187d..8e044185 100644 --- a/examples/server/recipes/sd2-768-checkpoint.json +++ b/examples/server/recipes/sd2-768-checkpoint.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sd2-768-checkpoint", "name": "SD 2.1 768", + "size_multiple": 64, "slots": [ { "id": "model", diff --git a/examples/server/recipes/sd2-inpainting.json b/examples/server/recipes/sd2-inpainting.json index 4980f7f0..7e5e3126 100644 --- a/examples/server/recipes/sd2-inpainting.json +++ b/examples/server/recipes/sd2-inpainting.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sd2-inpainting", "name": "SD 2 Inpainting", + "size_multiple": 64, "description": "Select an inpainting checkpoint, then supply Init Image and Mask Image.", "slots": [ { diff --git a/examples/server/recipes/sdxl-checkpoint.json b/examples/server/recipes/sdxl-checkpoint.json index 372d5a6d..a1fea1bd 100644 --- a/examples/server/recipes/sdxl-checkpoint.json +++ b/examples/server/recipes/sdxl-checkpoint.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sdxl-checkpoint", "name": "SDXL", + "size_multiple": 64, "description": "Supports SDXL and compatible reduced-depth checkpoints such as SSD-1B and Segmind Vega.", "model_url": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0", "slots": [ diff --git a/examples/server/recipes/sdxl-controlnet.json b/examples/server/recipes/sdxl-controlnet.json index c50efbd2..3ffb9b4c 100644 --- a/examples/server/recipes/sdxl-controlnet.json +++ b/examples/server/recipes/sdxl-controlnet.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sdxl-controlnet", "name": "SDXL ControlNet", + "size_multiple": 64, "description": "Select a compatible SDXL ControlNet and supply a preprocessed control image. Input image and mask are optional for image-to-image or inpainting.", "model_url": "https://huggingface.co/diffusers/controlnet-canny-sdxl-1.0", "slots": [ diff --git a/examples/server/recipes/sdxl-inpainting.json b/examples/server/recipes/sdxl-inpainting.json index 574423d0..f4ef9721 100644 --- a/examples/server/recipes/sdxl-inpainting.json +++ b/examples/server/recipes/sdxl-inpainting.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sdxl-inpainting", "name": "SDXL Inpainting", + "size_multiple": 64, "description": "Select an inpainting checkpoint, then supply Init Image and Mask Image.", "model_url": "https://huggingface.co/diffusers/stable-diffusion-xl-1.0-inpainting-0.1", "slots": [ diff --git a/examples/server/recipes/sdxl-ip-adapter.json b/examples/server/recipes/sdxl-ip-adapter.json index b649fb1d..f0bc8cda 100644 --- a/examples/server/recipes/sdxl-ip-adapter.json +++ b/examples/server/recipes/sdxl-ip-adapter.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sdxl-ip-adapter", "name": "SDXL IP-Adapter / Plus", + "size_multiple": 64, "description": "Select matching SDXL ViT-H IP-Adapter or Plus weights and CLIP Vision H, then supply an IP-Adapter image.", "model_url": "https://huggingface.co/h94/IP-Adapter", "slots": [ diff --git a/examples/server/recipes/sdxl-photomaker.json b/examples/server/recipes/sdxl-photomaker.json index 025fb766..c8b57902 100644 --- a/examples/server/recipes/sdxl-photomaker.json +++ b/examples/server/recipes/sdxl-photomaker.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sdxl-photomaker", "name": "SDXL PhotoMaker", + "size_multiple": 64, "description": "Select PhotoMaker v1 and supply identity images. Include the img trigger in the prompt.", "model_url": "https://huggingface.co/TencentARC/PhotoMaker", "slots": [ diff --git a/examples/server/recipes/sdxl-turbo.json b/examples/server/recipes/sdxl-turbo.json index 8650e6c5..496291d7 100644 --- a/examples/server/recipes/sdxl-turbo.json +++ b/examples/server/recipes/sdxl-turbo.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sdxl-turbo", "name": "SDXL Turbo", + "size_multiple": 64, "model_url": "https://huggingface.co/stabilityai/sdxl-turbo", "slots": [ { diff --git a/examples/server/recipes/sdxs.json b/examples/server/recipes/sdxs.json index 19e218ac..f83b060a 100644 --- a/examples/server/recipes/sdxs.json +++ b/examples/server/recipes/sdxs.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sdxs", "name": "SDXS", + "size_multiple": 64, "model_url": "https://huggingface.co/IDKiro/sdxs-512-dreamshaper", "slots": [ { diff --git a/examples/server/recipes/sefi-image.json b/examples/server/recipes/sefi-image.json index 35e93336..168809d1 100644 --- a/examples/server/recipes/sefi-image.json +++ b/examples/server/recipes/sefi-image.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sefi-image", "name": "SeFi Image 5B RL", + "size_multiple": 16, "model_url": "https://huggingface.co/SeFi-Image/SeFi-Image-5B-Base", "slots": [ { diff --git a/examples/server/recipes/sensenova-u15.json b/examples/server/recipes/sensenova-u15.json index f2df865e..9ca386e4 100644 --- a/examples/server/recipes/sensenova-u15.json +++ b/examples/server/recipes/sensenova-u15.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "sensenova-u15", "name": "SenseNova U1.5", + "size_multiple": 32, "description": "Bundled MoT checkpoint.", "model_url": "https://huggingface.co/sensenova/SenseNova-U1.5-8B-MoT", "slots": [ diff --git a/examples/server/recipes/wan21-flf2v.json b/examples/server/recipes/wan21-flf2v.json index 02f0e80a..6bb4c33d 100644 --- a/examples/server/recipes/wan21-flf2v.json +++ b/examples/server/recipes/wan21-flf2v.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "wan21-flf2v", "name": "Wan 2.1 FLF2V 14B", + "size_multiple": 16, "description": "Select FLF2V weights and supply both start and end images.", "model_url": "https://huggingface.co/Wan-AI/Wan2.1-FLF2V-14B-720P", "slots": [ @@ -35,6 +36,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 4, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "sampling_method": "euler", "flow_shift": 16, diff --git a/examples/server/recipes/wan21-i2v.json b/examples/server/recipes/wan21-i2v.json index d8d8d13e..32d5fc7a 100644 --- a/examples/server/recipes/wan21-i2v.json +++ b/examples/server/recipes/wan21-i2v.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "wan21-i2v", "name": "Wan 2.1 I2V", + "size_multiple": 16, "description": "Image-to-video with Wan 2.1, compatible SkyReels V2 weights, or a single Wan 2.2 I2V expert. CLIP Vision is required by Wan 2.1 I2V and is unused by Wan 2.2. Defaults target Wan 2.1 480p.", "model_url": "https://huggingface.co/Wan-AI/Wan2.1-I2V-14B-480P", "slots": [ @@ -35,6 +36,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 4, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "sampling_method": "euler", "video_frames": 33, diff --git a/examples/server/recipes/wan21-t2v.json b/examples/server/recipes/wan21-t2v.json index 367611c4..998078cb 100644 --- a/examples/server/recipes/wan21-t2v.json +++ b/examples/server/recipes/wan21-t2v.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "wan21-t2v", "name": "Wan 2.1 T2V", + "size_multiple": 16, "description": "Text-to-video with Wan 2.1 or a compatible single Wan 2.2 T2V expert. Defaults target Wan 2.1 14B; adjust settings for the selected weights and acceleration LoRAs.", "model_url": "https://huggingface.co/Wan-AI/Wan2.1-T2V-14B", "slots": [ @@ -30,6 +31,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 4, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "sampling_method": "euler", "video_frames": 33, diff --git a/examples/server/recipes/wan21-vace.json b/examples/server/recipes/wan21-vace.json index 7b7f7e41..d7f83438 100644 --- a/examples/server/recipes/wan21-vace.json +++ b/examples/server/recipes/wan21-vace.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "wan21-vace", "name": "Wan 2.1 VACE 1.3B / 14B", + "size_multiple": 16, "description": "Select matching VACE weights. Supports text, reference images and control video through Video Generation.", "model_url": "https://huggingface.co/Wan-AI/Wan2.1-VACE-14B", "slots": [ @@ -30,6 +31,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 4, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "sampling_method": "euler", "video_frames": 33, diff --git a/examples/server/recipes/wan22-i2v.json b/examples/server/recipes/wan22-i2v.json index 62afdfce..c7ab2ed8 100644 --- a/examples/server/recipes/wan22-i2v.json +++ b/examples/server/recipes/wan22-i2v.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "wan22-i2v", "name": "Wan 2.2 I2V A14B", + "size_multiple": 16, "description": "Image-to-video. Select a start image in Video Generation after loading. LightX2V acceleration LoRAs must match the I2V low- and high-noise models; configure LoRAs and sampling separately.", "model_url": "https://huggingface.co/Wan-AI/Wan2.2-I2V-A14B", "slots": [ @@ -35,6 +36,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 4, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "sampling_method": "euler", "video_frames": 33, diff --git a/examples/server/recipes/wan22-s2v.json b/examples/server/recipes/wan22-s2v.json index 55440ad3..641ed6e5 100644 --- a/examples/server/recipes/wan22-s2v.json +++ b/examples/server/recipes/wan22-s2v.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "wan22-s2v", "name": "Wan 2.2 S2V 14B", + "size_multiple": 16, "description": "Supply a start image and reference audio through Video Generation.", "model_url": "https://huggingface.co/Wan-AI/Wan2.2-S2V-14B", "slots": [ @@ -35,6 +36,13 @@ "tae" ] ], + "video_constraints": { + "frame_step": 4, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1, + "fixed_fps": 16 + }, "defaults": { "sampling_method": "euler", "flow_shift": 3, diff --git a/examples/server/recipes/wan22-t2v.json b/examples/server/recipes/wan22-t2v.json index 905bcfa4..fba962fc 100644 --- a/examples/server/recipes/wan22-t2v.json +++ b/examples/server/recipes/wan22-t2v.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "wan22-t2v", "name": "Wan 2.2 T2V A14B", + "size_multiple": 16, "description": "Text-to-video. Use Video Generation after loading. LightX2V acceleration LoRAs must match the T2V low- and high-noise models; configure LoRAs and sampling separately.", "model_url": "https://huggingface.co/Wan-AI/Wan2.2-T2V-A14B", "slots": [ @@ -35,6 +36,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 4, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "sampling_method": "euler", "video_frames": 33, diff --git a/examples/server/recipes/wan22-ti2v-5b.json b/examples/server/recipes/wan22-ti2v-5b.json index 3e2aa35b..bc433611 100644 --- a/examples/server/recipes/wan22-ti2v-5b.json +++ b/examples/server/recipes/wan22-ti2v-5b.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "wan22-ti2v-5b", "name": "Wan 2.2 TI2V 5B", + "size_multiple": 32, "description": "Use Wan 2.2 VAE. Supports text, start-image and start/end-image conditioning.", "model_url": "https://huggingface.co/Wan-AI/Wan2.2-TI2V-5B", "slots": [ @@ -30,6 +31,12 @@ "tae" ] ], + "video_constraints": { + "frame_step": 4, + "frame_offset": 1, + "frame_rounding": "floor", + "min_frames": 1 + }, "defaults": { "sampling_method": "euler", "video_frames": 33, diff --git a/examples/server/recipes/z-image-l2p.json b/examples/server/recipes/z-image-l2p.json index 0375f138..192d5472 100644 --- a/examples/server/recipes/z-image-l2p.json +++ b/examples/server/recipes/z-image-l2p.json @@ -2,6 +2,7 @@ "schema_version": 1, "id": "z-image-l2p", "name": "Z-Image L2P", + "size_multiple": 16, "description": "Pixel-space L2P checkpoint with Qwen 3 4B; no VAE is needed. Use dimensions divisible by 16.", "model_url": "https://huggingface.co/zhen-nan/L2P", "slots": [ diff --git a/src/model/diffusion/moss_image.hpp b/src/model/diffusion/moss_image.hpp index a854c327..188c7419 100644 --- a/src/model/diffusion/moss_image.hpp +++ b/src/model/diffusion/moss_image.hpp @@ -185,7 +185,7 @@ namespace MOSSImage { ggml_build_forward_expand(ctx->graph, parts[i]); } } - x = ggml_concat(c, x, text, 1); + x = ggml_concat(c, x, text, 1); for (int64_t i = 0; i < config.depth; ++i) { if (std::find(skip.begin(), skip.end(), i) == skip.end()) { x = std::dynamic_pointer_cast(blocks["layers." + std::to_string(i)])->forward(ctx, x, pe, parts); diff --git a/src/pipeline/image.cpp b/src/pipeline/image.cpp index 7d264e22..607ec18c 100644 --- a/src/pipeline/image.cpp +++ b/src/pipeline/image.cpp @@ -314,8 +314,8 @@ namespace sd::pipeline { } bool skip_vae_encoding = (sd->version == VERSION_HIDREAM_O1) || !( - ref_image_params.pass_to_dit || ref_image_params.vlm_use_vae_input || - sd_version_is_unet_edit(sd->version) || sd_version_is_pid(sd->version)); + ref_image_params.pass_to_dit || ref_image_params.vlm_use_vae_input || + sd_version_is_unet_edit(sd->version) || sd_version_is_pid(sd->version)); std::vector> ref_latents; for (size_t i = 0; i < ref_images.size(); i++) { diff --git a/src/runtime/image_preprocess.cpp b/src/runtime/image_preprocess.cpp index ec649390..445f0d1f 100644 --- a/src/runtime/image_preprocess.cpp +++ b/src/runtime/image_preprocess.cpp @@ -379,8 +379,13 @@ namespace sd { if (target == ImageTarget::Mask && has_init_transform_) { auto options = resolve_options(target, index); if (image.width != init_transform_.source_width || image.height != init_transform_.source_height) { - fail("mask and init source dimensions must match"); - return {}; + ImagePreprocessor alignment; + tensor = alignment.apply_geometry(tensor, target, index, init_transform_.source_width, init_transform_.source_height, + ImageResizeMode::CropResize, ops::InterpolateMode::NearestExact); + if (tensor.empty()) { + fail("could not align mask to init source dimensions"); + return {}; + } } bool geometry_override = options.count("width") || options.count("anchor") || (options.count("mode") && options.at("mode") != "auto");