Compare commits

...
32 changed files with 1150 additions and 266 deletions
+1
View File
@@ -64,6 +64,7 @@ API and command-line option may change frequently.***
- [HiDream-O1-Image](./docs/hidream_o1_image.md)
- [Ideogram4](./docs/ideogram4.md)
- [LLaDA-Image](./docs/llada_image.md)
- [PixArt](./docs/pixart.md)
- [Image Edit Models](./docs/edit.md)
- [FLUX.1-Kontext-dev](./docs/kontext.md)
- [Qwen Image Edit series](./docs/qwen_image_edit.md)
Binary file not shown.
+48
View File
@@ -21,6 +21,54 @@ CPU fallback. It excludes weights and cache buffers. Within a runner lifecycle,
the summary is printed only on the first graph or when backend capacities or the
segment count change.
## Use VAE tiling to reduce encode and decode memory usage.
`--vae-tiling` enables spatial tiling for both VAE encoding and decoding. The
default tile size is 256x256 **image pixels**, independent of the VAE scale factor:
```shell
--vae-tiling --vae-tile-size 256x256 --vae-tile-overlap 0.5
```
`--vae-tile-size` accepts one size or `WIDTHxHEIGHT`. A zero dimension uses the
256-pixel default. Sizes are rounded down to a multiple of the VAE scale factor
and capped at the current input dimensions. Explicit sizes below four latent
pixels per axis (or the full axis when it is smaller) are rejected. Encoding and
decoding use the same spatial sizes, without an additional encoding multiplier.
Inputs that fit within a tile are processed as one tile.
For a 512x512 image with the default 50% overlap, both encoding and decoding use
3x3 tiles. A 256-pixel tile corresponds to 32 latent pixels for an 8x VAE, 16 for
a 16x VAE, and 8 for a 32x VAE. Smaller tiles reduce each graph's memory demand,
but overlapping work can increase processing time and tiling can affect image
quality, especially during encoding. Use larger tiles when more context is needed.
`--vae-relative-tile-size` overrides the absolute size on each axis with a positive
value. Values up to and including 1 specify a fraction of the current input size;
values greater than 1 specify a target number of tiles per axis, accounting for
overlap. For example, `0.5x0.5` uses half the width and height in both encode and
decode. The target overlap is clamped to 0 through 0.5 and the actual overlap is
adjusted to fit the image. Size and overlap options require `--vae-tiling`.
**Migration:** `--vae-tile-size` and the C/JSON fields `tile_size_w` and
`tile_size_h` now use image pixels instead of latent units. The C/JSON fields
`tile_size_x/y` have been renamed to `tile_size_w/h`, and `rel_size_x/y` to
`rel_size_w/h`. The command-line option names are unchanged. For example, an old
decode tile size of 32 corresponds to 256 pixels for an 8x VAE or 512 pixels for a
16x VAE. Encoding no longer enlarges explicit or relative tile sizes.
The main VAE decode path retries allocation failures with smaller tiles, even
without `--vae-tiling`. Supported video VAEs first try temporal tiling; spatial
retries use at most 256-pixel tiles initially and then halve the effective tile
dimensions down to the minimum size. Each spatial retry must reduce the effective
tile size. These runtime adjustments do not change the caller's parameters.
Execution failures are not retried, and encoding has no automatic OOM retry.
`--temporal-tiling` remains independent of spatial tiling. MiniMax H3 always uses
spatial tiling (256x256 pixels and 25% overlap by default) and its own temporal
windows. With `--vae-tiling`, its overlap follows `--vae-tile-overlap`; explicit
spatial sizes are honored.
## Offload weights to the CPU to save VRAM without reducing generation speed.
Using `--offload-to-cpu` allows you to offload weights to the CPU, saving VRAM without reducing generation speed.
+46
View File
@@ -0,0 +1,46 @@
# How to Use
You can run PixArt-α / PixArt-Σ with stable-diffusion.cpp.
PixArt is a DiT-based text-to-image model family conditioned by a T5-XXL text
encoder and a 4-channel VAE: SDXL-style for PixArt-Σ and SD1.x-style for PixArt-α.
## Download weights
- Download the transformer (diffusion model)
- PixArt-Σ XL-2 1024-MS: https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS/tree/main/transformer
- PixArt-α XL-2 1024-MS: https://huggingface.co/PixArt-alpha/PixArt-XL-2-1024-MS/tree/main/transformer
- Download the T5-XXL text encoder
- safetensors: https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS/tree/main/text_encoder
- Download the VAE
- PixArt-Σ: https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS/tree/main/vae
- PixArt-α: https://huggingface.co/PixArt-alpha/PixArt-XL-2-1024-MS/tree/main/vae
- Use the VAE matching the checkpoint's latent space. For TAE decoding or
preview, use TAESDXL for PixArt-Σ and TAESD for PixArt-α.
- Tokenizer: the T5 vocabulary is embedded; no extra tokenizer file is needed.
## Examples
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\pixart_sigma_xl2_1024_ms.safetensors --t5xxl ..\models\text_encoders\t5xxl.safetensors --vae ..\models\vae\pixart_vae.safetensors -p "a lovely cat" --cfg-scale 4.5 -W 1024 -H 1024 --steps 20 -v
```
## Notes
- The VAE scaling factor defaults to `0.13025` for PixArt-Σ. PixArt-α
checkpoints with resolution micro-condition weights use `0.18215`.
PixArt-α 512 has the same tensor layout as PixArt-Σ, so it requires an
explicit override: `--model-args "pixart_vae_scale_factor=0.18215"`.
This argument can also override the scale for other compatible checkpoints.
- PixArt-Σ checkpoints compute 2D sincos positional embeddings at runtime;
the trained grid is 64x64 patches with an interpolation scale of 2.
For checkpoints trained at a different resolution, the positional embedding
parameters can be adjusted via model args:
`--model-args "pixart_pos_embed_base_size=<trained grid>,pixart_interpolation_scale=<scale>"`
(e.g. `pixart_pos_embed_base_size=32,pixart_interpolation_scale=1,pixart_vae_scale_factor=0.18215` for
PixArt-α XL-2 512).
- Checkpoints carrying resolution/aspect-ratio micro-condition weights are
detected but those conditions are not applied yet; a warning is logged and
generation proceeds with the timestep embedding only.
- The transformer predicts 8 channels (noise + learned variance); only the
noise half is used for sampling, matching the reference implementation.
+25
View File
@@ -40,6 +40,9 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
- safetensors: https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/tree/main/split_files/diffusion_models
- gguf: https://huggingface.co/QuantStack/Wan2.2-S2V-14B-GGUF/tree/main
- int8_convrot safetensors: https://huggingface.co/noctrex/Wan2.2-S2V-14B-int8_convrot
- Wan2.2 VACE-Fun A14B
- safetensors: https://huggingface.co/alibaba-pai/Wan2.2-VACE-Fun-A14B
- gguf: https://huggingface.co/QuantStack/Wan2.2-VACE-Fun-A14B-GGUF/tree/main
- Download vae
- wan_2.1_vae (for all the wan model except Wan2.2 TI2V 5B)
- safetensors: https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/blob/main/split_files/vae/wan_2.1_vae.safetensors
@@ -256,3 +259,25 @@ ffmpeg -i ..\..\ComfyUI\input\post+depth.mp4 -qscale:v 1 -vf fps=8 post+depth\fr
```
<video src=../assets/wan/Wan2.1_14B_vace_v2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
### Wan2.2 VACE-Fun A14B
VACE-Fun runs as a MoE pair: `--diffusion-model` takes the low-noise expert and
`--high-noise-diffusion-model` the high-noise one. Reference-to-video uses `-i`
for the reference image, same as Wan2.1 VACE.
#### R2V
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-VACE-Fun-A14B-low-noise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-VACE-Fun-A14B-high-noise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 3.5 --sampling-method euler --steps 10 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 8 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa -i ..\assets\cat_with_sd_cpp_42.png --video-frames 33 --offload-to-cpu
```
<video src=../assets/wan/Wan2.2_A14B_vace_r2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
#### T2V
Same command without `-i` (VACE context is synthesized from an empty control
video, like Wan2.1 VACE t2v).
> On GPUs with ~12 GB VRAM, VACE also needs `--vae-tiling` — the control-video
> encode can exceed the budget otherwise.
+29 -29
View File
@@ -1341,7 +1341,7 @@ ArgOptions SDGenerationParams::get_options() {
&embed_image_metadata},
{"",
"--vae-tiling",
"process vae in tiles to reduce memory usage",
"process vae encode and decode in spatial tiles to reduce memory usage (default: 256x256 image pixels)",
true,
&vae_tiling_params.enabled},
{"",
@@ -1605,12 +1605,12 @@ ArgOptions SDGenerationParams::get_options() {
size_t x_pos = tile_size_str.find('x');
try {
if (x_pos != std::string::npos) {
std::string tile_x_str = tile_size_str.substr(0, x_pos);
std::string tile_y_str = tile_size_str.substr(x_pos + 1);
vae_tiling_params.tile_size_x = std::stoi(tile_x_str);
vae_tiling_params.tile_size_y = std::stoi(tile_y_str);
std::string tile_w_str = tile_size_str.substr(0, x_pos);
std::string tile_h_str = tile_size_str.substr(x_pos + 1);
vae_tiling_params.tile_size_w = std::stoi(tile_w_str);
vae_tiling_params.tile_size_h = std::stoi(tile_h_str);
} else {
vae_tiling_params.tile_size_x = vae_tiling_params.tile_size_y = std::stoi(tile_size_str);
vae_tiling_params.tile_size_w = vae_tiling_params.tile_size_h = std::stoi(tile_size_str);
}
} catch (const std::invalid_argument&) {
return -1;
@@ -1628,12 +1628,12 @@ ArgOptions SDGenerationParams::get_options() {
size_t x_pos = rel_size_str.find('x');
try {
if (x_pos != std::string::npos) {
std::string rel_x_str = rel_size_str.substr(0, x_pos);
std::string rel_y_str = rel_size_str.substr(x_pos + 1);
vae_tiling_params.rel_size_x = std::stof(rel_x_str);
vae_tiling_params.rel_size_y = std::stof(rel_y_str);
std::string rel_w_str = rel_size_str.substr(0, x_pos);
std::string rel_h_str = rel_size_str.substr(x_pos + 1);
vae_tiling_params.rel_size_w = std::stof(rel_w_str);
vae_tiling_params.rel_size_h = std::stof(rel_h_str);
} else {
vae_tiling_params.rel_size_x = vae_tiling_params.rel_size_y = std::stof(rel_size_str);
vae_tiling_params.rel_size_w = vae_tiling_params.rel_size_h = std::stof(rel_size_str);
}
} catch (const std::invalid_argument&) {
return -1;
@@ -1763,11 +1763,11 @@ ArgOptions SDGenerationParams::get_options() {
on_scm_policy_arg},
{"",
"--vae-tile-size",
"tile size for vae tiling in latent units, not image pixels, format [X]x[Y] (default: 32x32)",
"tile size for vae encode and decode in image pixels, format [W]x[H] or [S] (default: 256x256; requires --vae-tiling)",
on_tile_size_arg},
{"",
"--vae-relative-tile-size",
"relative tile size for vae tiling, format [X]x[Y], in fraction of image size if < 1, in number of tiles per dim if >=1 (overrides --vae-tile-size)",
"relative tile size for vae encode and decode, format [W]x[H] or [S]: <=1 is a dimension fraction, >1 a target tile count (overrides --vae-tile-size; requires --vae-tiling)",
on_relative_tile_size_arg},
{"",
"--prompt-file",
@@ -2224,20 +2224,20 @@ bool SDGenerationParams::from_json_str(
if (tiling_json.contains("temporal_tiling") && tiling_json["temporal_tiling"].is_boolean()) {
vae_tiling_params.temporal_tiling = tiling_json["temporal_tiling"];
}
if (tiling_json.contains("tile_size_x") && tiling_json["tile_size_x"].is_number_integer()) {
vae_tiling_params.tile_size_x = tiling_json["tile_size_x"];
if (tiling_json.contains("tile_size_w") && tiling_json["tile_size_w"].is_number_integer()) {
vae_tiling_params.tile_size_w = tiling_json["tile_size_w"];
}
if (tiling_json.contains("tile_size_y") && tiling_json["tile_size_y"].is_number_integer()) {
vae_tiling_params.tile_size_y = tiling_json["tile_size_y"];
if (tiling_json.contains("tile_size_h") && tiling_json["tile_size_h"].is_number_integer()) {
vae_tiling_params.tile_size_h = tiling_json["tile_size_h"];
}
if (tiling_json.contains("target_overlap") && tiling_json["target_overlap"].is_number()) {
vae_tiling_params.target_overlap = tiling_json["target_overlap"];
}
if (tiling_json.contains("rel_size_x") && tiling_json["rel_size_x"].is_number()) {
vae_tiling_params.rel_size_x = tiling_json["rel_size_x"];
if (tiling_json.contains("rel_size_w") && tiling_json["rel_size_w"].is_number()) {
vae_tiling_params.rel_size_w = tiling_json["rel_size_w"];
}
if (tiling_json.contains("rel_size_y") && tiling_json["rel_size_y"].is_number()) {
vae_tiling_params.rel_size_y = tiling_json["rel_size_y"];
if (tiling_json.contains("rel_size_h") && tiling_json["rel_size_h"].is_number()) {
vae_tiling_params.rel_size_h = tiling_json["rel_size_h"];
}
if (tiling_json.contains("extra_tiling_args") && tiling_json["extra_tiling_args"].is_string()) {
extra_tiling_args = tiling_json["extra_tiling_args"].get<std::string>();
@@ -2934,11 +2934,11 @@ std::string SDGenerationParams::to_string() const {
<< " vae_tiling_params: { "
<< vae_tiling_params.enabled << ", "
<< vae_tiling_params.temporal_tiling << ", "
<< vae_tiling_params.tile_size_x << ", "
<< vae_tiling_params.tile_size_y << ", "
<< vae_tiling_params.tile_size_w << ", "
<< vae_tiling_params.tile_size_h << ", "
<< vae_tiling_params.target_overlap << ", "
<< vae_tiling_params.rel_size_x << ", "
<< vae_tiling_params.rel_size_y << ", "
<< vae_tiling_params.rel_size_w << ", "
<< vae_tiling_params.rel_size_h << ", "
<< "\"" << extra_tiling_args << "\" },\n"
<< "}";
return oss.str();
@@ -3140,11 +3140,11 @@ std::string build_sdcpp_image_metadata_json(const SDContextParams& ctx_params,
root["vae_tiling"] = {
{"enabled", gen_params.vae_tiling_params.enabled},
{"temporal_tiling", gen_params.vae_tiling_params.temporal_tiling},
{"tile_size_x", gen_params.vae_tiling_params.tile_size_x},
{"tile_size_y", gen_params.vae_tiling_params.tile_size_y},
{"tile_size_w", gen_params.vae_tiling_params.tile_size_w},
{"tile_size_h", gen_params.vae_tiling_params.tile_size_h},
{"target_overlap", gen_params.vae_tiling_params.target_overlap},
{"rel_size_x", gen_params.vae_tiling_params.rel_size_x},
{"rel_size_y", gen_params.vae_tiling_params.rel_size_y},
{"rel_size_w", gen_params.vae_tiling_params.rel_size_w},
{"rel_size_h", gen_params.vae_tiling_params.rel_size_h},
{"extra_tiling_args", gen_params.extra_tiling_args},
};
}
+22 -20
View File
@@ -524,11 +524,11 @@ Shared default fields used by both `img_gen` and `vid_gen`:
| `vae_tiling_params` | `object` |
| `vae_tiling_params.enabled` | `boolean` |
| `vae_tiling_params.temporal_tiling` | `boolean` |
| `vae_tiling_params.tile_size_x` | `integer` |
| `vae_tiling_params.tile_size_y` | `integer` |
| `vae_tiling_params.tile_size_w` | `integer` |
| `vae_tiling_params.tile_size_h` | `integer` |
| `vae_tiling_params.target_overlap` | `number` |
| `vae_tiling_params.rel_size_x` | `number` |
| `vae_tiling_params.rel_size_y` | `number` |
| `vae_tiling_params.rel_size_w` | `number` |
| `vae_tiling_params.rel_size_h` | `number` |
| `vae_tiling_params.extra_tiling_args` | `string` |
| `cache_mode` | `string` |
| `cache_option` | `string` |
@@ -537,6 +537,8 @@ Shared default fields used by both `img_gen` and `vid_gen`:
| `output_format` | `string` |
| `output_compression` | `integer` |
`vae_tiling_params.tile_size_w` and `tile_size_h` are in **image pixels**, with `0` selecting the 256-pixel default. Both encode and decode use these sizes without an encoding multiplier. Positive `rel_size_w`/`rel_size_h` values override the corresponding absolute size: values up to 1 are dimension fractions, and values greater than 1 are target tile counts. Set `enabled` to use spatial tiling. Sizes are aligned down to the VAE scale factor and capped at the input dimensions; explicit sizes below the minimum supported tile size are rejected. These fields previously used latent units; see [VAE tiling](../../docs/performance.md#use-vae-tiling-to-reduce-encode-and-decode-memory-usage) for migration and OOM retry behavior.
`vae_tiling_params.extra_tiling_args` accepts a key=value list. Supported video VAEs accept `temporal_tile_frames` (alias `temporal_tile_size`, default `4`) and `temporal_tile_overlap` (default `1`).
LTX and Wan preserve causal state between temporal tiles. Hunyuan Video and TAEHV use overlap blending. MiniMax H3 keeps its model-specific fixed temporal windows because its latent-to-frame mapping is non-linear.
@@ -767,11 +769,11 @@ Example:
"vae_tiling_params": {
"enabled": false,
"temporal_tiling": false,
"tile_size_x": 0,
"tile_size_y": 0,
"tile_size_w": 0,
"tile_size_h": 0,
"target_overlap": 0.5,
"rel_size_x": 0.0,
"rel_size_y": 0.0,
"rel_size_w": 0.0,
"rel_size_h": 0.0,
"extra_tiling_args": ""
},
@@ -900,11 +902,11 @@ Other native fields:
| `vae_tiling_params` | `object` |
| `vae_tiling_params.enabled` | `boolean` |
| `vae_tiling_params.temporal_tiling` | `boolean` |
| `vae_tiling_params.tile_size_x` | `integer` |
| `vae_tiling_params.tile_size_y` | `integer` |
| `vae_tiling_params.tile_size_w` | `integer` |
| `vae_tiling_params.tile_size_h` | `integer` |
| `vae_tiling_params.target_overlap` | `number` |
| `vae_tiling_params.rel_size_x` | `number` |
| `vae_tiling_params.rel_size_y` | `number` |
| `vae_tiling_params.rel_size_w` | `number` |
| `vae_tiling_params.rel_size_h` | `number` |
| `vae_tiling_params.extra_tiling_args` | `string` |
| `cache_mode` | `string` |
| `cache_option` | `string` |
@@ -1115,11 +1117,11 @@ Example:
"vae_tiling_params": {
"enabled": false,
"temporal_tiling": false,
"tile_size_x": 0,
"tile_size_y": 0,
"tile_size_w": 0,
"tile_size_h": 0,
"target_overlap": 0.5,
"rel_size_x": 0.0,
"rel_size_y": 0.0,
"rel_size_w": 0.0,
"rel_size_h": 0.0,
"extra_tiling_args": ""
},
@@ -1240,11 +1242,11 @@ Other native fields:
| `vae_tiling_params` | `object` |
| `vae_tiling_params.enabled` | `boolean` |
| `vae_tiling_params.temporal_tiling` | `boolean` |
| `vae_tiling_params.tile_size_x` | `integer` |
| `vae_tiling_params.tile_size_y` | `integer` |
| `vae_tiling_params.tile_size_w` | `integer` |
| `vae_tiling_params.tile_size_h` | `integer` |
| `vae_tiling_params.target_overlap` | `number` |
| `vae_tiling_params.rel_size_x` | `number` |
| `vae_tiling_params.rel_size_y` | `number` |
| `vae_tiling_params.rel_size_w` | `number` |
| `vae_tiling_params.rel_size_h` | `number` |
| `vae_tiling_params.extra_tiling_args` | `string` |
| `cache_mode` | `string` |
| `cache_option` | `string` |
+4 -4
View File
@@ -78,11 +78,11 @@ static json make_vae_tiling_json(const sd_tiling_params_t& params) {
return {
{"enabled", params.enabled},
{"temporal_tiling", params.temporal_tiling},
{"tile_size_x", params.tile_size_x},
{"tile_size_y", params.tile_size_y},
{"tile_size_w", params.tile_size_w},
{"tile_size_h", params.tile_size_h},
{"target_overlap", params.target_overlap},
{"rel_size_x", params.rel_size_x},
{"rel_size_y", params.rel_size_y},
{"rel_size_w", params.rel_size_w},
{"rel_size_h", params.rel_size_h},
{"extra_tiling_args", params.extra_tiling_args ? params.extra_tiling_args : ""},
};
}
+6 -4
View File
@@ -173,11 +173,13 @@ enum lora_apply_mode_t {
typedef struct {
bool enabled;
bool temporal_tiling;
int tile_size_x;
int tile_size_y;
// Spatial tile dimensions in image pixels for both encode and decode; 0 uses 256.
int tile_size_w;
int tile_size_h;
float target_overlap;
float rel_size_x;
float rel_size_y;
// Positive values override tile_size: <= 1 is a dimension fraction, > 1 a target tile count.
float rel_size_w;
float rel_size_h;
const char* extra_tiling_args;
} sd_tiling_params_t;
+30 -13
View File
@@ -478,7 +478,12 @@ namespace sd::backend_fit {
return true;
}
bool prepare_vae_decode_retry_tiling(sd_tiling_params_t& tiling_params, bool prefer_temporal_tiling, ggml_status status) {
bool prepare_vae_decode_retry_tiling(sd_tiling_params_t& tiling_params,
bool prefer_temporal_tiling,
ggml_status status,
int latent_tile_size_w,
int latent_tile_size_h,
int scale_factor) {
// Execution failures can leave the device unusable; tiling only helps with allocation failures.
if (status != GGML_STATUS_ALLOC_FAILED) {
return false;
@@ -487,19 +492,31 @@ namespace sd::backend_fit {
if (prefer_temporal_tiling && !tiling_params.temporal_tiling) {
tiling_params.temporal_tiling = true;
retry_mode = tiling_params.enabled ? "spatial+temporal" : "temporal";
} else if (!tiling_params.enabled) {
tiling_params.enabled = true;
tiling_params.rel_size_x = 0.5f;
tiling_params.rel_size_y = 0.5f;
if (tiling_params.tile_size_x <= 0) {
tiling_params.tile_size_x = 256;
}
if (tiling_params.tile_size_y <= 0) {
tiling_params.tile_size_y = 256;
}
retry_mode = tiling_params.temporal_tiling ? "spatial+temporal" : "spatial";
} else {
return false;
if (latent_tile_size_w <= 0 || latent_tile_size_h <= 0 || scale_factor <= 0) {
return false;
}
auto smaller_tile = [&](int size) {
int next_size = size / 2;
if (!tiling_params.enabled) {
next_size = std::min(next_size, 256 / scale_factor);
}
return std::min(size, std::max(4, next_size));
};
const int tile_size_w = smaller_tile(latent_tile_size_w);
const int tile_size_h = smaller_tile(latent_tile_size_h);
if (tile_size_w == latent_tile_size_w && tile_size_h == latent_tile_size_h) {
return false;
}
tiling_params.enabled = true;
tiling_params.rel_size_w = 0.0f;
tiling_params.rel_size_h = 0.0f;
tiling_params.tile_size_w = tile_size_w * scale_factor;
tiling_params.tile_size_h = tile_size_h * scale_factor;
retry_mode = tiling_params.temporal_tiling ? "spatial+temporal" : "spatial";
LOG_WARN("Reducing VAE decode tiles from %dx%d to %dx%d image pixels",
latent_tile_size_w * scale_factor, latent_tile_size_h * scale_factor,
tiling_params.tile_size_w, tiling_params.tile_size_h);
}
LOG_WARN("VAE decode ran out of memory; retrying with %s tiling",
+4 -1
View File
@@ -17,7 +17,10 @@ namespace sd::backend_fit {
bool prepare_vae_decode_retry_tiling(sd_tiling_params_t& tiling_params,
bool prefer_temporal_tiling,
ggml_status status);
ggml_status status,
int latent_tile_size_w,
int latent_tile_size_h,
int scale_factor);
} // namespace sd::backend_fit
+7 -1
View File
@@ -62,6 +62,7 @@ enum SDVersion {
VERSION_SENSENOVA_U1_5,
VERSION_LLADA_IMAGE,
VERSION_ESRGAN,
VERSION_PIXART,
VERSION_COUNT,
};
@@ -252,6 +253,10 @@ static inline bool sd_version_is_sensenova_u1(SDVersion version) {
return version == VERSION_SENSENOVA_U1_5;
}
static inline bool sd_version_is_pixart(SDVersion version) {
return version == VERSION_PIXART;
}
static inline bool sd_version_supports_video_generation(SDVersion version) {
return version == VERSION_SVD || sd_version_is_wan(version) || sd_version_is_hunyuan_video(version) || sd_version_is_lingbot_video(version) || sd_version_is_ltxav(version) || sd_version_is_minimax_h3(version);
}
@@ -320,7 +325,8 @@ static inline bool sd_version_is_dit(SDVersion version) {
sd_version_is_sefi_image(version) ||
sd_version_is_krea2(version) ||
sd_version_is_mage_flow(version) ||
sd_version_is_sensenova_u1(version)) {
sd_version_is_sensenova_u1(version) ||
sd_version_is_pixart(version)) {
return true;
}
return false;
+21
View File
@@ -163,6 +163,27 @@ struct LoraModel : public GGMLRunner {
lora_tensors = std::move(new_lora_tensors);
}
std::unordered_map<std::string, ggml_tensor*> new_lora_tensors;
for (const auto& [old_name, tensor] : lora_tensors) {
std::string new_name = old_name;
if (starts_with(old_name, "lora.model.diffusion_model.transformer_blocks.")) {
// Qwen Image 2.1 stores the gate before the projection in fused MLP weights.
for (const auto& suffix : {std::string(".img_mlp.gate_layer.weight."), std::string(".img_mlp.proj.weight.")}) {
size_t pos = old_name.find(suffix);
if (pos == std::string::npos) {
continue;
}
std::string fused_name = old_name.substr(5, pos - 5) + ".img_mlp.gate_up.weight";
if (model_tensor_names.find(fused_name) != model_tensor_names.end()) {
new_name = "lora." + fused_name + (suffix == ".img_mlp.proj.weight." ? ".1." : ".") + old_name.substr(pos + suffix.size());
}
break;
}
}
new_lora_tensors[new_name] = tensor;
}
lora_tensors = std::move(new_lora_tensors);
}
ggml_tensor* get_lora_weight_diff(const std::string& model_tensor_name, ggml_context* ctx, ggml_backend_t backend) {
+399
View File
@@ -0,0 +1,399 @@
#ifndef __SD_MODEL_DIFFUSION_PIXART_HPP__
#define __SD_MODEL_DIFFUSION_PIXART_HPP__
#include <algorithm>
#include <cinttypes>
#include <cmath>
#include <vector>
#include "core/ggml_extend.h"
#include "core/ggml_runner.h"
#include "core/util.h"
#include "model/common/ggml_block.hpp"
#include "model/diffusion/dit.hpp"
#include "model/diffusion/mmdit.hpp"
#include "model/diffusion/model.hpp"
#include "model_loader.h"
// Ref: https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/transformers/pixart_transformer_2d.py
// Ref: https://github.com/PixArt-alpha/PixArt-sigma
namespace PixArt {
constexpr int PIXART_GRAPH_SIZE = 20480;
constexpr int ADALN_EMBED_DIM = 256;
struct PixArtConfig {
int64_t in_channels = 4;
int64_t out_channels = 8; // learn_sigma: noise prediction + learned variance
int64_t hidden_size = 1152;
int64_t cross_attention_dim = 1152;
int64_t caption_channels = 4096;
int64_t num_heads = 16;
int64_t patch_size = 2;
int64_t ffn_dim = 4608;
int64_t pos_embed_base_size = 64;
float interpolation_scale = 2.f;
int num_layers = 28;
static PixArtConfig detect_from_weights(const String2TensorStorage& weights, const std::string& prefix) {
PixArtConfig config;
auto find = [&](const std::string& suffix) -> const TensorStorage* {
auto it = weights.find(prefix + "." + suffix);
return it == weights.end() ? nullptr : &it->second;
};
if (auto w = find("x_embedder.proj.weight")) {
config.hidden_size = w->ne[3];
config.in_channels = w->ne[2];
config.patch_size = w->ne[0];
}
if (auto w = find("final_layer.linear.weight")) {
config.out_channels = w->ne[1] / (config.patch_size * config.patch_size);
}
if (auto w = find("y_embedder.y_proj.fc1.weight")) {
config.caption_channels = w->ne[0];
}
if (auto w = find("blocks.0.cross_attn.kv_linear.weight")) {
config.cross_attention_dim = w->ne[0];
}
if (auto w = find("blocks.0.mlp.fc1.weight")) {
config.ffn_dim = w->ne[1];
}
if (find("csize_embedder.mlp.0.weight") != nullptr) {
LOG_WARN("pixart: resolution/aspect-ratio micro conditions are not supported; output may differ from the reference");
}
int layers = 0;
const std::string block_prefix = prefix + ".blocks.";
for (const auto& [name, _] : weights) {
if (starts_with(name, block_prefix)) {
layers = std::max(layers, atoi(name.substr(block_prefix.size()).c_str()) + 1);
}
}
if (layers > 0) {
config.num_layers = layers;
LOG_VERBOSE("pixart: layers = %d, hidden_size = %" PRId64,
layers, config.hidden_size);
}
return config;
}
};
// Mirrors diffusers get_2d_sincos_pos_embed for a (gh, gw) patch grid.
static std::vector<float> gen_2d_sincos_pos_embed(int64_t dim,
int64_t gh,
int64_t gw,
int64_t base_size,
float interpolation_scale) {
// diffusers: meshgrid(grid_w, grid_h, indexing="xy") -> grid[0]=w, grid[1]=h,
// embedding = concat(sincos(w), sincos(h))
std::vector<float> out(static_cast<size_t>(gh) * gw * dim);
int64_t quarter = dim / 4;
for (int64_t h = 0; h < gh; ++h) {
float pos_h = static_cast<float>(h) / (static_cast<float>(gh) / base_size) / interpolation_scale;
for (int64_t w = 0; w < gw; ++w) {
float pos_w = static_cast<float>(w) / (static_cast<float>(gw) / base_size) / interpolation_scale;
float* dst_w = out.data() + (h * gw + w) * dim;
float* dst_h = dst_w + dim / 2;
for (int64_t i = 0; i < quarter; ++i) {
float omega = 1.f / powf(10000.f, static_cast<float>(i) / quarter);
dst_w[i] = sinf(pos_w * omega);
dst_w[i + quarter] = cosf(pos_w * omega);
dst_h[i] = sinf(pos_h * omega);
dst_h[i + quarter] = cosf(pos_h * omega);
}
}
}
return out;
}
class PixArtTimestepEmbedding : public GGMLBlock {
public:
PixArtTimestepEmbedding(int64_t in_channels, int64_t out_dim) {
blocks["mlp.0"] = std::make_shared<Linear>(in_channels, out_dim);
blocks["mlp.2"] = std::make_shared<Linear>(out_dim, out_dim);
}
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) {
x = std::dynamic_pointer_cast<Linear>(blocks["mlp.0"])->forward(ctx, x);
x = ggml_silu(ctx->ggml_ctx, x);
return std::dynamic_pointer_cast<Linear>(blocks["mlp.2"])->forward(ctx, x);
}
};
class PixArtAttention : public GGMLBlock {
int64_t num_heads;
bool self_attention;
public:
PixArtAttention(int64_t dim, int64_t num_heads, int64_t context_dim, bool self_attention)
: num_heads(num_heads), self_attention(self_attention) {
if (self_attention) {
blocks["qkv"] = std::make_shared<Linear>(dim, 3 * dim);
} else {
blocks["q_linear"] = std::make_shared<Linear>(dim, dim);
blocks["kv_linear"] = std::make_shared<Linear>(context_dim, 2 * dim);
}
blocks["proj"] = std::make_shared<Linear>(dim, dim);
}
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* context, ggml_tensor* mask = nullptr) {
std::vector<ggml_tensor*> qkv;
if (self_attention) {
auto projected = std::dynamic_pointer_cast<Linear>(blocks["qkv"])->forward(ctx, x);
qkv = ggml_ext_chunk(ctx->ggml_ctx, projected, 3, 0);
} else {
auto q = std::dynamic_pointer_cast<Linear>(blocks["q_linear"])->forward(ctx, x);
auto kv = std::dynamic_pointer_cast<Linear>(blocks["kv_linear"])->forward(ctx, context);
auto parts = ggml_ext_chunk(ctx->ggml_ctx, kv, 2, 0);
qkv = {q, parts[0], parts[1]};
}
auto out = ggml_ext_attention_ext(ctx, qkv[0], qkv[1], qkv[2], num_heads, mask, false, ctx->flash_attn_enabled);
return std::dynamic_pointer_cast<Linear>(blocks["proj"])->forward(ctx, out);
}
};
class PixArtBlock : public GGMLBlock {
int64_t dim;
void init_params(ggml_context* ctx,
const String2TensorStorage& tensor_storage_map = {},
const std::string prefix = "") override {
ggml_type wtype = get_type(prefix + "scale_shift_table", tensor_storage_map, GGML_TYPE_F32);
params["scale_shift_table"] = ggml_new_tensor_2d(ctx, wtype, dim, 6);
}
public:
PixArtBlock(int64_t dim, int64_t num_heads, int64_t context_dim, int64_t ffn_dim)
: dim(dim) {
blocks["attn"] = std::make_shared<PixArtAttention>(dim, num_heads, dim, true);
blocks["cross_attn"] = std::make_shared<PixArtAttention>(dim, num_heads, context_dim, false);
blocks["mlp.fc1"] = std::make_shared<Linear>(dim, ffn_dim);
blocks["mlp.fc2"] = std::make_shared<Linear>(ffn_dim, dim);
}
static ggml_tensor* norm(ggml_context* ctx, ggml_tensor* x) {
return ggml_norm(ctx, x, 1e-6f);
}
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* mod, ggml_tensor* context, ggml_tensor* context_mask) {
// x: [N, n_token, dim]
// mod: [N, 6 * dim], shared adaLN-single output
int64_t N = x->ne[2];
auto table = params["scale_shift_table"];
if (table->type != GGML_TYPE_F32) {
table = ggml_cast(ctx->ggml_ctx, table, GGML_TYPE_F32);
}
table = ggml_reshape_3d(ctx->ggml_ctx, table, dim, 6, 1);
auto m = ggml_add(ctx->ggml_ctx, ggml_reshape_3d(ctx->ggml_ctx, mod, dim, 6, N), table);
auto mv = ggml_ext_chunk(ctx->ggml_ctx, ggml_reshape_2d(ctx->ggml_ctx, ggml_ext_cont(ctx->ggml_ctx, m), dim * 6, N), 6, 0);
auto attn1 = std::dynamic_pointer_cast<PixArtAttention>(blocks["attn"]);
auto attn2 = std::dynamic_pointer_cast<PixArtAttention>(blocks["cross_attn"]);
auto proj = std::dynamic_pointer_cast<Linear>(blocks["mlp.fc1"]);
auto fc2 = std::dynamic_pointer_cast<Linear>(blocks["mlp.fc2"]);
auto gate = [&](ggml_tensor* y, ggml_tensor* g) {
g = ggml_reshape_3d(ctx->ggml_ctx, g, dim, 1, N);
return ggml_add(ctx->ggml_ctx, x, ggml_mul(ctx->ggml_ctx, y, g));
};
auto h = modulate(ctx->ggml_ctx, norm(ctx->ggml_ctx, x), mv[0], mv[1]);
x = gate(attn1->forward(ctx, h, h), mv[2]);
// ada_norm_single: no norm before cross-attention (PixArtMS.py)
x = ggml_add(ctx->ggml_ctx, x, attn2->forward(ctx, x, context, context_mask));
h = modulate(ctx->ggml_ctx, norm(ctx->ggml_ctx, x), mv[3], mv[4]);
h = proj->forward(ctx, h);
h = ggml_ext_gelu(ctx->ggml_ctx, h, true);
h = fc2->forward(ctx, h);
return gate(h, mv[5]);
}
};
class PixArtModel : public GGMLBlock {
PixArtConfig config;
void init_params(ggml_context* ctx,
const String2TensorStorage& tensor_storage_map = {},
const std::string prefix = "") override {
ggml_type wtype = get_type(prefix + "final_layer.scale_shift_table", tensor_storage_map, GGML_TYPE_F32);
params["final_layer.scale_shift_table"] = ggml_new_tensor_2d(ctx, wtype, config.hidden_size, 2);
}
public:
PixArtModel() = default;
PixArtModel(const PixArtConfig& config)
: config(config) {
blocks["x_embedder.proj"] = std::make_shared<Conv2d>(config.in_channels,
config.hidden_size,
std::pair<int, int>{static_cast<int>(config.patch_size), static_cast<int>(config.patch_size)},
std::pair<int, int>{static_cast<int>(config.patch_size), static_cast<int>(config.patch_size)});
blocks["t_embedder"] = std::make_shared<PixArtTimestepEmbedding>(ADALN_EMBED_DIM, config.hidden_size);
blocks["t_block.1"] = std::make_shared<Linear>(config.hidden_size, 6 * config.hidden_size);
blocks["y_embedder.y_proj.fc1"] = std::make_shared<Linear>(config.caption_channels, config.hidden_size);
blocks["y_embedder.y_proj.fc2"] = std::make_shared<Linear>(config.hidden_size, config.cross_attention_dim);
for (int i = 0; i < config.num_layers; ++i) {
blocks["blocks." + std::to_string(i)] =
std::make_shared<PixArtBlock>(config.hidden_size, config.num_heads, config.cross_attention_dim, config.ffn_dim);
}
blocks["final_layer.norm_final"] = std::make_shared<LayerNorm>(config.hidden_size, 1e-6f, false);
blocks["final_layer.linear"] = std::make_shared<Linear>(config.hidden_size,
config.patch_size * config.patch_size * config.out_channels);
}
ggml_tensor* forward(GGMLRunnerContext* ctx,
ggml_tensor* x,
ggml_tensor* timesteps,
ggml_tensor* context,
ggml_tensor* pos_embed,
ggml_tensor* context_mask) {
// x: [N, C, H, W] latent, context: [N, n_ctx, caption_channels]
int64_t W = x->ne[0];
int64_t H = x->ne[1];
int64_t N = x->ne[3];
int64_t p = config.patch_size;
int64_t wp = W / p;
int64_t hp = H / p;
auto h = std::dynamic_pointer_cast<Conv2d>(blocks["x_embedder.proj"])->forward(ctx, x); // [N, hidden, hp, wp]
h = ggml_ext_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, h, 1, 2, 0, 3)); // [N, hp, wp, hidden] -> [N, hp*wp, hidden]
h = ggml_reshape_3d(ctx->ggml_ctx, h, config.hidden_size, wp * hp, N); // [N, hp*wp, hidden]
h = ggml_add(ctx->ggml_ctx, h, pos_embed);
auto t = ggml_ext_timestep_embedding(ctx->ggml_ctx, timesteps, ADALN_EMBED_DIM, 10000);
auto emb = std::dynamic_pointer_cast<PixArtTimestepEmbedding>(blocks["t_embedder"])->forward(ctx, t);
auto mod = std::dynamic_pointer_cast<Linear>(blocks["t_block.1"])
->forward(ctx, ggml_silu(ctx->ggml_ctx, emb)); // [N, 6 * hidden]
auto ctx_emb = std::dynamic_pointer_cast<Linear>(blocks["y_embedder.y_proj.fc1"])->forward(ctx, context);
ctx_emb = ggml_ext_gelu(ctx->ggml_ctx, ctx_emb, true);
ctx_emb = std::dynamic_pointer_cast<Linear>(blocks["y_embedder.y_proj.fc2"])->forward(ctx, ctx_emb);
for (int i = 0; i < config.num_layers; ++i) {
auto block = std::dynamic_pointer_cast<PixArtBlock>(blocks["blocks." + std::to_string(i)]);
h = block->forward(ctx, h, mod, ctx_emb, context_mask);
sd::ggml_graph_cut::mark_graph_cut(h, "pixart.blocks." + std::to_string(i), "h");
}
// scale_shift_table + emb -> (shift, scale) for the affine-free final norm
auto tail_table = params["final_layer.scale_shift_table"];
if (tail_table->type != GGML_TYPE_F32) {
tail_table = ggml_cast(ctx->ggml_ctx, tail_table, GGML_TYPE_F32);
}
auto ss = ggml_add(ctx->ggml_ctx,
ggml_reshape_3d(ctx->ggml_ctx, tail_table, config.hidden_size, 2, 1),
ggml_reshape_3d(ctx->ggml_ctx, emb, config.hidden_size, 1, N)); // [2, hidden, N]
auto parts = ggml_ext_chunk(ctx->ggml_ctx,
ggml_reshape_2d(ctx->ggml_ctx, ggml_ext_cont(ctx->ggml_ctx, ss), config.hidden_size * 2, N),
2, 0);
h = std::dynamic_pointer_cast<LayerNorm>(blocks["final_layer.norm_final"])->forward(ctx, h);
h = modulate(ctx->ggml_ctx, h, parts[0], parts[1]);
h = std::dynamic_pointer_cast<Linear>(blocks["final_layer.linear"])->forward(ctx, h); // [N, hp*wp, p*p*out_ch]
h = DiT::unpatchify(ctx->ggml_ctx, h, hp, wp, static_cast<int>(p), static_cast<int>(p), false);
return h; // [N, out_channels, H, W]
}
};
struct PixArtRunner : public DiffusionModelRunner {
PixArtConfig config;
PixArtModel model;
std::vector<float> pos_vec;
PixArtRunner(ggml_backend_t backend,
const String2TensorStorage& tensor_storage_map = {},
const std::string prefix = "",
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr,
const char* model_args = nullptr)
: DiffusionModelRunner(backend, prefix, weight_manager),
config(PixArtConfig::detect_from_weights(tensor_storage_map, prefix)) {
for (const auto& [key, value] : parse_key_value_args(model_args, "model arg")) {
if (key == "pixart_pos_embed_base_size") {
int parsed = 0;
if (parse_strict_int(value, parsed)) {
config.pos_embed_base_size = parsed;
} else {
LOG_WARN("ignoring invalid PixArt model arg '%s=%s'", key.c_str(), value.c_str());
}
} else if (key == "pixart_interpolation_scale") {
float parsed = 0.f;
if (parse_strict_float(value, parsed)) {
config.interpolation_scale = parsed;
} else {
LOG_WARN("ignoring invalid PixArt model arg '%s=%s'", key.c_str(), value.c_str());
}
}
}
model = PixArtModel(config);
model.init(params_ctx, tensor_storage_map, prefix);
}
std::string get_desc() override {
return "pixart";
}
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors, const std::string& prefix) override {
model.get_param_tensors(tensors, prefix);
}
ggml_cgraph* build_graph(const sd::Tensor<float>& x_tensor,
const sd::Tensor<float>& timesteps_tensor,
const sd::Tensor<float>& context_tensor,
const sd::Tensor<float>& mask_tensor) {
ggml_cgraph* gf = new_graph_custom(PIXART_GRAPH_SIZE);
ggml_tensor* x = make_input(x_tensor);
ggml_tensor* timesteps = make_input(timesteps_tensor);
GGML_ASSERT(x->ne[3] == 1);
GGML_ASSERT(!context_tensor.empty());
ggml_tensor* context = make_input(context_tensor);
ggml_tensor* context_mask = nullptr;
if (!mask_tensor.empty()) {
// additive attention bias over context tokens: 0 keep / -inf discard
context_mask = ggml_reshape_4d(compute_ctx, make_input(mask_tensor), mask_tensor.shape()[0], 1, 1, 1);
}
int64_t W = x->ne[0];
int64_t H = x->ne[1];
int64_t wp = W / config.patch_size;
int64_t hp = H / config.patch_size;
pos_vec = gen_2d_sincos_pos_embed(config.hidden_size, hp, wp,
config.pos_embed_base_size, config.interpolation_scale);
auto pos = ggml_new_tensor_3d(compute_ctx, GGML_TYPE_F32, config.hidden_size, wp * hp, 1);
set_backend_tensor_data(pos, pos_vec.data());
auto runner_ctx = get_context();
ggml_tensor* out = model.forward(&runner_ctx, x, timesteps, context, pos, context_mask);
// learn_sigma: keep the noise prediction half of the output channels
out = ggml_ext_slice(compute_ctx, out, 2, 0, config.in_channels);
ggml_build_forward_expand(gf, out);
return gf;
}
sd::Tensor<float> compute(int n_threads,
const sd::Tensor<float>& x,
const sd::Tensor<float>& timesteps,
const sd::Tensor<float>& context,
const sd::Tensor<float>& context_mask) {
auto get_graph = [&]() -> ggml_cgraph* {
return build_graph(x, timesteps, context, context_mask);
};
return restore_trailing_singleton_dims(GGMLRunner::compute(get_graph, n_threads, false), x.dim());
}
sd::Tensor<float> compute(int n_threads,
const DiffusionParams& diffusion_params) override {
GGML_ASSERT(diffusion_params.x != nullptr);
GGML_ASSERT(diffusion_params.timesteps != nullptr);
auto context = tensor_or_empty(diffusion_params.context);
auto context_msk = tensor_or_empty(diffusion_params.y);
return compute(n_threads,
*diffusion_params.x,
*diffusion_params.timesteps,
context,
context_msk);
}
};
} // namespace PixArt
#endif // __SD_MODEL_DIFFUSION_PIXART_HPP__
+2 -2
View File
@@ -544,7 +544,7 @@ public:
const String2TensorStorage& tensor_storage_map = {},
const std::string& prefix = "")
: version(version), decode_only(decode_only), use_video_decoder(use_video_decoder) {
if (sd_version_is_dit(version)) {
if (sd_version_is_dit(version) && version != VERSION_PIXART) {
if (sd_version_uses_flux2_vae(version)) {
dd_config.z_channels = 32;
embed_dim = 32;
@@ -678,7 +678,7 @@ struct AutoEncoderKL : public VAE {
if (sd_version_is_sd1(version) || sd_version_is_sd2(version)) {
scale_factor = 0.18215f;
shift_factor = 0.f;
} else if (sd_version_is_sdxl(version)) {
} else if (sd_version_is_sdxl(version) || sd_version_is_pixart(version)) {
scale_factor = 0.13025f;
shift_factor = 0.f;
} else if (sd_version_is_sd3(version)) {
+12 -6
View File
@@ -556,12 +556,18 @@ namespace MiniMaxH3VAE {
tensor.shape()[3]});
}
static sd_tiling_params_t h3_tiling(sd_tiling_params_t params) {
sd_tiling_params_t resolve_tiling_params(sd_tiling_params_t params) const override {
if (!params.enabled) {
params.target_overlap = 0.25f;
}
if (params.tile_size_w == 0 && params.rel_size_w == 0.f) {
params.tile_size_w = 256;
}
if (params.tile_size_h == 0 && params.rel_size_h == 0.f) {
params.tile_size_h = 256;
}
params.enabled = true;
params.temporal_tiling = false;
params.tile_size_x = 16;
params.tile_size_y = 16;
params.target_overlap = 0.25f;
return params;
}
@@ -605,7 +611,7 @@ namespace MiniMaxH3VAE {
bool circular_x = false,
bool circular_y = false) override {
auto input = ensure_video_shape(x);
auto tiling = h3_tiling(tiling_params);
auto tiling = resolve_tiling_params(tiling_params);
if (input.shape()[2] == 1) {
auto encoded = VAE::encode(n_threads, input, tiling, circular_x, circular_y);
if (!encoded.empty() && encoded.shape()[2] > 1) {
@@ -646,7 +652,7 @@ namespace MiniMaxH3VAE {
bool circular_y = false,
bool silent = false) override {
auto input = ensure_video_shape(x);
auto tiling = h3_tiling(tiling_params);
auto tiling = resolve_tiling_params(tiling_params);
if (input.shape()[2] == 1) {
auto decoded = VAE::decode(n_threads,
input,
+1 -1
View File
@@ -701,7 +701,7 @@ public:
bool use_midblock_gn = false;
taef2 = sd_version_uses_flux2_vae(version);
if (sd_version_is_dit(version)) {
if (sd_version_is_dit(version) && !sd_version_is_pixart(version)) {
z_channels = 16;
}
if (taef2) {
+82 -50
View File
@@ -1,6 +1,9 @@
#ifndef __SD_MODEL_VAE_VAE_HPP__
#define __SD_MODEL_VAE_VAE_HPP__
#include <cmath>
#include <limits>
#include "core/tensor_ggml.hpp"
#include "model/common/block.hpp"
#include "model/vae/vae_tiling.hpp"
@@ -117,8 +120,8 @@ protected:
int output_width,
int output_height,
int scale,
int p_tile_size_x,
int p_tile_size_y,
int p_tile_size_w,
int p_tile_size_h,
float tile_overlap_factor,
bool circular_x,
bool circular_y,
@@ -138,17 +141,28 @@ protected:
}
return output_tile;
};
return ::process_tiles_2d(input,
output_width,
output_height,
scale,
p_tile_size_x,
p_tile_size_y,
tile_overlap_factor,
circular_x,
circular_y,
on_processing,
silent);
const bool original_circular_x = circular_x_enabled;
const bool original_circular_y = circular_y_enabled;
const int64_t latent_width = decode_graph ? input.shape()[0] : output_width;
const int64_t latent_height = decode_graph ? input.shape()[1] : output_height;
circular_x = circular_x || original_circular_x;
circular_y = circular_y || original_circular_y;
// Full-width axes wrap in convolutions; split axes wrap between tiles.
set_circular_axes(circular_x && p_tile_size_w >= latent_width,
circular_y && p_tile_size_h >= latent_height);
auto output = ::process_tiles_2d(input,
output_width,
output_height,
scale,
p_tile_size_w,
p_tile_size_h,
tile_overlap_factor,
circular_x && p_tile_size_w < latent_width,
circular_y && p_tile_size_h < latent_height,
on_processing,
silent);
set_circular_axes(original_circular_x, original_circular_y);
return output;
}
public:
@@ -178,33 +192,48 @@ public:
return supports_temporal_tiling(VAETemporalDirection::DECODE);
}
void get_tile_sizes(int& tile_size_x,
int& tile_size_y,
virtual sd_tiling_params_t resolve_tiling_params(sd_tiling_params_t params) const {
return params;
}
bool get_tile_sizes(int& tile_size_w,
int& tile_size_h,
float& tile_overlap,
const sd_tiling_params_t& params,
int64_t latent_x,
int64_t latent_y,
float encoding_factor = 1.0f) {
tile_overlap = std::max(std::min(params.target_overlap, 0.5f), 0.0f);
auto get_tile_size = [&](int requested_size, float factor, int64_t latent_size) {
const int default_tile_size = 32;
const int min_tile_dimension = 4;
int tile_size = default_tile_size;
// factor <= 1 means simple fraction of the latent dimension
// factor > 1 means number of tiles across that dimension
if (factor > 0.f) {
if (factor > 1.0)
factor = 1 / (factor - factor * tile_overlap + tile_overlap);
tile_size = static_cast<int>(std::round(latent_size * factor));
} else if (requested_size >= min_tile_dimension) {
tile_size = requested_size;
int64_t latent_w,
int64_t latent_h) {
const auto tiling = resolve_tiling_params(params);
if (latent_w <= 0 || latent_h <= 0 ||
latent_w > std::numeric_limits<int>::max() || latent_h > std::numeric_limits<int>::max() ||
!std::isfinite(tiling.target_overlap)) {
LOG_ERROR("invalid VAE tiling dimensions or overlap");
return false;
}
const int scale_factor = get_scale_factor();
tile_overlap = std::max(std::min(tiling.target_overlap, 0.5f), 0.0f);
auto get_tile_size = [&](int requested_size, double factor, int64_t latent_size, int& tile_size) {
if (requested_size < 0 || !std::isfinite(factor) || factor < 0.0) {
LOG_ERROR("VAE tile sizes and relative sizes must be finite and non-negative");
return false;
}
tile_size = static_cast<int>(tile_size * encoding_factor);
return std::max(std::min(tile_size, static_cast<int>(latent_size)), min_tile_dimension);
const int min_tile_dimension = std::min(4, static_cast<int>(latent_size));
double size = (requested_size > 0 ? requested_size : 256) / scale_factor;
if (factor > 0.0) {
if (factor > 1.0) {
factor = 1.0 / (factor * (1.0 - tile_overlap) + tile_overlap);
}
size = std::floor(static_cast<double>(latent_size) * factor);
}
if (size < min_tile_dimension && (requested_size > 0 || factor > 0.0)) {
LOG_ERROR("VAE tile size must be at least %d image pixels on this axis", min_tile_dimension * scale_factor);
return false;
}
tile_size = static_cast<int>(std::min(static_cast<double>(latent_size), std::max<double>(min_tile_dimension, size)));
return true;
};
tile_size_x = get_tile_size(params.tile_size_x, params.rel_size_x, latent_x);
tile_size_y = get_tile_size(params.tile_size_y, params.rel_size_y, latent_y);
return get_tile_size(tiling.tile_size_w, tiling.rel_size_w, latent_w, tile_size_w) &&
get_tile_size(tiling.tile_size_h, tiling.rel_size_h, latent_h, tile_size_h);
}
virtual sd::Tensor<float> encode(int n_threads,
@@ -213,6 +242,7 @@ public:
bool circular_x = false,
bool circular_y = false) {
int64_t t0 = ggml_time_ms();
tiling_params = resolve_tiling_params(tiling_params);
sd::Tensor<float> input = x;
sd::Tensor<float> output;
if (scale_input) {
@@ -224,21 +254,19 @@ public:
int64_t W = input.shape()[0] / scale_factor;
int64_t H = input.shape()[1] / scale_factor;
float tile_overlap;
int tile_size_x, tile_size_y;
// Image VAE encode is more sensitive to tile boundary context than decode.
// Keep the smaller legacy factor for video VAEs, but default image encode
// tiles to 64 latent pixels so a 512px SD image is encoded as one tile.
const float encode_tile_factor = sd_version_is_minimax_h3(version) ? 1.f : (sd_version_is_wan(version) || sd_version_is_hunyuan_video(version) || sd_version_is_ltxav(version)) ? 1.30539f
: 2.0f;
get_tile_sizes(tile_size_x, tile_size_y, tile_overlap, tiling_params, W, H, encode_tile_factor);
LOG_VERBOSE("VAE Tile size: %dx%d", tile_size_x, tile_size_y);
int tile_size_w, tile_size_h;
if (!get_tile_sizes(tile_size_w, tile_size_h, tile_overlap, tiling_params, W, H)) {
return {};
}
LOG_VERBOSE("VAE encode tile size: %dx%d pixels (%dx%d latent)",
tile_size_w * scale_factor, tile_size_h * scale_factor, tile_size_w, tile_size_h);
output = tiled_compute(input,
n_threads,
static_cast<int>(W),
static_cast<int>(H),
scale_factor,
tile_size_x,
tile_size_y,
tile_size_w,
tile_size_h,
tile_overlap,
circular_x,
circular_y,
@@ -271,6 +299,7 @@ public:
bool circular_y = false,
bool silent = false) {
int64_t t0 = ggml_time_ms();
tiling_params = resolve_tiling_params(tiling_params);
sd::Tensor<float> input = x;
sd::Tensor<float> output;
@@ -279,10 +308,13 @@ public:
int64_t W = input.shape()[0] * scale_factor;
int64_t H = input.shape()[1] * scale_factor;
float tile_overlap;
int tile_size_x, tile_size_y;
get_tile_sizes(tile_size_x, tile_size_y, tile_overlap, tiling_params, input.shape()[0], input.shape()[1]);
int tile_size_w, tile_size_h;
if (!get_tile_sizes(tile_size_w, tile_size_h, tile_overlap, tiling_params, input.shape()[0], input.shape()[1])) {
return {};
}
if (!silent) {
LOG_VERBOSE("VAE Tile size: %dx%d", tile_size_x, tile_size_y);
LOG_VERBOSE("VAE decode tile size: %dx%d pixels (%dx%d latent)",
tile_size_w * scale_factor, tile_size_h * scale_factor, tile_size_w, tile_size_h);
}
output = tiled_compute(
input,
@@ -290,8 +322,8 @@ public:
static_cast<int>(W),
static_cast<int>(H),
scale_factor,
tile_size_x,
tile_size_y,
tile_size_w,
tile_size_h,
tile_overlap,
circular_x,
circular_y,
+7 -3
View File
@@ -47,10 +47,14 @@ bool read_gguf_file(const std::string& file_path,
gguf_context* ctx_gguf_ = nullptr;
ggml_context* ctx_meta_ = nullptr;
ctx_gguf_ = gguf_init_from_file(file_path.c_str(), {true, &ctx_meta_});
GGUFReader gguf_reader;
bool probe_ok = gguf_reader.load(file_path);
if (!probe_ok || !gguf_reader.has_tensors_beyond_ggml_limits()) {
ctx_gguf_ = gguf_init_from_file(file_path.c_str(), {true, &ctx_meta_});
}
if (!ctx_gguf_) {
GGUFReader gguf_reader;
if (!gguf_reader.load(file_path)) {
if (!probe_ok && !gguf_reader.load(file_path)) {
set_error(error, "failed to open '" + file_path + "' with GGUFReader");
return false;
}
+71 -51
View File
@@ -35,23 +35,72 @@ enum class GGUFMetadataType : uint32_t {
class GGUFReader {
private:
std::vector<GGUFTensorInfo> tensors_;
bool has_wide_tensors_ = false;
uint64_t remaining_bytes_ = 0;
size_t data_offset_;
size_t alignment_ = 32; // default alignment is 32
template <typename T>
bool safe_read(std::ifstream& fin, T& value) {
fin.read(reinterpret_cast<char*>(&value), sizeof(T));
return fin.good();
return safe_read(fin, reinterpret_cast<char*>(&value), sizeof(T));
}
bool safe_read(std::ifstream& fin, char* buffer, size_t size) {
if (size > remaining_bytes_)
return false;
fin.read(buffer, size);
return fin.good();
if (!fin.good())
return false;
remaining_bytes_ -= size;
return true;
}
bool safe_seek(std::ifstream& fin, std::streamoff offset, std::ios::seekdir dir) {
fin.seekg(offset, dir);
return fin.good();
bool safe_skip(std::ifstream& fin, uint64_t count, uint64_t element_size = 1) {
if (count > remaining_bytes_ / element_size)
return false;
uint64_t size = count * element_size;
fin.seekg(static_cast<std::streamoff>(size), std::ios::cur);
if (!fin.good())
return false;
remaining_bytes_ -= size;
return true;
}
bool skip_metadata_values(std::ifstream& fin, GGUFMetadataType type, uint64_t count) {
switch (type) {
case GGUFMetadataType::UINT8:
case GGUFMetadataType::INT8:
case GGUFMetadataType::BOOL:
return safe_skip(fin, count);
case GGUFMetadataType::UINT16:
case GGUFMetadataType::INT16:
return safe_skip(fin, count, 2);
case GGUFMetadataType::UINT32:
case GGUFMetadataType::INT32:
case GGUFMetadataType::FLOAT32:
return safe_skip(fin, count, 4);
case GGUFMetadataType::UINT64:
case GGUFMetadataType::INT64:
case GGUFMetadataType::FLOAT64:
return safe_skip(fin, count, 8);
case GGUFMetadataType::STRING:
if (count > remaining_bytes_ / sizeof(uint64_t))
return false;
for (uint64_t i = 0; i < count; i++) {
uint64_t len = 0;
if (!safe_read(fin, len) || !safe_skip(fin, len))
return false;
}
return true;
default:
LOG_ERROR("Unknown metadata type=%u", static_cast<uint32_t>(type));
return false;
}
}
bool read_metadata(std::ifstream& fin) {
@@ -84,52 +133,12 @@ private:
return true;
}
switch (static_cast<GGUFMetadataType>(type)) {
case GGUFMetadataType::UINT8:
case GGUFMetadataType::INT8:
case GGUFMetadataType::BOOL:
return safe_seek(fin, 1, std::ios::cur);
case GGUFMetadataType::UINT16:
case GGUFMetadataType::INT16:
return safe_seek(fin, 2, std::ios::cur);
case GGUFMetadataType::UINT32:
case GGUFMetadataType::INT32:
case GGUFMetadataType::FLOAT32:
return safe_seek(fin, 4, std::ios::cur);
case GGUFMetadataType::UINT64:
case GGUFMetadataType::INT64:
case GGUFMetadataType::FLOAT64:
return safe_seek(fin, 8, std::ios::cur);
case GGUFMetadataType::STRING: {
uint64_t len = 0;
if (!safe_read(fin, len))
return false;
return safe_seek(fin, len, std::ios::cur);
}
case GGUFMetadataType::ARRAY: {
uint32_t elem_type = 0;
uint64_t len = 0;
if (!safe_read(fin, elem_type))
return false;
if (!safe_read(fin, len))
return false;
for (uint64_t i = 0; i < len; i++) {
if (!read_metadata(fin))
return false;
}
return true;
}
default:
LOG_ERROR("Unknown metadata type=%u", type);
uint64_t count = 1;
if (type == static_cast<uint32_t>(GGUFMetadataType::ARRAY)) {
if (!safe_read(fin, type) || !safe_read(fin, count))
return false;
}
return skip_metadata_values(fin, static_cast<GGUFMetadataType>(type), count);
}
GGUFTensorInfo read_tensor_info(std::ifstream& fin) {
@@ -154,6 +163,7 @@ private:
}
if (n_dims > GGML_MAX_DIMS) {
has_wide_tensors_ = true;
for (uint32_t i = GGML_MAX_DIMS; i < n_dims; i++) {
info.shape[GGML_MAX_DIMS - 1] *= info.shape[i]; // stack to last dim;
}
@@ -174,12 +184,20 @@ private:
public:
bool load(const std::string& file_path) {
std::ifstream fin(file_path, std::ios::binary);
std::ifstream fin(file_path, std::ios::binary | std::ios::ate);
if (!fin) {
LOG_ERROR("failed to open '%s'", file_path.c_str());
return false;
}
std::streamoff file_size = fin.tellg();
if (file_size < 0)
return false;
remaining_bytes_ = static_cast<uint64_t>(file_size);
fin.seekg(0, std::ios::beg);
if (!fin.good())
return false;
// --- Header ---
char magic[4];
if (!safe_read(fin, magic, 4) || strncmp(magic, "GGUF", 4) != 0) {
@@ -228,6 +246,8 @@ public:
}
const std::vector<GGUFTensorInfo>& tensors() const { return tensors_; }
bool has_tensors_beyond_ggml_limits() const { return has_wide_tensors_; }
size_t data_offset() const { return data_offset_; }
};
+27 -5
View File
@@ -322,7 +322,13 @@ bool ModelLoader::init_from_safetensors_index_file(const std::string& file_path,
}
for (const std::string& shard_path : shard_paths) {
if (!parse_file(shard_path, prefix)) {
FileStamp stamp;
if (!read_file_stamp(shard_path, stamp)) {
return false;
}
parsed_dependencies_.push_back(stamp);
LOG_INFO("load %s using safetensors format", shard_path.c_str());
if (!init_from_safetensors_file(shard_path, prefix)) {
return false;
}
}
@@ -526,7 +532,18 @@ SDVersion ModelLoader::get_sd_version() const {
if (tensor_storage.name.find("model.diffusion_model.layers.0.adaLN_sa_ln.weight") != std::string::npos) {
return VERSION_ERNIE_IMAGE;
}
if (tensor_storage.name.find("model.diffusion_model.t_block.1.weight") != std::string::npos &&
tensor_storage_map.find("model.diffusion_model.x_embedder.proj.weight") != tensor_storage_map.end() &&
tensor_storage_map.find("model.diffusion_model.audio_patchify_proj.weight") == tensor_storage_map.end()) {
return VERSION_PIXART;
}
if (tensor_storage.name.find("model.diffusion_model.adaln_single.emb.timestep_embedder.linear_1.bias") != std::string::npos) {
// PixArt shares this timestep embedding with LTX-AV.
if (tensor_storage_map.find("model.diffusion_model.pos_embed.proj.weight") != tensor_storage_map.end() &&
tensor_storage_map.find("model.diffusion_model.adaln_single.linear.weight") != tensor_storage_map.end() &&
tensor_storage_map.find("model.diffusion_model.audio_patchify_proj.weight") == tensor_storage_map.end()) {
return VERSION_PIXART;
}
return VERSION_LTXAV;
}
if (tensor_storage.name.find("model.diffusion_model.video_patch_proj.weight") != std::string::npos &&
@@ -1084,10 +1101,12 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb,
if (tensors_to_process.empty()) {
continue;
}
LOG_VERBOSE("loading %zu/%zu tensors from %s",
tensors_to_process.size(),
file_tensors.size(),
file_path.c_str());
if (log_progress) {
LOG_VERBOSE("loading %zu/%zu tensors from %s",
tensors_to_process.size(),
file_tensors.size(),
file_path.c_str());
}
bool is_zip = fdata.is_zip;
@@ -1591,6 +1610,9 @@ bool ModelLoader::tensor_should_be_converted(const TensorStorage& tensor_storage
// Pass, do not convert. For Unet
} else if (contains(name, "embedding")) {
// Pass, do not convert embedding
} else if (contains(name, "scale_shift_table")) {
// Pass, do not convert. adaLN modulation tables (PixArt, LTXV) are sliced
// element-wise, which is invalid on quantized block layouts.
} else if (ends_with(name, "_pad_token")) {
// Pass, do not convert. LLaDA-Image stores its pad tokens far outside the f16
// range, so any format with an f16 scale or payload turns them into inf.
+93 -14
View File
@@ -246,17 +246,13 @@ bool ModelManager::register_param_tensors(ModelComponent component,
}
ggml_set_name(tensor, name.c_str());
auto state = std::make_unique<TensorState>();
state->name = name;
state->tensor = tensor;
state->component = component;
state->source_file = source_file;
state->source_version = source_version;
auto source = sources.find(name);
if (source != sources.end()) {
state->source = source->second;
state->has_source = true;
}
auto state = std::make_unique<TensorState>();
state->name = name;
state->tensor = tensor;
state->component = component;
state->source_file = source_file;
state->source_version = source_version;
state->sources = find_tensor_sources(*state, sources);
state->residency_mode = residency_mode;
state->compute_backend = compute_backend;
state->params_backend = params_backend;
@@ -757,12 +753,33 @@ bool ModelManager::validate_tensor(const TensorState& state) const {
return true;
}
if (!state.has_source) {
if (state.sources.empty()) {
LOG_ERROR("%s tensor '%s' not in model metadata", model_component_name(state.component), state.name.c_str());
return false;
}
const TensorStorage& tensor_storage = state.source;
TensorStorage tensor_storage = state.sources.front();
if (state.sources.size() > 1) {
const int dim = tensor_storage.n_dims - 1;
if (dim < 0 || dim >= GGML_MAX_DIMS) {
return false;
}
tensor_storage.ne[dim] = 0;
for (const auto& part : state.sources) {
if (part.n_dims != tensor_storage.n_dims || part.ne[dim] < 0 ||
part.ne[dim] > state.tensor->ne[dim] - tensor_storage.ne[dim]) {
LOG_ERROR("invalid tensor part '%s' for '%s'", part.name.c_str(), state.name.c_str());
return false;
}
for (int i = 0; i < GGML_MAX_DIMS; ++i) {
if (i != dim && part.ne[i] != tensor_storage.ne[i]) {
LOG_ERROR("incompatible tensor part '%s' for '%s'", part.name.c_str(), state.name.c_str());
return false;
}
}
tensor_storage.ne[dim] += part.ne[dim];
}
}
if (state.tensor->ne[0] != tensor_storage.ne[0] ||
state.tensor->ne[1] != tensor_storage.ne[1] ||
state.tensor->ne[2] != tensor_storage.ne[2] ||
@@ -831,7 +848,7 @@ bool ModelManager::mmap_params(const std::vector<TensorState*>& states,
}
bool ModelManager::can_mmap_storage(const TensorState& state) const {
if (state.source_file != 0 || !enable_mmap_ || state.residency_mode != ResidencyMode::ParamBackend) {
if (state.sources.size() > 1 || state.source_file != 0 || !enable_mmap_ || state.residency_mode != ResidencyMode::ParamBackend) {
return false;
}
if (state.compute_backend == nullptr || state.params_backend == nullptr) {
@@ -941,6 +958,62 @@ bool ModelManager::alloc_params_buffers(const std::vector<TensorState*>& states,
return true;
}
bool ModelManager::load_tensor_parts(TensorState& state) {
auto ctx = std::unique_ptr<ggml_context, decltype(&ggml_free)>(
ggml_init({state.sources.size() * ggml_tensor_overhead(), nullptr, true}), ggml_free);
if (!ctx) {
return false;
}
const size_t size = ggml_nbytes(state.tensor);
std::vector<uint8_t> buffer;
void* data = state.tensor->data;
if (!ggml_backend_buffer_is_host(state.tensor->buffer)) {
buffer.resize(size);
data = buffer.data();
}
std::map<std::string, ggml_tensor*> parts;
std::set<std::string> names;
size_t offset = 0;
for (const auto& source : state.sources) {
auto part = ggml_new_tensor(ctx.get(), state.tensor->type, source.n_dims, source.ne);
const size_t part_size = ggml_nbytes(part);
if (part_size > size - offset) {
return false;
}
part->data = static_cast<uint8_t*>(data) + offset;
parts[source.name] = part;
names.insert(source.name);
offset += part_size;
}
if (offset != size) {
return false;
}
std::set<std::string> loaded;
std::mutex mutex;
auto callback = [&](const TensorStorage& source, ggml_tensor** dst) {
*dst = nullptr;
auto part = parts.find(source.name);
if (part != parts.end()) {
*dst = part->second;
std::lock_guard<std::mutex> lock(mutex);
loaded.insert(source.name);
}
return true;
};
const bool success = state.source_file == 0
? model_loader_.load_tensors(callback, enable_mmap_, &names, false)
: model_loader_.load_file_tensors(state.source_file, state.source_version, callback, names, enable_mmap_);
if (!success || loaded != names) {
return false;
}
if (!buffer.empty()) {
// Upload the assembled tensor once, including for row-split backend buffers.
ggml_backend_tensor_set(state.tensor, buffer.data(), 0, size);
}
state.loaded_to_params_backend = true;
return true;
}
bool ModelManager::load_tensors(const std::vector<TensorState*>& states) {
using ReadGroup = std::pair<ModelLoader::FileId, SDVersion>;
using ReadBatch = std::map<std::string, std::vector<TensorState*>>;
@@ -948,6 +1021,12 @@ bool ModelManager::load_tensors(const std::vector<TensorState*>& states) {
for (auto* state : states) {
if (state == nullptr)
continue;
if (state->sources.size() > 1) {
if (!load_tensor_parts(*state)) {
return false;
}
continue;
}
auto& batches = groups[{state->source_file, state->source_version}];
// The loader supplies one destination per name; only conflicting types need another batch.
auto batch = std::find_if(batches.begin(), batches.end(), [&](const ReadBatch& candidate) {
+3 -2
View File
@@ -38,8 +38,7 @@ private:
std::string name;
ggml_tensor* tensor = nullptr;
ModelComponent component = ModelComponent::Count;
TensorStorage source;
bool has_source = false;
std::vector<TensorStorage> sources;
ModelLoader::FileId source_file = 0;
SDVersion source_version = VERSION_COUNT;
@@ -138,6 +137,8 @@ private:
bool apply_loras_to_params(const std::vector<TensorState*>& states);
bool mmap_params(const std::vector<TensorState*>& states,
std::vector<ParamsStorageBlock*>& created_storage_blocks);
static std::vector<TensorStorage> find_tensor_sources(const TensorState& state, const String2TensorStorage& sources);
bool load_tensor_parts(TensorState& state);
bool can_mmap_storage(const TensorState& state) const;
bool alloc_params_buffers(const std::vector<TensorState*>& states,
std::vector<ParamsStorageBlock*>& created_storage_blocks);
+26 -11
View File
@@ -15,6 +15,26 @@ static bool same_tensor_source(const TensorStorage& a, const TensorStorage& b) {
a.int8_convrot_group_size == b.int8_convrot_group_size;
}
std::vector<TensorStorage> ModelManager::find_tensor_sources(const TensorState& state, const String2TensorStorage& sources) {
auto first = sources.find(state.name);
if (first == sources.end()) {
return {};
}
std::vector<TensorStorage> result{first->second};
if (state.component == ModelComponent::LoRA ||
std::equal(first->second.ne, first->second.ne + GGML_MAX_DIMS, state.tensor->ne)) {
return result;
}
for (size_t i = 1;; ++i) {
auto part = sources.find(state.name + "." + std::to_string(i));
if (part == sources.end()) {
break;
}
result.push_back(part->second);
}
return result;
}
void ModelManager::invalidate_sources(const std::unordered_set<TensorState*>& states) {
auto affected = states;
for (const auto& block : params_storage_blocks_) {
@@ -76,20 +96,16 @@ bool ModelManager::set_loader(ModelLoader loader) {
}
std::unordered_set<TensorState*> changed;
for (const auto& state : tensor_states_) {
const auto& sources = sources_for(*state);
auto source = sources.find(state->name);
const bool found = source != sources.end();
if (found != state->has_source || (found && !same_tensor_source(state->source, source->second)) ||
const auto sources = find_tensor_sources(*state, sources_for(*state));
if (sources.size() != state->sources.size() ||
!std::equal(sources.begin(), sources.end(), state->sources.begin(), same_tensor_source) ||
(lora_changed && state->component != ModelComponent::LoRA && state->applied_lora_epoch != UINT64_MAX)) {
changed.insert(state.get());
}
}
invalidate_sources(changed);
for (auto* state : changed) {
const auto& sources = sources_for(*state);
auto source = sources.find(state->name);
state->has_source = source != sources.end();
state->source = state->has_source ? source->second : TensorStorage{};
state->sources = find_tensor_sources(*state, sources_for(*state));
}
if (lora_changed) {
++current_lora_epoch_;
@@ -134,9 +150,8 @@ ModelLoader::FileVersions ModelManager::source_versions(const std::set<ModelComp
versions[state->source_file] = loader.file_revision(state->source_file);
continue;
}
auto source = sources.find(state->name);
if (source != sources.end()) {
versions[source->second.file_id] = source->second.file_revision;
for (const auto& source : find_tensor_sources(*state, sources)) {
versions[source.file_id] = source.file_revision;
}
}
return versions;
+64
View File
@@ -932,6 +932,68 @@ static bool is_diffusers_controlnet_name(const std::string& name) {
return false;
}
static std::string convert_diffusers_dit_to_original_pixart(std::string name) {
static const std::vector<std::pair<std::string, std::string>> prefix_map = {
{"pos_embed.proj.", "x_embedder.proj."},
{"adaln_single.emb.timestep_embedder.linear_1.", "t_embedder.mlp.0."},
{"adaln_single.emb.timestep_embedder.linear_2.", "t_embedder.mlp.2."},
{"adaln_single.emb.resolution_embedder.linear_1.", "csize_embedder.mlp.0."},
{"adaln_single.emb.resolution_embedder.linear_2.", "csize_embedder.mlp.2."},
{"adaln_single.emb.aspect_ratio_embedder.linear_1.", "ar_embedder.mlp.0."},
{"adaln_single.emb.aspect_ratio_embedder.linear_2.", "ar_embedder.mlp.2."},
{"adaln_single.linear.", "t_block.1."},
{"caption_projection.linear_1.", "y_embedder.y_proj.fc1."},
{"caption_projection.linear_2.", "y_embedder.y_proj.fc2."},
{"proj_out.", "final_layer.linear."},
};
for (const auto& entry : prefix_map) {
if (starts_with(name, entry.first)) {
return entry.second + name.substr(entry.first.size());
}
}
if (name == "scale_shift_table") {
return "final_layer.scale_shift_table";
}
const std::string block_prefix = "transformer_blocks.";
if (!starts_with(name, block_prefix)) {
return name;
}
const size_t block_end = name.find('.', block_prefix.size());
if (block_end == std::string::npos) {
return name;
}
const std::string prefix = "blocks." + name.substr(block_prefix.size(), block_end - block_prefix.size()) + ".";
name = name.substr(block_end + 1);
static const std::vector<std::pair<std::string, std::string>> block_map = {
{"attn1.to_q.", "attn.qkv."},
{"attn1.to_out.0.", "attn.proj."},
{"attn2.to_q.", "cross_attn.q_linear."},
{"attn2.to_k.", "cross_attn.kv_linear."},
{"attn2.to_out.0.", "cross_attn.proj."},
{"ff.net.0.proj.", "mlp.fc1."},
{"ff.net.2.", "mlp.fc2."},
};
for (const auto& entry : block_map) {
if (starts_with(name, entry.first)) {
return prefix + entry.second + name.substr(entry.first.size());
}
}
static const std::vector<std::pair<std::string, std::string>> part_map = {
{"attn1.to_k.weight", "attn.qkv.weight.1"},
{"attn1.to_k.bias", "attn.qkv.bias.1"},
{"attn1.to_v.weight", "attn.qkv.weight.2"},
{"attn1.to_v.bias", "attn.qkv.bias.2"},
{"attn2.to_v.weight", "cross_attn.kv_linear.weight.1"},
{"attn2.to_v.bias", "cross_attn.kv_linear.bias.1"},
};
for (const auto& entry : part_map) {
if (name == entry.first || starts_with(name, entry.first + ".")) {
return prefix + entry.second + name.substr(entry.first.size());
}
}
return prefix + name;
}
std::string convert_diffusion_model_name(std::string name, std::string prefix, SDVersion version) {
if (sd_version_is_sd1(version) || sd_version_is_sd2(version)) {
name = convert_diffusers_unet_to_original_sd1(name);
@@ -951,6 +1013,8 @@ std::string convert_diffusion_model_name(std::string name, std::string prefix, S
name = convert_other_dit_to_original_anima(name);
} else if (sd_version_is_krea2(version)) {
name = convert_diffusers_dit_to_original_krea2(name);
} else if (sd_version_is_pixart(version)) {
name = convert_diffusers_dit_to_original_pixart(name);
}
return name;
}
+39 -8
View File
@@ -105,6 +105,7 @@ const char* model_version_to_str[] = {
"SenseNova U1.5",
"LLaDA-Image",
"ESRGAN",
"PixArt",
};
static_assert(VERSION_COUNT == sizeof(model_version_to_str) / sizeof(model_version_to_str[0]),
@@ -125,6 +126,18 @@ void calculate_alphas_cumprod(float* alphas_cumprod,
}
}
void calculate_alphas_cumprod_linear_beta(float* alphas_cumprod,
float beta_start,
float beta_end,
int timesteps = TIMESTEPS) {
float product = 1.0f;
for (int i = 0; i < timesteps; i++) {
float beta = beta_start + (beta_end - beta_start) * ((float)i / (timesteps - 1));
product *= 1.0f - beta;
alphas_cumprod[i] = product;
}
}
template <typename T, typename = void>
struct has_set_runtime_backends : std::false_type {};
template <typename T>
@@ -666,6 +679,10 @@ void StableDiffusionGGML::refresh_compvis_denoiser_sigmas() {
std::vector<float> alphas_cumprod(TIMESTEPS);
if (file_alphas_cumprod.size() == TIMESTEPS) {
alphas_cumprod = file_alphas_cumprod;
} else if (sd_version_is_pixart(version)) {
// PixArt checkpoints train with a linear beta schedule (0.0001 -> 0.02)
// instead of the scaled_linear schedule used by SD1.x/SDXL.
calculate_alphas_cumprod_linear_beta(alphas_cumprod.data(), 0.0001f, 0.02f);
} else {
calculate_alphas_cumprod(alphas_cumprod.data());
}
@@ -2731,7 +2748,7 @@ int StableDiffusionGGML::get_diffusion_model_down_factor() {
if (sd_version_is_dit(version)) {
if (sd_version_is_sensenova_u1(version)) {
down_factor = 32;
} else if (version == VERSION_QWEN_IMAGE_2_1 || sd_version_is_wan(version) || sd_version_is_lingbot_video(version) || sd_version_is_minimax_h3(version)) {
} else if (version == VERSION_QWEN_IMAGE_2_1 || sd_version_is_wan(version) || sd_version_is_lingbot_video(version) || sd_version_is_minimax_h3(version) || sd_version_is_pixart(version)) {
down_factor = 2;
} else {
down_factor = 1;
@@ -2769,6 +2786,8 @@ int StableDiffusionGGML::get_latent_channel() {
latent_channel = 128;
} else if (sd_version_is_mage_flow(version)) {
latent_channel = 128;
} else if (sd_version_is_pixart(version)) {
latent_channel = 4;
} else {
latent_channel = 16;
}
@@ -2864,14 +2883,26 @@ sd::Tensor<float> StableDiffusionGGML::decode_first_stage(const sd::Tensor<float
return sd::ops::clamp((x + 1.f) * 0.5f, 0.0f, 1.0f);
}
auto latents = first_stage_model->diffusion_to_vae_latents(x);
auto decoded = first_stage_model->decode(n_threads, latents, vae_tiling_params, decode_video, circular_x, circular_y);
const bool prefer_temporal_tiling = decode_video && first_stage_model->can_temporal_tile_decode();
while (decoded.empty() &&
sd::backend_fit::prepare_vae_decode_retry_tiling(vae_tiling_params, prefer_temporal_tiling,
first_stage_model->last_compute_status())) {
decoded = first_stage_model->decode(n_threads, latents, vae_tiling_params, decode_video, circular_x, circular_y);
auto tiling_params = first_stage_model->resolve_tiling_params(vae_tiling_params);
const bool prefer_temporal_tiling = decode_video && latents.dim() == 5 && latents.shape()[2] > 1 &&
first_stage_model->can_temporal_tile_decode();
for (;;) {
int tile_size_w = static_cast<int>(latents.shape()[0]);
int tile_size_h = static_cast<int>(latents.shape()[1]);
float tile_overlap;
if (tiling_params.enabled &&
!first_stage_model->get_tile_sizes(tile_size_w, tile_size_h, tile_overlap, tiling_params,
latents.shape()[0], latents.shape()[1])) {
return {};
}
auto decoded = first_stage_model->decode(n_threads, latents, tiling_params, decode_video, circular_x, circular_y);
if (!decoded.empty() ||
!sd::backend_fit::prepare_vae_decode_retry_tiling(tiling_params, prefer_temporal_tiling,
first_stage_model->last_compute_status(),
tile_size_w, tile_size_h, first_stage_model->get_scale_factor())) {
return decoded;
}
}
return decoded;
}
sd::Tensor<float> StableDiffusionGGML::normalize_ltx_video_latents(const sd::Tensor<float>& x) {
+15 -13
View File
@@ -35,19 +35,21 @@ namespace sd::pipeline {
return original_axes;
}
int tile_size_x, tile_size_y;
int tile_size_w, tile_size_h;
float overlap;
int latent_size_x = request.width / request.vae_scale_factor;
int latent_size_y = request.height / request.vae_scale_factor;
sd->first_stage_model->get_tile_sizes(tile_size_x,
tile_size_y,
overlap,
sd_img_gen_params->vae_tiling_params,
latent_size_x,
latent_size_y);
int latent_size_w = request.width / request.vae_scale_factor;
int latent_size_h = request.height / request.vae_scale_factor;
if (!sd->first_stage_model->get_tile_sizes(tile_size_w,
tile_size_h,
overlap,
sd_img_gen_params->vae_tiling_params,
latent_size_w,
latent_size_h)) {
return original_axes;
}
sd->circular_x = sd->circular_x && (tile_size_x >= latent_size_x);
sd->circular_y = sd->circular_y && (tile_size_y >= latent_size_y);
sd->circular_x = sd->circular_x && (tile_size_w >= latent_size_w);
sd->circular_y = sd->circular_y && (tile_size_h >= latent_size_h);
if (sd->first_stage_model) {
sd->first_stage_model->set_circular_axes(sd->circular_x, sd->circular_y);
@@ -56,8 +58,8 @@ namespace sd::pipeline {
sd->preview_vae->set_circular_axes(sd->circular_x, sd->circular_y);
}
sd->circular_x = original_axes.circular_x && (tile_size_x < latent_size_x);
sd->circular_y = original_axes.circular_y && (tile_size_y < latent_size_y);
sd->circular_x = original_axes.circular_x && (tile_size_w < latent_size_w);
sd->circular_y = original_axes.circular_y && (tile_size_h < latent_size_h);
return original_axes;
}
+32
View File
@@ -1,5 +1,6 @@
#include "model_builders.h"
#include <cmath>
#include <cstring>
#include <utility>
@@ -28,6 +29,7 @@
#include "model/diffusion/mmdit.hpp"
#include "model/diffusion/model.hpp"
#include "model/diffusion/pid.hpp"
#include "model/diffusion/pixart.hpp"
#include "model/diffusion/qwen_image.hpp"
#include "model/diffusion/qwen_image_2_1.hpp"
#include "model/diffusion/sensenova_u1.h"
@@ -301,6 +303,19 @@ namespace sd::model_builders {
weight_manager,
sd_ctx_params->model_args);
}
} else if (version == VERSION_PIXART) {
result.conditioner = std::make_shared<T5CLIPEmbedder>(ctx.backends.runtime_backend(SDBackendModule::TE),
tensor_storage_map,
true,
0,
false,
weight_manager,
sd_ctx_params->model_args);
result.diffusion = std::make_shared<PixArt::PixArtRunner>(ctx.backends.runtime_backend(SDBackendModule::DIFFUSION),
tensor_storage_map,
"model.diffusion_model",
weight_manager,
sd_ctx_params->model_args);
} else if (sd_version_is_mage_flow(version)) {
result.conditioner = std::make_shared<LLMEmbedder>(ctx.backends.runtime_backend(SDBackendModule::TE),
tensor_storage_map,
@@ -562,6 +577,23 @@ namespace sd::model_builders {
false,
vae_version,
weight_manager);
if (sd_version_is_pixart(version)) {
// Alpha-512 and Sigma share tensor layouts; Alpha-512 needs an explicit scale override.
if (tensor_storage_map.count("model.diffusion_model.csize_embedder.mlp.0.weight") != 0) {
model->scale_factor = 0.18215f;
}
for (const auto& [key, value] : parse_key_value_args(sd_ctx_params->model_args, "model arg")) {
if (key == "pixart_vae_scale_factor") {
float parsed = 0.f;
if (parse_strict_float(value, parsed) && std::isfinite(parsed) && parsed > 0.f) {
model->scale_factor = parsed;
} else {
LOG_WARN("ignoring invalid PixArt model arg '%s=%s'", key.c_str(), value.c_str());
}
}
}
LOG_VERBOSE("pixart: VAE scale factor = %.5f", model->scale_factor);
}
if (sd_version_is_sdxl(version) &&
(strlen(SAFE_STR(sd_ctx_params->vae_path)) == 0 || sd_ctx_params->force_sdxl_vae_conv_scale || options.external_vae_is_invalid)) {
float vae_conv_2d_scale = 1.f / 32.f;
+24 -24
View File
@@ -142,8 +142,8 @@ sd::Tensor<float> process_tiles_2d(const sd::Tensor<float>& input,
int output_width,
int output_height,
int scale,
int p_tile_size_x,
int p_tile_size_y,
int p_tile_size_w,
int p_tile_size_h,
float tile_overlap_factor,
bool circular_x,
bool circular_y,
@@ -168,28 +168,28 @@ sd::Tensor<float> process_tiles_2d(const sd::Tensor<float>& input,
int num_tiles_x;
float tile_overlap_factor_x;
sd_tiling_calc_tiles(num_tiles_x, tile_overlap_factor_x, small_width, p_tile_size_x, tile_overlap_factor, circular_x);
sd_tiling_calc_tiles(num_tiles_x, tile_overlap_factor_x, small_width, p_tile_size_w, tile_overlap_factor, circular_x);
int num_tiles_y;
float tile_overlap_factor_y;
sd_tiling_calc_tiles(num_tiles_y, tile_overlap_factor_y, small_height, p_tile_size_y, tile_overlap_factor, circular_y);
sd_tiling_calc_tiles(num_tiles_y, tile_overlap_factor_y, small_height, p_tile_size_h, tile_overlap_factor, circular_y);
int tile_overlap_x = static_cast<int32_t>(p_tile_size_x * tile_overlap_factor_x);
int non_tile_overlap_x = p_tile_size_x - tile_overlap_x;
int tile_overlap_y = static_cast<int32_t>(p_tile_size_y * tile_overlap_factor_y);
int non_tile_overlap_y = p_tile_size_y - tile_overlap_y;
int tile_size_x = p_tile_size_x < small_width ? p_tile_size_x : small_width;
int tile_size_y = p_tile_size_y < small_height ? p_tile_size_y : small_height;
int input_tile_size_x = tile_size_x;
int input_tile_size_y = tile_size_y;
int output_tile_size_x = tile_size_x;
int output_tile_size_y = tile_size_y;
int tile_overlap_x = static_cast<int32_t>(p_tile_size_w * tile_overlap_factor_x);
int non_tile_overlap_x = p_tile_size_w - tile_overlap_x;
int tile_overlap_y = static_cast<int32_t>(p_tile_size_h * tile_overlap_factor_y);
int non_tile_overlap_y = p_tile_size_h - tile_overlap_y;
int tile_size_w = p_tile_size_w < small_width ? p_tile_size_w : small_width;
int tile_size_h = p_tile_size_h < small_height ? p_tile_size_h : small_height;
int input_tile_size_w = tile_size_w;
int input_tile_size_h = tile_size_h;
int output_tile_size_w = tile_size_w;
int output_tile_size_h = tile_size_h;
if (decode) {
output_tile_size_x *= scale;
output_tile_size_y *= scale;
output_tile_size_w *= scale;
output_tile_size_h *= scale;
} else {
input_tile_size_x *= scale;
input_tile_size_y *= scale;
input_tile_size_w *= scale;
input_tile_size_h *= scale;
}
int num_tiles = num_tiles_x * num_tiles_y;
@@ -205,9 +205,9 @@ sd::Tensor<float> process_tiles_2d(const sd::Tensor<float>& input,
}
for (int y = 0; y < small_height && !last_y; y += non_tile_overlap_y) {
int dy = 0;
if (!circular_y && y + tile_size_y >= small_height) {
if (!circular_y && y + tile_size_h >= small_height) {
int original_y = y;
y = small_height - tile_size_y;
y = small_height - tile_size_h;
dy = original_y - y;
if (decode) {
dy *= scale;
@@ -216,9 +216,9 @@ sd::Tensor<float> process_tiles_2d(const sd::Tensor<float>& input,
}
for (int x = 0; x < small_width && !last_x; x += non_tile_overlap_x) {
int dx = 0;
if (!circular_x && x + tile_size_x >= small_width) {
if (!circular_x && x + tile_size_w >= small_width) {
int original_x = x;
x = small_width - tile_size_x;
x = small_width - tile_size_w;
dx = original_x - x;
if (decode) {
dx *= scale;
@@ -235,12 +235,12 @@ sd::Tensor<float> process_tiles_2d(const sd::Tensor<float>& input,
int overlap_y_out = decode ? tile_overlap_y * scale : tile_overlap_y;
int64_t t1 = ggml_time_ms();
auto input_tile = sd_tensor_split_2d(input, input_tile_size_x, input_tile_size_y, x_in, y_in);
auto input_tile = sd_tensor_split_2d(input, input_tile_size_w, input_tile_size_h, x_in, y_in);
auto output_tile = on_processing(input_tile);
if (output_tile.empty()) {
return {};
}
GGML_ASSERT(output_tile.shape()[0] == output_tile_size_x && output_tile.shape()[1] == output_tile_size_y);
GGML_ASSERT(output_tile.shape()[0] == output_tile_size_w && output_tile.shape()[1] == output_tile_size_h);
if (output.empty()) {
std::vector<int64_t> output_shape = output_tile.shape();
output_shape[0] = output_width;
+2 -2
View File
@@ -11,8 +11,8 @@ sd::Tensor<float> process_tiles_2d(const sd::Tensor<float>& input,
int output_width,
int output_height,
int scale,
int p_tile_size_x,
int p_tile_size_y,
int p_tile_size_w,
int p_tile_size_h,
float tile_overlap_factor,
bool circular_x,
bool circular_y,
+7 -1
View File
@@ -295,7 +295,13 @@ bool T5UniGramTokenizer::encode(const std::string& input, std::vector<int>& resu
std::vector<int32_t> tokens;
std::vector<std::string> token_strs;
std::string normalized = normalize(input);
auto splited_texts = split_with_special_tokens(normalized, special_tokens);
if (normalized.empty()) {
// HF reference tokenizers emit no pieces for empty input; pad_tokens
// still appends EOS so the sequence becomes [EOS] + padding.
result = std::move(tokens);
return true;
}
auto splited_texts = split_with_special_tokens(normalized, special_tokens);
if (splited_texts.empty()) {
splited_texts.push_back(normalized); // for empty string
}