mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-10-17 05:39:54 +08:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc4000d9f8 | ||
|
|
c797899732 | ||
|
|
afd5306d88 | ||
|
|
2c929495ab | ||
|
|
be0e34480d | ||
|
|
50d6405683 | ||
|
|
0a565f2950 | ||
|
|
12ee60dc02 | ||
|
|
97d2990807 | ||
|
|
16304cc3fd | ||
|
|
760717a060 | ||
|
|
88b044be7f | ||
|
|
1706b32813 | ||
|
|
58b6cb6b0c | ||
|
|
6100d8339b | ||
|
|
de298c225b | ||
|
|
fabe481212 | ||
|
|
06c359f17a | ||
|
|
bcc7e29568 | ||
|
|
487de75c96 | ||
|
|
b4f1fd6d7f | ||
|
|
c6beeef355 |
@@ -23,6 +23,7 @@ on:
|
||||
"**/*.c",
|
||||
"**/*.cpp",
|
||||
"**/*.cu",
|
||||
"ggml",
|
||||
"examples/server/frontend",
|
||||
"examples/server/frontend/**",
|
||||
]
|
||||
@@ -40,6 +41,7 @@ on:
|
||||
"**/*.c",
|
||||
"**/*.cpp",
|
||||
"**/*.cu",
|
||||
"ggml",
|
||||
"examples/server/frontend",
|
||||
"examples/server/frontend/**",
|
||||
]
|
||||
|
||||
@@ -15,6 +15,7 @@ API and command-line option may change frequently.***
|
||||
|
||||
## 🔥Important News
|
||||
|
||||
* **2026/08/20** 🚀 stable-diffusion.cpp now supports **LTX-2.5**
|
||||
* **2026/08/04** 🚀 stable-diffusion.cpp adds **Day-1 support for MiniMax-H3**
|
||||
* **2026/06/25** 🚀 stable-diffusion.cpp now supports **Krea2**
|
||||
* **2026/06/04** 🚀 stable-diffusion.cpp now supports **Ideogram4**
|
||||
@@ -68,7 +69,7 @@ API and command-line option may change frequently.***
|
||||
- Video Models
|
||||
- [Wan2.1/Wan2.2](./docs/wan.md)
|
||||
- [MiniMax-H3](./docs/minimax_h3.md)
|
||||
- [LTX-2.3](./docs/ltx2.md)
|
||||
- [LTX-2.3/LTX-2.5](./docs/ltx2.md)
|
||||
- [HunyuanVideo 1.5](./docs/hunyuan_video.md)
|
||||
- [LingBot-Video](./docs/lingbot_video.md)
|
||||
- [PhotoMaker](./docs/photo_maker.md) support.
|
||||
@@ -152,6 +153,7 @@ For runtime and parameter backend placement, see the [backend selection guide](.
|
||||
- [LCM/LCM-LoRA](./docs/lcm.md)
|
||||
- [Docker](./docs/docker.md)
|
||||
- [Quantization and GGUF](./docs/quantization_and_gguf.md)
|
||||
- [INT8 convrot safetensors](./docs/int8_convrot.md)
|
||||
- [Inference acceleration via caching](./docs/caching.md)
|
||||
|
||||
## Bindings
|
||||
|
||||
+13
-1
@@ -11,7 +11,19 @@
|
||||
- Download Qwen3-VL-8B-Instruct
|
||||
- gguf: https://huggingface.co/unsloth/Qwen3-VL-8B-Instruct-GGUF/tree/main
|
||||
|
||||
## Convert weights
|
||||
## Use original FP8 weights
|
||||
|
||||
The original Ideogram4 FP8 safetensors can be loaded directly. FP8 tensors stay
|
||||
at one byte per element in RAM and VRAM. Backends that cannot multiply FP8
|
||||
weights directly cast only the active layer to a temporary BF16 tensor during
|
||||
execution; the loader does not expand the entire checkpoint to BF16.
|
||||
|
||||
Use `ideogram4_fp8.safetensors` and `ideogram4_uncond_fp8.safetensors` directly
|
||||
with `--diffusion-model` and `--uncond-diffusion-model`, respectively.
|
||||
|
||||
## Optional conversion for quantization
|
||||
|
||||
The following conversion is only needed when creating a quantized GGUF model.
|
||||
|
||||
fp8 scale -> bf16
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# INT8 Convrot Safetensors
|
||||
|
||||
sd.cpp can load and execute ComfyUI `int8_tensorwise` safetensors with `convrot` metadata directly. The stored INT8 weights are not converted to another weight type at load time.
|
||||
|
||||
## Checkpoint format
|
||||
|
||||
Each quantized linear module contains the following tensors:
|
||||
|
||||
- `<module>.weight`: an I8 weight matrix.
|
||||
- `<module>.weight_scale`: one floating-point scale for each output row. ComfyUI's two-dimensional `[out_features, 1]` representation is normalized to a one-dimensional tensor while loading.
|
||||
- `<module>.comfy_quant`: a U8 tensor containing the JSON quantization configuration.
|
||||
|
||||
A supported configuration has this form:
|
||||
|
||||
```json
|
||||
{
|
||||
"format": "int8_tensorwise",
|
||||
"convrot": true,
|
||||
"convrot_groupsize": 256
|
||||
}
|
||||
```
|
||||
|
||||
The convrot group size must be a power of four and must divide the input feature dimension. The commonly used configuration is H256, with `convrot_groupsize` set to `256`.
|
||||
|
||||
## How INT8 convrot works
|
||||
|
||||
Convrot combines an offline rotation of the weights with the same rotation of the activations at runtime. The rotation uses a normalized regular Hadamard matrix constructed recursively from
|
||||
|
||||
```text
|
||||
[ 1 1 1 -1 ]
|
||||
[ 1 1 -1 1 ]
|
||||
H4 = [ 1 -1 1 1 ] / 2
|
||||
[-1 1 1 1 ]
|
||||
```
|
||||
|
||||
For a group size `G = 4^n`, the transform is the normalized Kronecker power of `H4`. It is applied independently to every contiguous group of `G` input features. The resulting block-diagonal rotation matrix `R` is orthogonal and symmetric, so `R R^T = I`.
|
||||
|
||||
For an original floating-point linear layer
|
||||
|
||||
```text
|
||||
Y = X W^T + b
|
||||
```
|
||||
|
||||
the checkpoint stores a rotated weight matrix `W_rot = W R`, quantized per output row. At runtime sd.cpp computes `X_rot = X R`. Ignoring quantization error,
|
||||
|
||||
```text
|
||||
X_rot W_rot^T = X R (W R)^T = X R R^T W^T = X W^T
|
||||
```
|
||||
|
||||
The rotation therefore preserves the linear operation. Its purpose is to spread isolated large values across each feature group, reducing the effect of outliers on tensorwise INT8 quantization.
|
||||
|
||||
### Weight quantization
|
||||
|
||||
The rotated weights are quantized offline with one scale per output row:
|
||||
|
||||
```text
|
||||
s_w[o] = max_i(abs(W_rot[o, i])) / 127
|
||||
Q_w[o, i] = clamp(round(W_rot[o, i] / s_w[o]), -127, 127)
|
||||
```
|
||||
|
||||
`Q_w` is stored in `<module>.weight`, and `s_w` is stored in `<module>.weight_scale`.
|
||||
|
||||
### Runtime activation quantization
|
||||
|
||||
For every activation row, sd.cpp applies the group-wise Hadamard rotation and then calculates one dynamic scale across the entire rotated row:
|
||||
|
||||
```text
|
||||
s_x[r] = max_i(abs(X_rot[r, i])) / 127
|
||||
Q_x[r, i] = clamp(round(X_rot[r, i] / s_x[r]), -127, 127)
|
||||
```
|
||||
|
||||
The matrix multiplication accumulates into signed 32-bit integers:
|
||||
|
||||
```text
|
||||
A[r, o] = sum_i(Q_x[r, i] * Q_w[o, i])
|
||||
```
|
||||
|
||||
The floating-point output is reconstructed as
|
||||
|
||||
```text
|
||||
Y[r, o] ~= A[r, o] * s_x[r] * s_w[o] + b[o]
|
||||
```
|
||||
|
||||
The packed runtime activation tensor contains the I8 activation rows and their floating-point row scales. Linear layers that share the same input and convrot group size reuse this packed tensor, avoiding repeated rotation and activation quantization within the graph.
|
||||
|
||||
## Backend support
|
||||
|
||||
- CPU provides the portable regular Hadamard, activation quantization, INT8 matrix multiplication, and scale restoration implementations.
|
||||
- NVIDIA CUDA devices with compute capability 7.5 or newer use the native accelerated path. For H256, CUDA fuses the rotation, row-wise maximum reduction, and activation quantization. It uses cuBLAS for I8 x I8 to I32 GEMM and a CUDA kernel for scale restoration and bias addition.
|
||||
- Vulkan and other GPU backends do not currently have dedicated INT8 convrot kernels. They use the backend scheduler to fall back to CPU, which is expected to be substantially slower than the CUDA path.
|
||||
|
||||
LoRA adapters are applied at runtime without modifying the INT8 weights. The INT8 convrot path computes the base linear output, while LoRA, LoHa, LoKr, and raw weight-difference adapters compute their output corrections from the original, unrotated activation and add them to the base output. `--lora-apply-mode auto` selects this path for models containing INT8 tensorwise weights. If `immediately` is requested, sd.cpp falls back to runtime application because merging an adapter would require dequantizing and rotating its weight update, then recalculating the per-row scales and requantizing the result.
|
||||
|
||||
The dedicated CUDA convrot activation path currently requires a group size of `256`; other supported group sizes use CPU execution.
|
||||
|
||||
## Example
|
||||
|
||||
ComfyUI INT8 convrot safetensors can be passed to `--diffusion-model` without conversion:
|
||||
|
||||
```powershell
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\krea2_turbo_int8_convrot.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\wan_2.1_vae.safetensors -p "a lovely cat holding a sign says 'krea2.cpp'" --steps 8 --cfg-scale 1 --diffusion-fa -v --offload-to-cpu
|
||||
```
|
||||
+63
-1
@@ -1,7 +1,17 @@
|
||||
# How to Use
|
||||
|
||||
Both LTX-2.3 and LTX-2.5 are supported. The two share a transformer, video VAE and audio
|
||||
VAE architecture; LTX-2.5 drops the video FFN biases, adds a learned keyframe
|
||||
absolute-position embedding, and pairs with a Gemma 4 text encoder instead of Gemma 3.
|
||||
Everything is detected from the weights, so the command lines differ only in which files
|
||||
you pass.
|
||||
|
||||
# LTX-2.3
|
||||
|
||||
## Download weights
|
||||
|
||||
### LTX-2.3
|
||||
|
||||
- Download LTX-2.3
|
||||
- safetensors: https://huggingface.co/Kijai/LTX2.3_comfy/tree/main/diffusion_models
|
||||
- gguf: https://huggingface.co/unsloth/LTX-2.3-GGUF/tree/main
|
||||
@@ -16,6 +26,30 @@
|
||||
- Download LTX spatial latent upscaler
|
||||
- safetensors: https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-spatial-upscaler-x2-1.1.safetensors
|
||||
|
||||
### LTX-2.5
|
||||
|
||||
- Download LTX-2.5
|
||||
- safetensors: https://huggingface.co/Lightricks/LTX-2.5/tree/main/diffusion_models
|
||||
- gguf: https://huggingface.co/vantagewithai/LTX-2.5-GGUF/tree/main
|
||||
- Download the text encoder. This is a Gemma 4 12B fine-tuned for LTX with the text
|
||||
projection bundled in, so no separate `--embeddings-connectors` file is needed. Google's
|
||||
stock Gemma 4 is not a substitute.
|
||||
- safetensors: https://huggingface.co/Lightricks/LTX-2.5/blob/main/text_encoders/gemma4-12b-with-proj-ltx-2.5-bf16.safetensors
|
||||
- Download the video vae. Use the **conv** variant: `ltx-2.5-video-vae-conv-bf16.safetensors`.
|
||||
The default `ltx-2.5-video-vae-bf16.safetensors` is a diffusion decoder, which is not
|
||||
implemented here.
|
||||
- safetensors: https://huggingface.co/Lightricks/LTX-2.5/blob/main/vae/ltx-2.5-video-vae-conv-bf16.safetensors
|
||||
- Download the audio vae
|
||||
- safetensors: https://huggingface.co/Lightricks/LTX-2.5/blob/main/vae/ltx-2.5-audio-vae-bf16.safetensors
|
||||
- Download the LTX spatial latent upscaler
|
||||
- safetensors: https://huggingface.co/Lightricks/LTX-2.5/blob/main/latent_upscale_models/ltx-2.5-latent-spatial-upscaler-x2-bf16-1.0.safetensors
|
||||
|
||||
To run the text encoder quantized, convert it once with sd-cli:
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M convert -m ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-bf16.safetensors --type q8_0 -o ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### LTX-2.3 dev T2V
|
||||
@@ -74,4 +108,32 @@ By default, the hires refine pass uses the main sampler and scheduler, then trim
|
||||
src="../assets/ltx2/hires_i2v.webm"
|
||||
controls
|
||||
muted
|
||||
style="max-width: 100%; height: auto;"></video>
|
||||
style="max-width: 100%; height: auto;"></video>
|
||||
|
||||
### LTX-2.5 dev T2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf -p "A wide aerial shot of a red vintage convertible driving along a coastal cliff road at sunset, waves crashing below" --cfg-scale 3.0 --sampling-method euler -v -n "worst quality, low quality, blurry, distorted, artifacts" -W 1280 -H 720 --diffusion-fa --offload-to-cpu --video-frames 121 --fps 24 -o t2v.webm
|
||||
```
|
||||
|
||||
### LTX-2.5 dev I2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf -p "a lovely cat blinking slowly, gentle camera push in" --cfg-scale 3.0 --sampling-method euler -v -W 1280 -H 720 --diffusion-fa --offload-to-cpu --video-frames 121 -i ..\assets\ernie_image\turbo_example.png -o i2v.webm
|
||||
```
|
||||
|
||||
### LTX-2.5 spatial latent upscale
|
||||
|
||||
Works exactly like the LTX-2.3 upscaler described below; put
|
||||
`ltx-2.5-latent-spatial-upscaler-x2-bf16-1.0.safetensors` under `--hires-upscalers-dir` and
|
||||
pass its name without path or extension to `--hires-upscaler`.
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf --hires-upscalers-dir ..\models\latent_upscale_models --hires-upscaler ltx-2.5-latent-spatial-upscaler-x2-bf16-1.0 --hires --hires-steps 6 -p "a lovely cat" --cfg-scale 3.0 --sampling-method euler -v -W 640 -H 360 --diffusion-fa --offload-to-cpu --video-frames 121 -o hires_t2v.webm
|
||||
```
|
||||
|
||||
## Not implemented
|
||||
|
||||
- The diffusion video decoder (`ltx-2.5-video-vae-bf16.safetensors`). Use the conv VAE.
|
||||
- The temporal latent upscaler and the duration head (`--auto-duration`); pass
|
||||
`--video-frames` explicitly.
|
||||
+11
-4
@@ -36,6 +36,7 @@ struct SDCliParams {
|
||||
SDMode mode = IMG_GEN;
|
||||
std::string output_path = "output.png";
|
||||
int output_begin_idx = -1;
|
||||
int compression_quality = 90;
|
||||
std::string image_path;
|
||||
std::string metadata_format = "text";
|
||||
|
||||
@@ -99,6 +100,10 @@ struct SDCliParams {
|
||||
"--output-begin-idx",
|
||||
"starting index for output image sequence, must be non-negative (default 0 if specified %d in output path, 1 otherwise)",
|
||||
&output_begin_idx},
|
||||
{"",
|
||||
"--compression-quality",
|
||||
"compression quality of video and JPEG / WebP images (90 by default)",
|
||||
&compression_quality},
|
||||
};
|
||||
|
||||
options.bool_options = {
|
||||
@@ -383,11 +388,13 @@ void step_callback(int step, int frame_count, sd_image_t* image, bool is_noisy,
|
||||
image->data,
|
||||
image->width,
|
||||
image->height,
|
||||
image->channel)) {
|
||||
image->channel,
|
||||
"",
|
||||
cli_params->compression_quality)) {
|
||||
LOG_ERROR("save preview image to '%s' failed", cli_params->preview_path.c_str());
|
||||
}
|
||||
} else {
|
||||
if (create_video_from_sd_images(cli_params->preview_path.c_str(), image, frame_count, cli_params->preview_fps) != 0) {
|
||||
if (create_video_from_sd_images(cli_params->preview_path.c_str(), image, frame_count, cli_params->preview_fps, cli_params->compression_quality) != 0) {
|
||||
LOG_ERROR("save preview video to '%s' failed", cli_params->preview_path.c_str());
|
||||
}
|
||||
}
|
||||
@@ -486,7 +493,7 @@ bool save_results(const SDCliParams& cli_params,
|
||||
std::string params = gen_params.embed_image_metadata
|
||||
? get_image_params(ctx_params, gen_params, metadata_seed, cli_params.mode)
|
||||
: "";
|
||||
const bool ok = write_image_to_file(path.string(), img.data, img.width, img.height, img.channel, params, 90);
|
||||
const bool ok = write_image_to_file(path.string(), img.data, img.width, img.height, img.channel, params, cli_params.compression_quality);
|
||||
LOG_INFO("save result image %d to '%s' (%s)", idx, path.string().c_str(), ok ? "success" : "failure");
|
||||
return ok;
|
||||
};
|
||||
@@ -532,7 +539,7 @@ bool save_results(const SDCliParams& cli_params,
|
||||
std::string final_ext_lower = ext.string();
|
||||
std::transform(final_ext_lower.begin(), final_ext_lower.end(), final_ext_lower.begin(), ::tolower);
|
||||
const bool mux_audio = generated_audio != nullptr && (final_ext_lower == ".avi" || final_ext_lower == ".webm");
|
||||
if (create_video_from_sd_images(video_path.string().c_str(), results, num_results, gen_params.fps, 90, mux_audio ? generated_audio : nullptr) == 0) {
|
||||
if (create_video_from_sd_images(video_path.string().c_str(), results, num_results, gen_params.fps, cli_params.compression_quality, mux_audio ? generated_audio : nullptr) == 0) {
|
||||
LOG_INFO("save result video to '%s'", video_path.string().c_str());
|
||||
if (generated_audio != nullptr && !mux_audio) {
|
||||
fs::path wav_path = video_path;
|
||||
|
||||
@@ -1008,7 +1008,7 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
&hires_upscaler},
|
||||
{"",
|
||||
"--extra-sample-args",
|
||||
"extra sampler/scheduler/guidance args, key=value list. CFG supports guidance_schedule; APG supports apg_eta, apg_momentum, apg_norm_threshold, apg_norm_threshold_smoothing; SLG supports slg_uncond; lcm supports noise_clip_std, noise_scale_start, noise_scale_end; flux supports base_shift, max_shift; ltx2 supports max_shift, base_shift, stretch, terminal; euler_ge supports gamma; beta scheduler supports alpha, beta; logit_normal supports mu, std, logsnr_min, logsnr_max, resolution_aware; lms supports lms_divisions",
|
||||
"extra sampler/scheduler/guidance args, key=value list. CFG supports guidance_schedule; APG supports apg_eta, apg_momentum, apg_norm_threshold, apg_norm_threshold_smoothing; SLG supports slg_uncond; lcm supports noise_clip_std, noise_scale_start, noise_scale_end; flux supports base_shift, max_shift; ltx2 supports max_shift, base_shift, stretch, terminal; euler_ge supports gamma; beta scheduler supports alpha, beta; logit_normal supports mu, std, logsnr_min, logsnr_max, resolution_aware; lms supports lms_max_order, lms_shift, lms_divisions",
|
||||
(int)',',
|
||||
&extra_sample_args},
|
||||
{"",
|
||||
@@ -1555,6 +1555,16 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
return 1;
|
||||
};
|
||||
|
||||
std::string sample_methods = sample_method_to_str[0];
|
||||
for (int i = 1; i < SAMPLE_METHOD_COUNT; i++) {
|
||||
sample_methods += ", " + std::string(sample_method_to_str[i]);
|
||||
}
|
||||
|
||||
std::string schedulers = scheduler_to_str[0];
|
||||
for (int i = 1; i < SCHEDULER_COUNT; i++) {
|
||||
schedulers += ", " + std::string(scheduler_to_str[i]);
|
||||
}
|
||||
|
||||
options.manual_options = {
|
||||
{"-s",
|
||||
"--seed",
|
||||
@@ -1562,17 +1572,18 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
on_seed_arg},
|
||||
{"",
|
||||
"--sampling-method",
|
||||
"sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, dpm++2m_sde, dpm++2m_sde_bt, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep, res_2s, er_sde, euler_cfg_pp, euler_a_cfg_pp, lms]"
|
||||
"(default: euler for Flux/SD3/Wan, euler_a otherwise)",
|
||||
"sampling method, one of [" + sample_methods + "], "
|
||||
"default: euler for Flux/SD3/Wan, euler_a otherwise",
|
||||
on_sample_method_arg},
|
||||
{"",
|
||||
"--high-noise-sampling-method",
|
||||
"(high noise) sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, dpm++2m_sde, dpm++2m_sde_bt, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep, res_2s, er_sde, euler_cfg_pp, euler_a_cfg_pp, lms]"
|
||||
" default: euler for Flux/SD3/Wan, euler_a otherwise",
|
||||
"(high noise) sampling method, one of [" + sample_methods + "], "
|
||||
"default: euler for Flux/SD3/Wan, euler_a otherwise",
|
||||
on_high_noise_sample_method_arg},
|
||||
{"",
|
||||
"--scheduler",
|
||||
"denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits, smoothstep, sgm_uniform, simple, kl_optimal, lcm, bong_tangent, ltx2, logit_normal, flux2, flux, beta], alias: normal=discrete, default: model-specific",
|
||||
"denoiser sigma scheduler, one of [" + schedulers + "], "
|
||||
"alias: normal=discrete, default: model-specific",
|
||||
on_scheduler_arg},
|
||||
{"",
|
||||
"--sigmas",
|
||||
|
||||
@@ -835,6 +835,9 @@ std::vector<uint8_t> create_mjpg_avi_from_sd_images_to_vector(sd_image_t* images
|
||||
const uint32_t audio_byte_rate = has_audio ? static_cast<uint32_t>(audio->sample_rate * audio_block_align) : 0;
|
||||
const uint32_t audio_data_size = has_audio ? static_cast<uint32_t>(audio_pcm.size()) : 0;
|
||||
|
||||
if (mjpg_quality != quality)
|
||||
LOG_DEBUG("create_mjpg_avi...(): compression quality was limited from %i to %i", quality, mjpg_quality);
|
||||
|
||||
std::vector<uint8_t> avi_data;
|
||||
avi_data.reserve(static_cast<size_t>(num_images) * 1024);
|
||||
|
||||
|
||||
+1
-1
Submodule ggml updated: eced84c86f...032b6997db
@@ -60,6 +60,8 @@ enum sample_method_t {
|
||||
SAMPLE_METHOD_COUNT
|
||||
};
|
||||
|
||||
extern SD_API const char* sample_method_to_str[];
|
||||
|
||||
enum scheduler_t {
|
||||
DISCRETE_SCHEDULER,
|
||||
KARRAS_SCHEDULER,
|
||||
@@ -80,6 +82,8 @@ enum scheduler_t {
|
||||
SCHEDULER_COUNT
|
||||
};
|
||||
|
||||
extern SD_API const char* scheduler_to_str[];
|
||||
|
||||
enum prediction_t {
|
||||
EPS_PRED,
|
||||
V_PRED,
|
||||
@@ -132,10 +136,13 @@ enum sd_type_t {
|
||||
// SD_TYPE_IQ4_NL_4_4 = 36,
|
||||
// SD_TYPE_IQ4_NL_4_8 = 37,
|
||||
// SD_TYPE_IQ4_NL_8_8 = 38,
|
||||
SD_TYPE_MXFP4 = 39, // MXFP4 (1 block)
|
||||
SD_TYPE_NVFP4 = 40, // NVFP4 (4 blocks, E4M3 scale)
|
||||
SD_TYPE_Q1_0 = 41,
|
||||
SD_TYPE_COUNT = 42,
|
||||
SD_TYPE_MXFP4 = 39, // MXFP4 (1 block)
|
||||
SD_TYPE_NVFP4 = 40, // NVFP4 (4 blocks, E4M3 scale)
|
||||
SD_TYPE_Q1_0 = 41,
|
||||
SD_TYPE_Q2_0 = 42,
|
||||
SD_TYPE_F8_E4M3 = 43,
|
||||
SD_TYPE_F8_E5M2 = 44,
|
||||
SD_TYPE_COUNT = 45,
|
||||
};
|
||||
|
||||
enum sd_log_level_t {
|
||||
|
||||
@@ -23,16 +23,16 @@ from typing import BinaryIO
|
||||
# Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
OUTPUT_PATH = Path(r"..\models\diffusion_models\minimax_h3_ref2va_pruned_bf16.safetensors")
|
||||
OUTPUT_PATH = Path(r".minimax_h3_fl2va_pruned_bf16.safetensors")
|
||||
|
||||
SOURCE_RULES = [
|
||||
{
|
||||
"path": Path(r"..\models\diffusion_models\minimax_h3_ref2va_bf16.safetensors"),
|
||||
"path": Path(r".minimax_h3_fl2va_bf16.safetensors"),
|
||||
"include": [r".*"],
|
||||
"exclude": [r".*adaln_proj\.linear.*", r"time_embedder.*"],
|
||||
},
|
||||
{
|
||||
"path": Path(r"..\models\diffusion_models\minimax_h3_ref2va_pruned_int8_convrot.safetensors"),
|
||||
"path": Path(r".minimax_h3_fl2va_pruned_int8_convrot.safetensors"),
|
||||
"include": [r"^.*adaln_proj\.linear.*", "adaln_t_table"],
|
||||
"exclude": [],
|
||||
},
|
||||
|
||||
@@ -2978,15 +2978,36 @@ struct LTXAVEmbedder : public Conditioner {
|
||||
std::shared_ptr<GemmaTokenizer> tokenizer;
|
||||
std::shared_ptr<LLM::LLMRunner> llm;
|
||||
std::shared_ptr<LTXAVTextProjectionRunner> projector;
|
||||
std::string projector_prefix;
|
||||
bool dual_projection = false;
|
||||
|
||||
// Gemma 4 keeps a per-layer output scalar that no Gemma 3 checkpoint has, and widens its
|
||||
// full-attention heads to 512 so their q_proj is twice a sliding layer's.
|
||||
static LLM::LLMArch detect_gemma_arch(const String2TensorStorage& tensor_storage_map,
|
||||
const std::string& llm_prefix) {
|
||||
if (tensor_storage_map.find(llm_prefix + ".model.layers.0.layer_scalar") != tensor_storage_map.end()) {
|
||||
return LLM::LLMArch::GEMMA4_12B;
|
||||
}
|
||||
auto global_q = tensor_storage_map.find(llm_prefix + ".model.layers.5.self_attn.q_proj.weight");
|
||||
auto sliding_q = tensor_storage_map.find(llm_prefix + ".model.layers.0.self_attn.q_proj.weight");
|
||||
if (global_q != tensor_storage_map.end() &&
|
||||
sliding_q != tensor_storage_map.end() &&
|
||||
global_q->second.ne[1] == sliding_q->second.ne[1] * 2) {
|
||||
return LLM::LLMArch::GEMMA4_12B;
|
||||
}
|
||||
return LLM::LLMArch::GEMMA3_12B;
|
||||
}
|
||||
|
||||
LTXAVEmbedder(ggml_backend_t backend,
|
||||
const String2TensorStorage& tensor_storage_map = {},
|
||||
const std::string& llm_prefix = "text_encoders.llm",
|
||||
const std::string& projector_prefix = "text_embedding_projection",
|
||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr) {
|
||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
: projector_prefix(projector_prefix) {
|
||||
LLM::LLMArch arch = detect_gemma_arch(tensor_storage_map, llm_prefix);
|
||||
LOG_INFO("ltxav text encoder: %s", arch == LLM::LLMArch::GEMMA4_12B ? "gemma 4" : "gemma 3");
|
||||
tokenizer = std::make_shared<GemmaTokenizer>();
|
||||
llm = std::make_shared<LLM::LLMRunner>(LLM::LLMArch::GEMMA3_12B,
|
||||
llm = std::make_shared<LLM::LLMRunner>(arch,
|
||||
backend,
|
||||
tensor_storage_map,
|
||||
llm_prefix,
|
||||
@@ -3001,7 +3022,7 @@ struct LTXAVEmbedder : public Conditioner {
|
||||
|
||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
|
||||
llm->get_param_tensors(tensors, "text_encoders.llm");
|
||||
projector->get_param_tensors(tensors, "text_embedding_projection");
|
||||
projector->get_param_tensors(tensors, projector_prefix);
|
||||
}
|
||||
|
||||
void get_param_tensor_ops(std::map<ggml_tensor*, enum ggml_op>& tensor_ops) override {
|
||||
|
||||
+159
-30
@@ -1039,6 +1039,38 @@ __STATIC_INLINE__ ggml_tensor* ggml_ext_linear(ggml_context* ctx,
|
||||
return x;
|
||||
}
|
||||
|
||||
__STATIC_INLINE__ ggml_tensor* ggml_ext_linear_i8_tensorwise(ggml_context* ctx,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* w,
|
||||
ggml_tensor* weight_scale,
|
||||
ggml_tensor* b,
|
||||
int convrot_group_size,
|
||||
float scale = 1.f) {
|
||||
GGML_ASSERT(x->type == GGML_TYPE_F32 || (x->type == GGML_TYPE_I8 && scale == 1.f));
|
||||
if (scale != 1.f) {
|
||||
x = ggml_ext_scale(ctx, x, scale);
|
||||
}
|
||||
|
||||
ggml_tensor* fused_bias = scale == 1.f ? b : nullptr;
|
||||
if (x->ne[2] * x->ne[3] > 1024) {
|
||||
int64_t ne2 = x->ne[2];
|
||||
int64_t ne3 = x->ne[3];
|
||||
x = ggml_reshape_2d(ctx, x, x->ne[0], x->ne[1] * x->ne[2] * x->ne[3]);
|
||||
x = ggml_mul_mat_i8_tensorwise(ctx, w, x, weight_scale, fused_bias, convrot_group_size);
|
||||
x = ggml_reshape_4d(ctx, x, x->ne[0], x->ne[1] / ne2 / ne3, ne2, ne3);
|
||||
} else {
|
||||
x = ggml_mul_mat_i8_tensorwise(ctx, w, x, weight_scale, fused_bias, convrot_group_size);
|
||||
}
|
||||
|
||||
if (scale != 1.f) {
|
||||
x = ggml_ext_scale(ctx, x, 1.f / scale);
|
||||
if (b != nullptr) {
|
||||
x = ggml_add_inplace(ctx, x, b);
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
__STATIC_INLINE__ ggml_tensor* ggml_ext_pad_ext(ggml_context* ctx,
|
||||
ggml_backend_t backend,
|
||||
ggml_tensor* x,
|
||||
@@ -1679,6 +1711,13 @@ struct WeightAdapter {
|
||||
ggml_tensor* b,
|
||||
const std::string& prefix,
|
||||
ForwardParams forward_params) = 0;
|
||||
virtual ggml_tensor* add_lora_to_output(ggml_context* ctx,
|
||||
ggml_backend_t backend,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* w,
|
||||
ggml_tensor* output,
|
||||
const std::string& prefix,
|
||||
ForwardParams forward_params) = 0;
|
||||
virtual size_t get_extra_graph_size() = 0;
|
||||
};
|
||||
|
||||
@@ -1696,6 +1735,7 @@ struct GGMLRunnerContext {
|
||||
std::function<ggml_tensor*(const std::string&)> get_cache_tensor;
|
||||
std::function<void(const std::string&, ggml_tensor*)> cache_tensor;
|
||||
std::function<void(ggml_tensor*, const void*)> set_backend_tensor_data;
|
||||
std::map<std::pair<ggml_tensor*, int>, ggml_tensor*> int8_convrot_cache;
|
||||
|
||||
void capture_tensor(const std::string& name, ggml_tensor* tensor) {
|
||||
if (debug_tensors == nullptr || tensor == nullptr) {
|
||||
@@ -1754,7 +1794,8 @@ protected:
|
||||
|
||||
std::vector<ggml_backend_t> extra_runtime_backends; // borrowed (SDBackendManager-owned)
|
||||
ggml_backend_sched_t sched = nullptr; // owned
|
||||
ggml_backend_t cpu_fallback_backend = nullptr; // owned, sched requires a trailing CPU backend
|
||||
size_t sched_graph_capacity = 0;
|
||||
ggml_backend_t cpu_fallback_backend = nullptr; // owned, sched requires a trailing CPU backend
|
||||
bool multi_device_eval_callback_warned = false;
|
||||
|
||||
std::shared_ptr<WeightAdapter> weight_adapter = nullptr;
|
||||
@@ -2040,9 +2081,19 @@ protected:
|
||||
// Pass explicit buffer types: synthesized defaults can make CUDA devices
|
||||
// report supporting each other's buffers and skip a required copy.
|
||||
bool ensure_sched(ggml_cgraph* gf) {
|
||||
if (sched != nullptr) {
|
||||
const size_t required_graph_size = gf != nullptr
|
||||
? std::max<size_t>(1,
|
||||
(size_t)ggml_graph_n_nodes(gf) +
|
||||
sd::ggml_graph_cut::leaf_count(gf))
|
||||
: 1;
|
||||
if (sched != nullptr && sched_graph_capacity >= required_graph_size) {
|
||||
return true;
|
||||
}
|
||||
if (sched != nullptr) {
|
||||
ggml_backend_sched_free(sched);
|
||||
sched = nullptr;
|
||||
sched_graph_capacity = 0;
|
||||
}
|
||||
std::vector<ggml_backend_t> backends;
|
||||
backends.reserve(extra_runtime_backends.size() + 2);
|
||||
backends.push_back(runtime_backend);
|
||||
@@ -2070,20 +2121,17 @@ protected:
|
||||
bufts.push_back(buft);
|
||||
}
|
||||
|
||||
size_t graph_size = MAX_GRAPH_SIZE;
|
||||
if (gf != nullptr) {
|
||||
graph_size = std::max<size_t>(graph_size, (size_t)ggml_graph_n_nodes(gf));
|
||||
}
|
||||
sched = ggml_backend_sched_new(backends.data(),
|
||||
bufts.data(),
|
||||
(int)backends.size(),
|
||||
graph_size,
|
||||
required_graph_size,
|
||||
/*parallel=*/false,
|
||||
/*op_offload=*/false);
|
||||
if (sched == nullptr) {
|
||||
LOG_ERROR("%s: failed to create backend sched", get_desc().c_str());
|
||||
return false;
|
||||
}
|
||||
sched_graph_capacity = required_graph_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2408,13 +2456,14 @@ protected:
|
||||
GGML_ASSERT(gf != nullptr);
|
||||
|
||||
size_t effective_budget = max_graph_vram_bytes;
|
||||
size_t free_clamp = SIZE_MAX;
|
||||
if (stream_layers_enabled && max_graph_vram_bytes > 0 && runtime_backend != nullptr) {
|
||||
ggml_backend_dev_t dev = ggml_backend_get_device(runtime_backend);
|
||||
if (dev != nullptr && ggml_backend_dev_type(dev) != GGML_BACKEND_DEVICE_TYPE_CPU) {
|
||||
size_t free_vram = 0, total_vram = 0;
|
||||
ggml_backend_dev_memory(dev, &free_vram, &total_vram);
|
||||
constexpr size_t safety_margin = 512ull * 1024 * 1024;
|
||||
size_t free_clamp = (free_vram > safety_margin) ? (free_vram - safety_margin) : 0;
|
||||
free_clamp = (free_vram > safety_margin) ? (free_vram - safety_margin) : 0;
|
||||
if (free_clamp < effective_budget) {
|
||||
LOG_DEBUG("%s clamping streaming budget: actual free VRAM %.2f MB < user cap %.2f MB",
|
||||
get_desc().c_str(),
|
||||
@@ -2431,7 +2480,9 @@ protected:
|
||||
observed_max_effective_budget_ = effective_budget;
|
||||
budget_increased = true;
|
||||
} else {
|
||||
effective_budget = observed_max_effective_budget_;
|
||||
// Keep the plan cache stable, but never plan above what is free now:
|
||||
// another model or process can take VRAM after the first measurement.
|
||||
effective_budget = std::min(observed_max_effective_budget_, free_clamp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3030,7 +3081,8 @@ public:
|
||||
}
|
||||
if (sched != nullptr) {
|
||||
ggml_backend_sched_free(sched);
|
||||
sched = nullptr;
|
||||
sched = nullptr;
|
||||
sched_graph_capacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3355,15 +3407,18 @@ protected:
|
||||
bool bias;
|
||||
bool force_f32;
|
||||
bool force_prec_f32;
|
||||
bool allow_weight_scale;
|
||||
bool has_weight_scale = false;
|
||||
bool has_weight_scale = false;
|
||||
bool int8_convrot = false;
|
||||
int int8_convrot_group_size = 0;
|
||||
float scale;
|
||||
std::string prefix;
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map = {}, const std::string prefix = "") override {
|
||||
this->prefix = prefix;
|
||||
has_weight_scale = false;
|
||||
enum ggml_type wtype = get_type(prefix + "weight", tensor_storage_map, GGML_TYPE_F32);
|
||||
this->prefix = prefix;
|
||||
has_weight_scale = false;
|
||||
int8_convrot = false;
|
||||
int8_convrot_group_size = 0;
|
||||
enum ggml_type wtype = get_type(prefix + "weight", tensor_storage_map, GGML_TYPE_F32);
|
||||
if (in_features % ggml_blck_size(wtype) != 0 || force_f32) {
|
||||
wtype = GGML_TYPE_F32;
|
||||
}
|
||||
@@ -3372,26 +3427,35 @@ protected:
|
||||
enum ggml_type wtype = GGML_TYPE_F32;
|
||||
params["bias"] = ggml_new_tensor_1d(ctx, wtype, out_features);
|
||||
}
|
||||
if (allow_weight_scale && tensor_storage_map.find(prefix + "weight_scale") != tensor_storage_map.end()) {
|
||||
params["weight_scale"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, out_features);
|
||||
auto weight_storage = tensor_storage_map.find(prefix + "weight");
|
||||
const bool is_int8_tensorwise = weight_storage != tensor_storage_map.end() && weight_storage->second.is_int8_tensorwise;
|
||||
auto weight_scale_storage = tensor_storage_map.find(prefix + "weight_scale");
|
||||
if (weight_scale_storage != tensor_storage_map.end()) {
|
||||
const int64_t scale_nelements = weight_scale_storage->second.nelements();
|
||||
GGML_ASSERT(scale_nelements == 1 || scale_nelements == out_features);
|
||||
params["weight_scale"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, scale_nelements);
|
||||
has_weight_scale = true;
|
||||
}
|
||||
if (is_int8_tensorwise) {
|
||||
GGML_ASSERT(wtype == GGML_TYPE_I8);
|
||||
GGML_ASSERT(has_weight_scale);
|
||||
int8_convrot = weight_storage->second.int8_convrot;
|
||||
int8_convrot_group_size = weight_storage->second.int8_convrot_group_size;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
Linear(int64_t in_features,
|
||||
int64_t out_features,
|
||||
bool bias = true,
|
||||
bool force_f32 = false,
|
||||
bool force_prec_f32 = false,
|
||||
float scale = 1.f,
|
||||
bool allow_weight_scale = false)
|
||||
bool bias = true,
|
||||
bool force_f32 = false,
|
||||
bool force_prec_f32 = false,
|
||||
float scale = 1.f)
|
||||
: in_features(in_features),
|
||||
out_features(out_features),
|
||||
bias(bias),
|
||||
force_f32(force_f32),
|
||||
force_prec_f32(force_prec_f32),
|
||||
allow_weight_scale(allow_weight_scale),
|
||||
scale(scale) {}
|
||||
|
||||
void set_scale(float scale_) {
|
||||
@@ -3403,13 +3467,84 @@ public:
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
ggml_tensor* w = params["weight"];
|
||||
ggml_tensor* w = params["weight"];
|
||||
ggml_tensor* weight_scale = has_weight_scale ? params["weight_scale"] : nullptr;
|
||||
if (w->type == GGML_TYPE_F8_E4M3 || w->type == GGML_TYPE_F8_E5M2) {
|
||||
w = ggml_cast(ctx->ggml_ctx, w, GGML_TYPE_BF16);
|
||||
}
|
||||
ggml_tensor* b = nullptr;
|
||||
if (bias) {
|
||||
b = params["bias"];
|
||||
}
|
||||
ggml_tensor* linear_bias = has_weight_scale ? nullptr : b;
|
||||
ggml_tensor* out = nullptr;
|
||||
if (w->type == GGML_TYPE_I8) {
|
||||
if (x->type != GGML_TYPE_F32) {
|
||||
x = ggml_ext_cast_f32(ctx->ggml_ctx, ctx->backend, x);
|
||||
}
|
||||
if (!ggml_is_contiguous(x)) {
|
||||
x = ggml_cont(ctx->ggml_ctx, x);
|
||||
}
|
||||
ggml_tensor* lora_input = x;
|
||||
if (ctx->weight_adapter && b != nullptr) {
|
||||
b = ctx->weight_adapter->patch_weight(ctx->ggml_ctx, ctx->backend, b, prefix + "bias");
|
||||
}
|
||||
if (int8_convrot && scale == 1.f) {
|
||||
const auto cache_key = std::make_pair(x, int8_convrot_group_size);
|
||||
auto cached = ctx->int8_convrot_cache.find(cache_key);
|
||||
if (cached == ctx->int8_convrot_cache.end()) {
|
||||
x = ggml_quantize_i8_convrot(ctx->ggml_ctx, x, int8_convrot_group_size);
|
||||
ctx->int8_convrot_cache.emplace(cache_key, x);
|
||||
} else {
|
||||
x = cached->second;
|
||||
}
|
||||
}
|
||||
out = ggml_ext_linear_i8_tensorwise(ctx->ggml_ctx,
|
||||
x,
|
||||
w,
|
||||
weight_scale,
|
||||
b,
|
||||
int8_convrot ? int8_convrot_group_size : 0,
|
||||
scale);
|
||||
if (ctx->weight_adapter) {
|
||||
WeightAdapter::ForwardParams forward_params;
|
||||
forward_params.op_type = WeightAdapter::ForwardParams::op_type_t::OP_LINEAR;
|
||||
forward_params.linear.force_prec_f32 = force_prec_f32;
|
||||
forward_params.linear.scale = scale;
|
||||
out = ctx->weight_adapter->add_lora_to_output(ctx->ggml_ctx,
|
||||
ctx->backend,
|
||||
lora_input,
|
||||
w,
|
||||
out,
|
||||
prefix,
|
||||
forward_params);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (has_weight_scale) {
|
||||
out = ggml_ext_linear(ctx->ggml_ctx, x, w, nullptr, force_prec_f32, scale);
|
||||
out = ggml_mul(ctx->ggml_ctx, out, weight_scale);
|
||||
if (ctx->weight_adapter) {
|
||||
WeightAdapter::ForwardParams forward_params;
|
||||
forward_params.op_type = WeightAdapter::ForwardParams::op_type_t::OP_LINEAR;
|
||||
forward_params.linear.force_prec_f32 = force_prec_f32;
|
||||
forward_params.linear.scale = scale;
|
||||
out = ctx->weight_adapter->add_lora_to_output(ctx->ggml_ctx,
|
||||
ctx->backend,
|
||||
x,
|
||||
w,
|
||||
out,
|
||||
prefix,
|
||||
forward_params);
|
||||
if (b != nullptr) {
|
||||
b = ctx->weight_adapter->patch_weight(ctx->ggml_ctx, ctx->backend, b, prefix + "bias");
|
||||
}
|
||||
}
|
||||
if (b != nullptr) {
|
||||
out = ggml_add_inplace(ctx->ggml_ctx, out, b);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (ctx->weight_adapter) {
|
||||
WeightAdapter::ForwardParams forward_params;
|
||||
forward_params.op_type = WeightAdapter::ForwardParams::op_type_t::OP_LINEAR;
|
||||
@@ -3419,12 +3554,6 @@ public:
|
||||
} else {
|
||||
out = ggml_ext_linear(ctx->ggml_ctx, x, w, linear_bias, force_prec_f32, scale);
|
||||
}
|
||||
if (has_weight_scale) {
|
||||
out = ggml_mul(ctx->ggml_ctx, out, params["weight_scale"]);
|
||||
if (b != nullptr) {
|
||||
out = ggml_add_inplace(ctx->ggml_ctx, out, b);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
+45
-10
@@ -453,11 +453,12 @@ namespace sd::ggml_graph_cut {
|
||||
if (tensor == nullptr || tensor->name[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
return std::strncmp(tensor->name, GGML_RUNNER_CUT_PREFIX, std::strlen(GGML_RUNNER_CUT_PREFIX)) == 0;
|
||||
return starts_with(tensor->name, GGML_RUNNER_CUT_PREFIX) &&
|
||||
ends_with(tensor->name, GGML_RUNNER_CUT_SUFFIX);
|
||||
}
|
||||
|
||||
std::string make_graph_cut_name(const std::string& group, const std::string& output) {
|
||||
return std::string(GGML_RUNNER_CUT_PREFIX) + group + "|" + output;
|
||||
return std::string(GGML_RUNNER_CUT_PREFIX) + group + "|" + output + GGML_RUNNER_CUT_SUFFIX;
|
||||
}
|
||||
|
||||
void mark_graph_cut(ggml_tensor* tensor, const std::string& group, const std::string& output) {
|
||||
@@ -603,7 +604,43 @@ namespace sd::ggml_graph_cut {
|
||||
GGML_ASSERT(gf != nullptr);
|
||||
GGML_ASSERT(graph_ctx_out != nullptr);
|
||||
|
||||
const size_t graph_size = segment.internal_node_indices.size() + segment.input_refs.size() + 8;
|
||||
// Collect leaf inputs and internal nodes, then any tensor they
|
||||
// reference that is not already represented, notably the view_src of a
|
||||
// view-typed input leaf. ggml_gallocr sizes its hash set from
|
||||
// n_nodes + n_leafs (plus a 25% margin that rounds down to zero for a
|
||||
// one-node segment), so every distinct tensor it will hash must be
|
||||
// counted here or a tiny segment overflows the hash set and aborts.
|
||||
std::vector<ggml_tensor*> leaves;
|
||||
std::unordered_set<ggml_tensor*> represented;
|
||||
for (const auto& input : segment.input_refs) {
|
||||
ggml_tensor* current_input = input_tensor(gf, input);
|
||||
if (current_input == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (represented.insert(current_input).second) {
|
||||
leaves.push_back(current_input);
|
||||
}
|
||||
}
|
||||
for (int node_idx : segment.internal_node_indices) {
|
||||
represented.insert(ggml_graph_node(gf, node_idx));
|
||||
}
|
||||
auto add_reference = [&](ggml_tensor* tensor) {
|
||||
if (tensor != nullptr && represented.insert(tensor).second) {
|
||||
leaves.push_back(tensor);
|
||||
}
|
||||
};
|
||||
for (int node_idx : segment.internal_node_indices) {
|
||||
ggml_tensor* node = ggml_graph_node(gf, node_idx);
|
||||
for (int src_idx = 0; src_idx < GGML_MAX_SRC; ++src_idx) {
|
||||
add_reference(node->src[src_idx]);
|
||||
}
|
||||
add_reference(node->view_src);
|
||||
}
|
||||
for (size_t i = 0; i < leaves.size(); ++i) {
|
||||
add_reference(leaves[i]->view_src);
|
||||
}
|
||||
|
||||
const size_t graph_size = segment.internal_node_indices.size() + leaves.size() + 8;
|
||||
ggml_init_params params = {
|
||||
/*.mem_size =*/ggml_graph_overhead_custom(graph_size, false) + 1024,
|
||||
/*.mem_buffer =*/nullptr,
|
||||
@@ -614,13 +651,9 @@ namespace sd::ggml_graph_cut {
|
||||
ggml_cgraph* segment_graph = ggml_new_graph_custom(graph_ctx, graph_size, false);
|
||||
GGML_ASSERT(segment_graph != nullptr);
|
||||
|
||||
for (const auto& input : segment.input_refs) {
|
||||
ggml_tensor* current_input = input_tensor(gf, input);
|
||||
if (current_input == nullptr) {
|
||||
continue;
|
||||
}
|
||||
for (ggml_tensor* leaf : leaves) {
|
||||
GGML_ASSERT(segment_graph->n_leafs < segment_graph->size);
|
||||
segment_graph->leafs[segment_graph->n_leafs++] = current_input;
|
||||
segment_graph->leafs[segment_graph->n_leafs++] = leaf;
|
||||
}
|
||||
|
||||
for (int output_node_index : segment.output_node_indices) {
|
||||
@@ -751,7 +784,9 @@ namespace sd::ggml_graph_cut {
|
||||
|
||||
plan.has_cuts = true;
|
||||
std::string full_name(node->name);
|
||||
std::string payload = full_name.substr(std::strlen(GGML_RUNNER_CUT_PREFIX));
|
||||
size_t prefix_len = std::strlen(GGML_RUNNER_CUT_PREFIX);
|
||||
size_t suffix_len = std::strlen(GGML_RUNNER_CUT_SUFFIX);
|
||||
std::string payload = full_name.substr(prefix_len, full_name.size() - prefix_len - suffix_len);
|
||||
size_t sep = payload.find('|');
|
||||
std::string group = sep == std::string::npos ? payload : payload.substr(0, sep);
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace sd::ggml_graph_cut {
|
||||
};
|
||||
|
||||
static constexpr const char* GGML_RUNNER_CUT_PREFIX = "ggml_runner_cut:";
|
||||
static constexpr const char* GGML_RUNNER_CUT_SUFFIX = "|";
|
||||
|
||||
struct MaxVramAssignment {
|
||||
float default_gib = 0.f;
|
||||
|
||||
@@ -1072,6 +1072,34 @@ public:
|
||||
return out;
|
||||
}
|
||||
|
||||
ggml_tensor* add_lora_to_output(ggml_context* ctx,
|
||||
ggml_backend_t backend,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* w,
|
||||
ggml_tensor* output,
|
||||
const std::string& prefix,
|
||||
WeightAdapter::ForwardParams forward_params) override {
|
||||
for (auto& lora_model : lora_models) {
|
||||
ggml_tensor* weight_diff = lora_model->get_weight_diff(prefix + "weight", backend, ctx, w, false);
|
||||
if (weight_diff != nullptr) {
|
||||
GGML_ASSERT(forward_params.op_type == ForwardParams::op_type_t::OP_LINEAR);
|
||||
ggml_tensor* out_diff = ggml_ext_linear(ctx,
|
||||
x,
|
||||
weight_diff,
|
||||
nullptr,
|
||||
forward_params.linear.force_prec_f32,
|
||||
forward_params.linear.scale);
|
||||
output = ggml_add_inplace(ctx, output, out_diff);
|
||||
}
|
||||
|
||||
ggml_tensor* out_diff = lora_model->get_out_diff(ctx, backend, x, w, forward_params, prefix + "weight");
|
||||
if (out_diff != nullptr) {
|
||||
output = ggml_add_inplace(ctx, output, out_diff);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
size_t get_extra_graph_size() override {
|
||||
size_t lora_tensor_num = 0;
|
||||
for (auto& lora_model : lora_models) {
|
||||
|
||||
@@ -268,10 +268,11 @@ public:
|
||||
int64_t dim_out,
|
||||
int64_t mult = 4,
|
||||
Activation activation = Activation::GEGLU,
|
||||
bool precision_fix = false) {
|
||||
bool precision_fix = false,
|
||||
bool bias = true) {
|
||||
int64_t inner_dim = dim * mult;
|
||||
if (activation == Activation::GELU) {
|
||||
blocks["net.0"] = std::shared_ptr<GGMLBlock>(new GELU(dim, inner_dim));
|
||||
blocks["net.0"] = std::shared_ptr<GGMLBlock>(new GELU(dim, inner_dim, bias));
|
||||
} else {
|
||||
blocks["net.0"] = std::shared_ptr<GGMLBlock>(new GEGLU(dim, inner_dim));
|
||||
}
|
||||
@@ -285,7 +286,7 @@ public:
|
||||
// The purpose of the scale here is to prevent NaN issues in certain situations.
|
||||
// For example, when using Vulkan without enabling force_prec_f32,
|
||||
// or when using CUDA but the weights are k-quants.
|
||||
blocks["net.2"] = std::shared_ptr<GGMLBlock>(new Linear(inner_dim, dim_out, true, false, force_prec_f32, scale));
|
||||
blocks["net.2"] = std::shared_ptr<GGMLBlock>(new Linear(inner_dim, dim_out, bias, false, force_prec_f32, scale));
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace Ideogram4 {
|
||||
__STATIC_INLINE__ std::shared_ptr<Linear> make_linear(int64_t in_features,
|
||||
int64_t out_features,
|
||||
bool bias = true) {
|
||||
return std::make_shared<Linear>(in_features, out_features, bias, false, false, 1.f, true);
|
||||
return std::make_shared<Linear>(in_features, out_features, bias);
|
||||
}
|
||||
|
||||
__STATIC_INLINE__ std::vector<float> gen_ideogram4_pe(int grid_h,
|
||||
|
||||
@@ -129,6 +129,10 @@ namespace LTXV {
|
||||
bool self_attention_gated = false;
|
||||
bool cross_attention_gated = false;
|
||||
|
||||
bool ff_bias = true;
|
||||
bool audio_ff_bias = true;
|
||||
bool use_keyframes_abs_pos_embedding = false;
|
||||
|
||||
static std::pair<int64_t, int64_t> infer_attention_layout(int64_t hidden_size,
|
||||
int64_t preferred_heads = -1) {
|
||||
if (preferred_heads > 0 && hidden_size % preferred_heads == 0) {
|
||||
@@ -207,6 +211,19 @@ namespace LTXV {
|
||||
tensor_storage_map.find(prefix + ".transformer_blocks.0.audio_attn2.to_gate_logits.weight") != tensor_storage_map.end()) {
|
||||
config.cross_attention_gated = true;
|
||||
}
|
||||
// LTX 2.5 sets ff_bias=false but leaves audio_ff_bias at its default, so the two
|
||||
// branches must be detected separately; older checkpoints ship both sets of biases.
|
||||
if (tensor_storage_map.find(prefix + ".transformer_blocks.0.ff.net.0.proj.bias") == tensor_storage_map.end() &&
|
||||
tensor_storage_map.find(prefix + ".transformer_blocks.0.ff.net.2.bias") == tensor_storage_map.end()) {
|
||||
config.ff_bias = false;
|
||||
}
|
||||
if (tensor_storage_map.find(prefix + ".transformer_blocks.0.audio_ff.net.0.proj.bias") == tensor_storage_map.end() &&
|
||||
tensor_storage_map.find(prefix + ".transformer_blocks.0.audio_ff.net.2.bias") == tensor_storage_map.end()) {
|
||||
config.audio_ff_bias = false;
|
||||
}
|
||||
if (tensor_storage_map.find(prefix + ".keyframes_abs_pos_embedding") != tensor_storage_map.end()) {
|
||||
config.use_keyframes_abs_pos_embedding = true;
|
||||
}
|
||||
if (tensor_storage_map.find(prefix + ".caption_projection.linear_1.weight") == tensor_storage_map.end() &&
|
||||
tensor_storage_map.find(prefix + ".caption_projection.linear_2.weight") == tensor_storage_map.end()) {
|
||||
config.use_caption_projection = false;
|
||||
@@ -874,8 +891,7 @@ namespace LTXV {
|
||||
const String2TensorStorage& tensor_storage_map = {},
|
||||
const std::string prefix = "") override {
|
||||
if (num_learnable_registers > 0) {
|
||||
ggml_type wtype = get_type(prefix + "learnable_registers", tensor_storage_map, GGML_TYPE_F32);
|
||||
params["learnable_registers"] = ggml_new_tensor_2d(ctx, wtype, hidden_size, num_learnable_registers);
|
||||
params["learnable_registers"] = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, hidden_size, num_learnable_registers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1130,7 +1146,9 @@ namespace LTXV {
|
||||
int64_t a_context_dim,
|
||||
bool apply_gated_attention,
|
||||
bool cross_attention_adaln,
|
||||
bool video_rope_interleaved)
|
||||
bool video_rope_interleaved,
|
||||
bool ff_bias = true,
|
||||
bool audio_ff_bias = true)
|
||||
: v_dim(v_dim),
|
||||
a_dim(a_dim),
|
||||
cross_attention_adaln(cross_attention_adaln) {
|
||||
@@ -1140,8 +1158,8 @@ namespace LTXV {
|
||||
blocks["audio_attn2"] = std::make_shared<CrossAttention>(a_dim, a_context_dim, a_heads, ad_head, apply_gated_attention, false);
|
||||
blocks["audio_to_video_attn"] = std::make_shared<CrossAttention>(v_dim, a_dim, a_heads, ad_head, apply_gated_attention, false);
|
||||
blocks["video_to_audio_attn"] = std::make_shared<CrossAttention>(a_dim, v_dim, a_heads, ad_head, apply_gated_attention, false);
|
||||
blocks["ff"] = std::make_shared<FeedForward>(v_dim, v_dim, 4, FeedForward::Activation::GELU);
|
||||
blocks["audio_ff"] = std::make_shared<FeedForward>(a_dim, a_dim, 4, FeedForward::Activation::GELU);
|
||||
blocks["ff"] = std::make_shared<FeedForward>(v_dim, v_dim, 4, FeedForward::Activation::GELU, false, ff_bias);
|
||||
blocks["audio_ff"] = std::make_shared<FeedForward>(a_dim, a_dim, 4, FeedForward::Activation::GELU, false, audio_ff_bias);
|
||||
}
|
||||
|
||||
std::vector<ggml_tensor*> get_ada_values(GGMLRunnerContext* ctx,
|
||||
@@ -1320,6 +1338,12 @@ namespace LTXV {
|
||||
get_type(prefix + "audio_scale_shift_table", tensor_storage_map, GGML_TYPE_F32),
|
||||
config.audio_hidden_size,
|
||||
2);
|
||||
if (config.use_keyframes_abs_pos_embedding) {
|
||||
params["keyframes_abs_pos_embedding"] = ggml_new_tensor_2d(ctx,
|
||||
get_type(prefix + "keyframes_abs_pos_embedding", tensor_storage_map, GGML_TYPE_F32),
|
||||
config.hidden_size,
|
||||
1);
|
||||
}
|
||||
}
|
||||
|
||||
LTXAVModelBlock(const LTXAVConfig& config)
|
||||
@@ -1386,7 +1410,9 @@ namespace LTXV {
|
||||
config.audio_cross_attention_dim,
|
||||
config.self_attention_gated || config.cross_attention_gated,
|
||||
config.cross_attention_adaln,
|
||||
config.video_rope_interleaved);
|
||||
config.video_rope_interleaved,
|
||||
config.ff_bias,
|
||||
config.audio_ff_bias);
|
||||
}
|
||||
|
||||
blocks["norm_out"] = std::make_shared<LayerNorm>(config.hidden_size, 1e-6f, false);
|
||||
@@ -1534,6 +1560,38 @@ namespace LTXV {
|
||||
return {v_context, a_context};
|
||||
}
|
||||
|
||||
// The video encoder is causal, so the first latent frame covers a single pixel frame while
|
||||
// every later one covers temporal_scale_factor. LTX 2.5 marks that token class with a
|
||||
// learned embedding added right after patchify_proj.
|
||||
ggml_tensor* apply_keyframes_abs_pos_embedding(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* vx,
|
||||
int64_t tokens_per_latent_frame) {
|
||||
if (!config.use_keyframes_abs_pos_embedding || params.count("keyframes_abs_pos_embedding") == 0) {
|
||||
return vx;
|
||||
}
|
||||
int64_t tokens = vx->ne[1];
|
||||
if (tokens_per_latent_frame <= 0 || tokens_per_latent_frame > tokens) {
|
||||
return vx;
|
||||
}
|
||||
auto embedding = params["keyframes_abs_pos_embedding"];
|
||||
auto first = ggml_cont(ctx->ggml_ctx,
|
||||
ggml_view_3d(ctx->ggml_ctx, vx, vx->ne[0], tokens_per_latent_frame, vx->ne[2], vx->nb[1], vx->nb[2], 0));
|
||||
first = ggml_add(ctx->ggml_ctx, first, embedding);
|
||||
if (tokens_per_latent_frame == tokens) {
|
||||
return first;
|
||||
}
|
||||
auto rest = ggml_cont(ctx->ggml_ctx,
|
||||
ggml_view_3d(ctx->ggml_ctx,
|
||||
vx,
|
||||
vx->ne[0],
|
||||
tokens - tokens_per_latent_frame,
|
||||
vx->ne[2],
|
||||
vx->nb[1],
|
||||
vx->nb[2],
|
||||
tokens_per_latent_frame * vx->nb[1]));
|
||||
return ggml_concat(ctx->ggml_ctx, first, rest, 1);
|
||||
}
|
||||
|
||||
std::vector<ggml_tensor*> get_output_scale_shift(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* table,
|
||||
ggml_tensor* embedded_timestep,
|
||||
@@ -1575,6 +1633,7 @@ namespace LTXV {
|
||||
|
||||
vx = patchify_video(ctx, vx, n);
|
||||
vx = patchify_proj->forward(ctx, vx);
|
||||
vx = apply_keyframes_abs_pos_embedding(ctx, vx, width * height);
|
||||
if (ax != nullptr && ggml_nelements(ax) > 0 && audio_time > 0) {
|
||||
ax = patchify_audio(ctx, ax);
|
||||
ax = audio_patchify_proj->forward(ctx, ax);
|
||||
|
||||
@@ -130,6 +130,18 @@ namespace MiniMaxH3 {
|
||||
return to_shift * a * a / (from_shift * b * b);
|
||||
}
|
||||
|
||||
static float time_shift_step_scale(float sigma,
|
||||
float next_sigma,
|
||||
float from_shift,
|
||||
float to_shift) {
|
||||
if (!std::isfinite(next_sigma) || next_sigma < 0.f || next_sigma == sigma) {
|
||||
return time_shift_slope(sigma, from_shift, to_shift);
|
||||
}
|
||||
float shifted_sigma = time_shift_sigma(sigma, from_shift, to_shift);
|
||||
float shifted_next_sigma = time_shift_sigma(next_sigma, from_shift, to_shift);
|
||||
return (shifted_sigma - shifted_next_sigma) / (sigma - next_sigma);
|
||||
}
|
||||
|
||||
struct TimeEmbedder : public GGMLBlock {
|
||||
TimeEmbedder(int64_t input_dim, int64_t hidden_dim, int64_t output_dim) {
|
||||
blocks["proj_in"] = std::make_shared<Linear>(input_dim, hidden_dim, true, true);
|
||||
@@ -1033,7 +1045,8 @@ namespace MiniMaxH3 {
|
||||
const std::vector<MiniMaxH3ReferenceBlock>& reference_blocks,
|
||||
int audio_length,
|
||||
float video_shift,
|
||||
float audio_shift) {
|
||||
float audio_shift,
|
||||
float next_video_sigma) {
|
||||
auto split = split_av_latents(packed, audio_length);
|
||||
video_input_cache = std::move(split.first);
|
||||
audio_input_cache = std::move(split.second);
|
||||
@@ -1130,7 +1143,16 @@ namespace MiniMaxH3 {
|
||||
layout.sequence_segments,
|
||||
layout.video_segment,
|
||||
layout.audio_segment,
|
||||
time_shift_slope(sigma_v, video_shift, audio_shift));
|
||||
// The generic Euler sampler advances the packed tensor by
|
||||
// `next_video_sigma - sigma_v`. For that sampler, scale H3's
|
||||
// audio velocity by the exact ratio of the independent audio
|
||||
// step. The derivative approximation substantially oversteps
|
||||
// at low step counts (the Turbo use case). Retain the local
|
||||
// slope for samplers that make extra/intermediate evaluations.
|
||||
time_shift_step_scale(sigma_v,
|
||||
next_video_sigma,
|
||||
video_shift,
|
||||
audio_shift));
|
||||
auto merged = merge_av_latents(compute_ctx, output.first, output.second);
|
||||
auto graph = new_graph_custom(H3_GRAPH_SIZE);
|
||||
ggml_build_forward_expand(graph, merged);
|
||||
@@ -1162,7 +1184,8 @@ namespace MiniMaxH3 {
|
||||
reference_blocks,
|
||||
extra->audio_length,
|
||||
extra->video_sigma_shift,
|
||||
extra->audio_sigma_shift);
|
||||
extra->audio_sigma_shift,
|
||||
extra->next_video_sigma);
|
||||
};
|
||||
return restore_trailing_singleton_dims(GGMLRunner::compute<float>(get_graph,
|
||||
n_threads,
|
||||
|
||||
@@ -108,6 +108,8 @@ struct MiniMaxH3DiffusionExtra {
|
||||
int audio_length = 0;
|
||||
float video_sigma_shift = 12.f;
|
||||
float audio_sigma_shift = 3.f;
|
||||
// Negative when the outer sampler is not a single-evaluation Euler step.
|
||||
float next_video_sigma = -1.f;
|
||||
};
|
||||
|
||||
struct MiniT2IDiffusionExtra {
|
||||
|
||||
+150
-27
@@ -40,6 +40,7 @@ namespace LLM {
|
||||
MINISTRAL_3_3B,
|
||||
GEMMA3_12B,
|
||||
GEMMA2_2B,
|
||||
GEMMA4_12B,
|
||||
GPT_OSS_20B,
|
||||
ARCH_COUNT,
|
||||
};
|
||||
@@ -52,6 +53,7 @@ namespace LLM {
|
||||
"ministral3.3b",
|
||||
"gemma3_12b",
|
||||
"gemma2_2b",
|
||||
"gemma4_12b",
|
||||
"gpt_oss_20b",
|
||||
};
|
||||
|
||||
@@ -120,6 +122,15 @@ namespace LLM {
|
||||
bool have_vision_weight = false;
|
||||
bool llama_cpp_style = false;
|
||||
|
||||
// gemma4 config
|
||||
int global_head_dim = 0;
|
||||
int num_global_kv_heads = 0;
|
||||
float global_partial_rotary = 1.f;
|
||||
bool global_k_eq_v = false;
|
||||
bool v_norm = false;
|
||||
bool layer_scalar = false;
|
||||
bool unscaled_attention = false;
|
||||
|
||||
static LLMConfig detect_from_weights(const String2TensorStorage& tensor_storage_map,
|
||||
const std::string& prefix,
|
||||
LLMArch arch) {
|
||||
@@ -157,6 +168,27 @@ namespace LLM {
|
||||
config.rope_thetas = {1000000.f, 10000.f};
|
||||
config.rope_scales = {8.f, 1.f};
|
||||
config.sliding_attention = {1024, 1024, 1024, 1024, 1024, 0};
|
||||
} else if (arch == LLMArch::GEMMA4_12B) {
|
||||
config.head_dim = 256;
|
||||
config.num_heads = 16;
|
||||
config.num_kv_heads = 8;
|
||||
config.global_head_dim = 512;
|
||||
config.num_global_kv_heads = 1;
|
||||
config.global_partial_rotary = 0.25f;
|
||||
config.global_k_eq_v = true;
|
||||
config.v_norm = true;
|
||||
config.layer_scalar = true;
|
||||
config.unscaled_attention = true;
|
||||
config.qkv_bias = false;
|
||||
config.qk_norm = true;
|
||||
config.rms_norm_eps = 1e-6f;
|
||||
config.rms_norm_add = false;
|
||||
config.normalize_input = true;
|
||||
config.max_position_embeddings = 262144;
|
||||
config.mlp_activation = MLPActivation::GELU_TANH;
|
||||
config.rope_thetas = {1000000.f, 10000.f};
|
||||
config.rope_scales = {1.f, 1.f};
|
||||
config.sliding_attention = {1024, 1024, 1024, 1024, 1024, 0};
|
||||
} else if (arch == LLMArch::GEMMA2_2B) {
|
||||
config.head_dim = 256;
|
||||
config.num_heads = 8;
|
||||
@@ -232,12 +264,12 @@ namespace LLM {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (contains(name, "visual.blocks.0.mlp.linear_fc1.weight") ||
|
||||
contains(name, "visual.blocks.0.mlp.gate_proj.weight")) {
|
||||
if (ends_with(name, "visual.blocks.0.mlp.linear_fc1.weight") ||
|
||||
ends_with(name, "visual.blocks.0.mlp.gate_proj.weight")) {
|
||||
config.vision.intermediate_size = tensor_storage.ne[1];
|
||||
}
|
||||
if (contains(name, "visual.merger.linear_fc2.weight") ||
|
||||
contains(name, "visual.merger.mlp.2.weight")) {
|
||||
if (ends_with(name, "visual.merger.linear_fc2.weight") ||
|
||||
ends_with(name, "visual.merger.mlp.2.weight")) {
|
||||
config.vision.out_hidden_size = tensor_storage.ne[1];
|
||||
}
|
||||
continue;
|
||||
@@ -256,22 +288,26 @@ namespace LLM {
|
||||
config.hidden_size = tensor_storage.ne[0];
|
||||
config.vocab_size = tensor_storage.ne[1];
|
||||
}
|
||||
if (contains(name, "layers.0.mlp.gate_proj.weight")) {
|
||||
if (ends_with(name, "layers.0.mlp.gate_proj.weight")) {
|
||||
config.intermediate_size = tensor_storage.ne[1];
|
||||
}
|
||||
if (contains(name, "layers.0.mlp.experts.gate_up_proj.weight")) {
|
||||
if (ends_with(name, "layers.0.mlp.experts.gate_up_proj.weight")) {
|
||||
config.intermediate_size = tensor_storage.ne[1] / 2;
|
||||
}
|
||||
if (contains(name, "layers.0.mlp.experts.gate_proj.weight")) {
|
||||
if (ends_with(name, "layers.0.mlp.experts.gate_proj.weight")) {
|
||||
config.intermediate_size = tensor_storage.ne[1];
|
||||
}
|
||||
}
|
||||
if ((arch == LLMArch::QWEN3 || arch == LLMArch::QWEN3_VL) && config.num_layers == 28) {
|
||||
config.num_heads = 16;
|
||||
}
|
||||
if (arch == LLMArch::QWEN3_VL && config.num_layers == 50 && config.hidden_size == 5120) {
|
||||
config.num_heads = 64;
|
||||
config.final_norm = false;
|
||||
if (arch == LLMArch::QWEN3_VL &&
|
||||
(config.num_layers == 50 || config.num_layers == 64) &&
|
||||
config.hidden_size == 5120) {
|
||||
config.num_heads = 64;
|
||||
if (config.num_layers == 50) {
|
||||
config.final_norm = false;
|
||||
}
|
||||
}
|
||||
if (detected_vision_layers > 0) {
|
||||
config.vision.num_layers = detected_vision_layers;
|
||||
@@ -1059,6 +1095,11 @@ namespace LLM {
|
||||
std::vector<float> rope_thetas;
|
||||
std::vector<float> rope_scales;
|
||||
bool has_attention_sinks;
|
||||
bool k_eq_v;
|
||||
bool v_norm;
|
||||
bool unscaled_attention;
|
||||
float rms_norm_eps;
|
||||
int rope_pairs;
|
||||
|
||||
void init_params(ggml_context* ctx,
|
||||
const String2TensorStorage& tensor_storage_map = {},
|
||||
@@ -1069,24 +1110,48 @@ namespace LLM {
|
||||
}
|
||||
|
||||
public:
|
||||
Attention(const LLMConfig& config)
|
||||
Attention(const LLMConfig& config, bool global_layer = false)
|
||||
: arch(config.arch),
|
||||
num_heads(config.num_heads),
|
||||
num_kv_heads(config.num_kv_heads),
|
||||
head_dim(config.head_dim),
|
||||
num_kv_heads(global_layer && config.num_global_kv_heads > 0 ? config.num_global_kv_heads : config.num_kv_heads),
|
||||
head_dim(global_layer && config.global_head_dim > 0 ? config.global_head_dim : config.head_dim),
|
||||
qk_norm(config.qk_norm),
|
||||
max_position_embeddings(config.max_position_embeddings),
|
||||
rope_thetas(config.rope_thetas),
|
||||
rope_scales(config.rope_scales),
|
||||
has_attention_sinks(config.arch == LLMArch::GPT_OSS_20B) {
|
||||
has_attention_sinks(config.arch == LLMArch::GPT_OSS_20B),
|
||||
k_eq_v(global_layer && config.global_k_eq_v),
|
||||
v_norm(config.v_norm),
|
||||
unscaled_attention(config.unscaled_attention),
|
||||
rms_norm_eps(config.rms_norm_eps),
|
||||
rope_pairs(0) {
|
||||
blocks["q_proj"] = std::make_shared<Linear>(config.hidden_size, num_heads * head_dim, config.qkv_bias);
|
||||
blocks["k_proj"] = std::make_shared<Linear>(config.hidden_size, num_kv_heads * head_dim, config.qkv_bias);
|
||||
blocks["v_proj"] = std::make_shared<Linear>(config.hidden_size, num_kv_heads * head_dim, config.qkv_bias);
|
||||
if (!k_eq_v) {
|
||||
blocks["v_proj"] = std::make_shared<Linear>(config.hidden_size, num_kv_heads * head_dim, config.qkv_bias);
|
||||
}
|
||||
blocks["o_proj"] = std::make_shared<Linear>(num_heads * head_dim, config.hidden_size, config.attention_out_bias);
|
||||
if (config.qk_norm) {
|
||||
blocks["q_norm"] = std::make_shared<LLMRMSNorm>(head_dim, config.rms_norm_eps, config.rms_norm_add);
|
||||
blocks["k_norm"] = std::make_shared<LLMRMSNorm>(head_dim, config.rms_norm_eps, config.rms_norm_add);
|
||||
}
|
||||
// Proportional RoPE rotates only the leading `rope_pairs` dimension pairs of the head;
|
||||
// the rest are left unrotated through freq_factors (see rope_freq_factors()).
|
||||
float partial = global_layer ? config.global_partial_rotary : 1.f;
|
||||
rope_pairs = static_cast<int>(partial * head_dim / 2.f);
|
||||
}
|
||||
|
||||
// ggml applies theta_i / freq_factors[i], so a huge factor collapses the angle to zero and
|
||||
// leaves that pair unrotated. This reproduces transformers' "proportional" RoPE, whose
|
||||
// inv_freq is zero-padded past `rope_pairs`, without reordering the head.
|
||||
ggml_tensor* rope_freq_factors(ggml_context* ctx) const {
|
||||
int pairs = head_dim / 2;
|
||||
if (rope_pairs >= pairs) {
|
||||
return nullptr;
|
||||
}
|
||||
auto rotated = ggml_ext_ones(ctx, rope_pairs, 1, 1, 1);
|
||||
auto unrotated = ggml_ext_full(ctx, 1e30f, pairs - rope_pairs, 1, 1, 1);
|
||||
return ggml_concat(ctx, rotated, unrotated, 0);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
@@ -1099,12 +1164,12 @@ namespace LLM {
|
||||
int64_t N = x->ne[2];
|
||||
auto q_proj = std::dynamic_pointer_cast<Linear>(blocks["q_proj"]);
|
||||
auto k_proj = std::dynamic_pointer_cast<Linear>(blocks["k_proj"]);
|
||||
auto v_proj = std::dynamic_pointer_cast<Linear>(blocks["v_proj"]);
|
||||
auto v_proj = k_eq_v ? nullptr : std::dynamic_pointer_cast<Linear>(blocks["v_proj"]);
|
||||
auto out_proj = std::dynamic_pointer_cast<Linear>(blocks["o_proj"]);
|
||||
|
||||
auto q = q_proj->forward(ctx, x); // [N, n_token, num_heads*head_dim]
|
||||
auto k = k_proj->forward(ctx, x); // [N, n_token, num_kv_heads*head_dim]
|
||||
auto v = v_proj->forward(ctx, x); // [N, n_token, num_kv_heads*head_dim]
|
||||
auto q = q_proj->forward(ctx, x); // [N, n_token, num_heads*head_dim]
|
||||
auto k = k_proj->forward(ctx, x); // [N, n_token, num_kv_heads*head_dim]
|
||||
auto v = k_eq_v ? k : v_proj->forward(ctx, x); // [N, n_token, num_kv_heads*head_dim]
|
||||
|
||||
q = ggml_reshape_4d(ctx->ggml_ctx, q, head_dim, num_heads, n_token, N); // [N, n_token, num_heads, head_dim]
|
||||
k = ggml_reshape_4d(ctx->ggml_ctx, k, head_dim, num_kv_heads, n_token, N); // [N, n_token, num_kv_heads, head_dim]
|
||||
@@ -1117,6 +1182,10 @@ namespace LLM {
|
||||
q = q_norm->forward(ctx, q);
|
||||
k = k_norm->forward(ctx, k);
|
||||
}
|
||||
if (v_norm) {
|
||||
// Gemma 4 normalizes V with a weightless RMS norm, and never rotates it.
|
||||
v = ggml_rms_norm(ctx->ggml_ctx, v, rms_norm_eps);
|
||||
}
|
||||
|
||||
if (arch == LLMArch::MISTRAL_SMALL_3_2) {
|
||||
q = ggml_rope_ext(ctx->ggml_ctx, q, input_pos, nullptr, 128, GGML_ROPE_TYPE_NORMAL, 8192, 1000000000.f, 1.f, 0.f, 1.f, 32.f, 1.f);
|
||||
@@ -1187,6 +1256,35 @@ namespace LLM {
|
||||
1.f,
|
||||
32.f,
|
||||
1.f);
|
||||
} else if (arch == LLMArch::GEMMA4_12B) {
|
||||
float rope_theta = (rope_index == 1 ? 10000.0f : 1000000.0f);
|
||||
auto freq_factors = rope_freq_factors(ctx->ggml_ctx);
|
||||
q = ggml_rope_ext(ctx->ggml_ctx,
|
||||
q,
|
||||
input_pos,
|
||||
freq_factors,
|
||||
head_dim,
|
||||
GGML_ROPE_TYPE_NEOX,
|
||||
static_cast<int>(max_position_embeddings),
|
||||
rope_theta,
|
||||
1.f,
|
||||
0.f,
|
||||
1.f,
|
||||
32.f,
|
||||
1.f);
|
||||
k = ggml_rope_ext(ctx->ggml_ctx,
|
||||
k,
|
||||
input_pos,
|
||||
freq_factors,
|
||||
head_dim,
|
||||
GGML_ROPE_TYPE_NEOX,
|
||||
static_cast<int>(max_position_embeddings),
|
||||
rope_theta,
|
||||
1.f,
|
||||
0.f,
|
||||
1.f,
|
||||
32.f,
|
||||
1.f);
|
||||
} else if (arch == LLMArch::GEMMA2_2B) {
|
||||
q = ggml_rope_ext(ctx->ggml_ctx,
|
||||
q,
|
||||
@@ -1224,6 +1322,11 @@ namespace LLM {
|
||||
k = ggml_rope_multi(ctx->ggml_ctx, k, input_pos, nullptr, head_dim, sections, GGML_ROPE_TYPE_MROPE, 128000, 1000000.f, 1.f, 0.f, 1.f, 32.f, 1.f);
|
||||
}
|
||||
|
||||
if (unscaled_attention) {
|
||||
// Gemma 4 attends with scaling=1.0; undo the helper's own 1/sqrt(head_dim).
|
||||
q = ggml_ext_scale(ctx->ggml_ctx, q, std::sqrt(static_cast<float>(head_dim)));
|
||||
}
|
||||
|
||||
q = ggml_cont(ctx->ggml_ctx, ggml_ext_torch_permute(ctx->ggml_ctx, q, 0, 2, 1, 3)); // [N, num_heads, n_token, head_dim]
|
||||
q = ggml_reshape_3d(ctx->ggml_ctx, q, q->ne[0], q->ne[1], q->ne[2] * q->ne[3]); // [N*num_heads, n_token, head_dim]
|
||||
|
||||
@@ -1262,15 +1365,30 @@ namespace LLM {
|
||||
protected:
|
||||
LLMArch arch;
|
||||
int sliding_attention;
|
||||
bool has_layer_scalar;
|
||||
std::string post_attention_norm_name;
|
||||
std::string pre_ffw_norm_name;
|
||||
std::string post_ffw_norm_name;
|
||||
|
||||
void init_params(ggml_context* ctx,
|
||||
const String2TensorStorage& tensor_storage_map = {},
|
||||
std::string prefix = "") override {
|
||||
GGMLBlock::init_params(ctx, tensor_storage_map, prefix);
|
||||
if (has_layer_scalar) {
|
||||
params["layer_scalar"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
TransformerBlock(const LLMConfig& config, int layer_index)
|
||||
: arch(config.arch),
|
||||
sliding_attention(0) {
|
||||
if (config.arch == LLMArch::GEMMA3_12B) {
|
||||
sliding_attention(0),
|
||||
has_layer_scalar(config.layer_scalar) {
|
||||
if (config.arch == LLMArch::GEMMA4_12B) {
|
||||
post_attention_norm_name = "post_attention_layernorm";
|
||||
pre_ffw_norm_name = "pre_feedforward_layernorm";
|
||||
post_ffw_norm_name = "post_feedforward_layernorm";
|
||||
} else if (config.arch == LLMArch::GEMMA3_12B || config.arch == LLMArch::GEMMA4_12B) {
|
||||
post_attention_norm_name = "post_attention_norm"; // attn_post_norm
|
||||
pre_ffw_norm_name = "post_attention_layernorm"; // ffn_norm
|
||||
post_ffw_norm_name = "post_ffw_norm"; // ffn_post_norm
|
||||
@@ -1284,7 +1402,10 @@ namespace LLM {
|
||||
pre_ffw_norm_name = "post_attention_layernorm"; // ffn_norm
|
||||
}
|
||||
|
||||
blocks["self_attn"] = std::make_shared<Attention>(config);
|
||||
if (!config.sliding_attention.empty()) {
|
||||
sliding_attention = config.sliding_attention[layer_index % config.sliding_attention.size()];
|
||||
}
|
||||
blocks["self_attn"] = std::make_shared<Attention>(config, sliding_attention == 0);
|
||||
if (config.arch == LLMArch::GPT_OSS_20B) {
|
||||
blocks["mlp"] = std::make_shared<GPTOSSMLP>(config);
|
||||
} else {
|
||||
@@ -1301,9 +1422,6 @@ namespace LLM {
|
||||
if (!post_ffw_norm_name.empty()) {
|
||||
blocks[post_ffw_norm_name] = std::make_shared<LLMRMSNorm>(config.hidden_size, config.rms_norm_eps, config.rms_norm_add);
|
||||
}
|
||||
if (!config.sliding_attention.empty()) {
|
||||
sliding_attention = config.sliding_attention[layer_index % config.sliding_attention.size()];
|
||||
}
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
@@ -1325,7 +1443,7 @@ namespace LLM {
|
||||
}
|
||||
ggml_tensor* block_attention_mask = attention_mask;
|
||||
int rope_index = 0;
|
||||
if ((arch == LLMArch::GEMMA3_12B || arch == LLMArch::GPT_OSS_20B) && sliding_attention > 0) {
|
||||
if ((arch == LLMArch::GEMMA3_12B || arch == LLMArch::GEMMA4_12B || arch == LLMArch::GPT_OSS_20B) && sliding_attention > 0) {
|
||||
block_attention_mask = sliding_attention_mask;
|
||||
rope_index = 1;
|
||||
}
|
||||
@@ -1352,6 +1470,10 @@ namespace LLM {
|
||||
}
|
||||
x = ggml_add_inplace(ctx->ggml_ctx, x, residual);
|
||||
|
||||
if (has_layer_scalar) {
|
||||
x = ggml_mul(ctx->ggml_ctx, x, params["layer_scalar"]);
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
};
|
||||
@@ -1846,6 +1968,7 @@ namespace LLM {
|
||||
config.arch == LLMArch::MINISTRAL_3_3B ||
|
||||
config.arch == LLMArch::QWEN3 ||
|
||||
config.arch == LLMArch::GEMMA3_12B ||
|
||||
config.arch == LLMArch::GEMMA4_12B ||
|
||||
config.arch == LLMArch::GEMMA2_2B ||
|
||||
config.arch == LLMArch::GPT_OSS_20B) {
|
||||
input_pos_vec.resize(n_tokens);
|
||||
@@ -1910,7 +2033,7 @@ namespace LLM {
|
||||
set_backend_tensor_data(attention_mask, attention_mask_vec.data());
|
||||
}
|
||||
|
||||
if (config.arch == LLMArch::GEMMA3_12B || config.arch == LLMArch::GPT_OSS_20B) {
|
||||
if (config.arch == LLMArch::GEMMA3_12B || config.arch == LLMArch::GEMMA4_12B || config.arch == LLMArch::GPT_OSS_20B) {
|
||||
int sliding_window = 0;
|
||||
for (int window : config.sliding_attention) {
|
||||
sliding_window = std::max(sliding_window, window);
|
||||
|
||||
@@ -154,8 +154,9 @@ namespace MiniMaxH3 {
|
||||
const String2TensorStorage& tensor_storage_map = {},
|
||||
const std::string prefix = "") override {
|
||||
GGMLBlock::init_params(ctx, tensor_storage_map, prefix);
|
||||
params["q_bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, in_channels);
|
||||
params["v_bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, in_channels);
|
||||
params["q_bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, in_channels);
|
||||
params["zero_k_bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, in_channels);
|
||||
params["v_bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, in_channels);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
@@ -166,7 +167,7 @@ namespace MiniMaxH3 {
|
||||
return ggml_reshape_4d(ctx->ggml_ctx, bias, bias->ne[0], 1, 1, 1);
|
||||
};
|
||||
auto q = ggml_add(ctx->ggml_ctx, qkv[0], bias_shape(params["q_bias"]));
|
||||
auto k = qkv[1];
|
||||
auto k = ggml_add(ctx->ggml_ctx, qkv[1], bias_shape(params["zero_k_bias"]));
|
||||
auto v = ggml_add(ctx->ggml_ctx, qkv[2], bias_shape(params["v_bias"]));
|
||||
|
||||
int64_t sequence = x->ne[1];
|
||||
@@ -358,22 +359,38 @@ namespace MiniMaxH3 {
|
||||
}
|
||||
|
||||
ggml_tensor* encode(GGMLRunnerContext* ctx, ggml_tensor* waveform) {
|
||||
GGML_ASSERT(waveform->ne[1] == 2);
|
||||
GGML_ASSERT(waveform->ne[1] * waveform->ne[2] * waveform->ne[3] == 2);
|
||||
auto encoder = std::dynamic_pointer_cast<AudioEncoder>(blocks["encoder"]);
|
||||
auto pre = std::dynamic_pointer_cast<AudioAttentionProjection>(blocks["pre_block"]);
|
||||
auto mean_proj = std::dynamic_pointer_cast<LTXV::Conv1D>(blocks["mean_proj"]);
|
||||
|
||||
waveform = ggml_reshape_3d(ctx->ggml_ctx, waveform, waveform->ne[0], 1, waveform->ne[1]);
|
||||
auto x = encoder->forward(ctx, waveform); // [B*S, 2048, T]
|
||||
x = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, x, 1, 0, 2, 3));
|
||||
x = pre->forward(ctx, x);
|
||||
x = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, x, 1, 0, 2, 3));
|
||||
auto z = mean_proj->forward(ctx, x);
|
||||
// GGML's batched conv1d storage interleaves the stream dimension
|
||||
// with output channels. Subsequent layers then read stereo samples
|
||||
// as adjacent feature channels. Run each mono stream independently,
|
||||
// matching PyTorch's reshape(B*S, 1, samples), and concatenate only
|
||||
// the completed normalized latents.
|
||||
const int64_t streams = waveform->ne[2] * waveform->ne[3];
|
||||
waveform = ggml_reshape_3d(ctx->ggml_ctx,
|
||||
waveform,
|
||||
waveform->ne[0],
|
||||
1,
|
||||
streams);
|
||||
ggml_tensor* stereo_z = nullptr;
|
||||
for (int64_t stream = 0; stream < streams; ++stream) {
|
||||
auto mono = ggml_ext_slice(ctx->ggml_ctx, waveform, 2, stream, stream + 1);
|
||||
auto x = encoder->forward(ctx, mono);
|
||||
x = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, x, 1, 0, 2, 3));
|
||||
x = pre->forward(ctx, x);
|
||||
x = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, x, 1, 0, 2, 3));
|
||||
auto z = mean_proj->forward(ctx, x);
|
||||
|
||||
auto mean = ggml_reshape_4d(ctx->ggml_ctx, params["latents_mean"], 1, kLatentChannels, 1, 1);
|
||||
auto std = ggml_reshape_4d(ctx->ggml_ctx, params["latents_std"], 1, kLatentChannels, 1, 1);
|
||||
z = ggml_div(ctx->ggml_ctx, ggml_sub(ctx->ggml_ctx, z, mean), std);
|
||||
return ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, z, 0, 2, 1, 3));
|
||||
auto mean = ggml_reshape_4d(ctx->ggml_ctx, params["latents_mean"], 1, kLatentChannels, 1, 1);
|
||||
auto std = ggml_reshape_4d(ctx->ggml_ctx, params["latents_std"], 1, kLatentChannels, 1, 1);
|
||||
z = ggml_div(ctx->ggml_ctx, ggml_sub(ctx->ggml_ctx, z, mean), std);
|
||||
z = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, z, 0, 2, 1, 3));
|
||||
stereo_z = stereo_z == nullptr ? z : ggml_concat(ctx->ggml_ctx, stereo_z, z, 1);
|
||||
}
|
||||
return stereo_z;
|
||||
}
|
||||
|
||||
ggml_tensor* decode(GGMLRunnerContext* ctx, ggml_tensor* latent) {
|
||||
|
||||
+108
-5
@@ -426,10 +426,10 @@ class TinyVideoDecoder : public UnaryBlock {
|
||||
static const int num_layers = 3;
|
||||
int channels[num_layers + 1] = {256, 128, 64, 64};
|
||||
int patch_size = 1;
|
||||
int t_upscale = 1;
|
||||
bool is_wide = false;
|
||||
|
||||
public:
|
||||
int t_upscale = 1;
|
||||
TinyVideoDecoder(int z_channels = 4, int patch_size = 1, std::vector<bool> time_upscale = {false, true, true}, bool is_wide = false)
|
||||
: z_channels(z_channels), patch_size(patch_size), is_wide(is_wide) {
|
||||
t_upscale = 1;
|
||||
@@ -536,6 +536,10 @@ public:
|
||||
patch = 4;
|
||||
time_downscale = {true, true, true};
|
||||
time_upscale = {true, true, true};
|
||||
} else if (sd_version_is_minimax_h3(version)) {
|
||||
z_channels = 24;
|
||||
patch = 2;
|
||||
time_downscale = {true, true, false};
|
||||
}
|
||||
blocks["decoder"] = std::shared_ptr<GGMLBlock>(new TinyVideoDecoder(z_channels, patch, time_upscale, is_wide));
|
||||
if (!decode_only) {
|
||||
@@ -545,24 +549,123 @@ public:
|
||||
|
||||
ggml_tensor* decode(GGMLRunnerContext* ctx, ggml_tensor* z) {
|
||||
auto decoder = std::dynamic_pointer_cast<TinyVideoDecoder>(blocks["decoder"]);
|
||||
if (sd_version_is_wan(version) || sd_version_is_hunyuan_video(version) || sd_version_is_ltxav(version)) {
|
||||
if (sd_version_is_wan(version) || sd_version_is_hunyuan_video(version) || sd_version_is_ltxav(version) || sd_version_is_minimax_h3(version)) {
|
||||
// (W, H, C, T) -> (W, H, T, C)
|
||||
z = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, z, 0, 1, 3, 2));
|
||||
}
|
||||
auto result = decoder->forward(ctx, z);
|
||||
if (sd_version_is_wan(version) || sd_version_is_hunyuan_video(version) || sd_version_is_ltxav(version)) {
|
||||
|
||||
if (sd_version_is_minimax_h3(version)) {
|
||||
int64_t num_frames = result->ne[3];
|
||||
int64_t chunk_frames = 5 * decoder->t_upscale;
|
||||
int64_t pad = (chunk_frames - (num_frames % chunk_frames)) % chunk_frames;
|
||||
|
||||
result = ggml_ext_pad_ext(ctx->ggml_ctx, ctx->backend, result, 0, 0, 0, 0, 0, 0, 0, pad, false, false);
|
||||
|
||||
int64_t num_chunks = (num_frames + pad) / chunk_frames;
|
||||
auto to_trim = decoder->t_upscale - 1;
|
||||
std::vector<ggml_tensor*> to_concat = {};
|
||||
for (int i = 0; i < num_chunks; i++) {
|
||||
auto chunk = ggml_view_4d(ctx->ggml_ctx, result,
|
||||
result->ne[0], result->ne[1], result->ne[2], chunk_frames - to_trim,
|
||||
result->nb[1], result->nb[2], result->nb[3],
|
||||
i * chunk_frames * result->nb[3]);
|
||||
to_concat.push_back(chunk);
|
||||
}
|
||||
result = ggml_ext_vec_concat(ctx->ggml_ctx, to_concat, 3);
|
||||
result = ggml_view_4d(ctx->ggml_ctx, result,
|
||||
result->ne[0], result->ne[1], result->ne[2],
|
||||
result->ne[3] - decoder->t_upscale * 3,
|
||||
result->nb[1], result->nb[2], result->nb[3], 0);
|
||||
}
|
||||
|
||||
if (sd_version_is_wan(version) || sd_version_is_hunyuan_video(version) || sd_version_is_ltxav(version) || sd_version_is_minimax_h3(version)) {
|
||||
// (W, H, T, C) -> (W, H, C, T)
|
||||
result = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, result, 0, 1, 3, 2));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ggml_tensor* encode(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
ggml_tensor* encode_h3(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
auto encoder = std::dynamic_pointer_cast<TinyVideoEncoder>(blocks["encoder"]);
|
||||
if (sd_version_is_wan(version) || sd_version_is_hunyuan_video(version) || sd_version_is_ltxav(version)) {
|
||||
|
||||
int64_t num_frames = x->ne[3];
|
||||
int64_t pad = (17 - (num_frames % 17)) % 17;
|
||||
|
||||
if (pad > 0) {
|
||||
auto last_frame = ggml_view_4d(ctx->ggml_ctx, x,
|
||||
x->ne[0], x->ne[1], x->ne[2], 1,
|
||||
x->nb[1], x->nb[2], x->nb[3],
|
||||
(num_frames - 1) * x->nb[3]);
|
||||
for (int i = 0; i < pad; i++) {
|
||||
x = ggml_concat(ctx->ggml_ctx, x, last_frame, 3);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t T_padded = x->ne[3];
|
||||
int64_t num_chunks = T_padded / 17;
|
||||
|
||||
auto zero_frame = ggml_view_4d(ctx->ggml_ctx, x,
|
||||
x->ne[0], x->ne[1], x->ne[2], 1,
|
||||
x->nb[1], x->nb[2], x->nb[3], 0);
|
||||
auto zeros_1 = ggml_scale(ctx->ggml_ctx, ggml_cont(ctx->ggml_ctx, zero_frame), 0.0f);
|
||||
auto zeros_3 = zeros_1;
|
||||
for (int i = 1; i < 3; i++) {
|
||||
zeros_3 = ggml_concat(ctx->ggml_ctx, zeros_3, zeros_1, 3);
|
||||
}
|
||||
ggml_tensor* out = nullptr;
|
||||
if (false) {
|
||||
std::vector<ggml_tensor*> to_concat = {};
|
||||
for (int i = 0; i < num_chunks; i++) {
|
||||
auto chunk = ggml_view_4d(ctx->ggml_ctx, x,
|
||||
x->ne[0], x->ne[1], x->ne[2], 17,
|
||||
x->nb[1], x->nb[2], x->nb[3],
|
||||
i * 17 * x->nb[3]);
|
||||
|
||||
auto chunk_padded = ggml_concat(ctx->ggml_ctx, zeros_3, chunk, 3);
|
||||
|
||||
to_concat.push_back(chunk_padded);
|
||||
}
|
||||
ggml_tensor* x_in = ggml_ext_vec_concat(ctx->ggml_ctx, to_concat, 3);
|
||||
out = encoder->forward(ctx, x_in);
|
||||
} else {
|
||||
std::vector<ggml_tensor*> to_concat = {};
|
||||
for (int i = 0; i < num_chunks; i++) {
|
||||
auto chunk = ggml_view_4d(ctx->ggml_ctx, x,
|
||||
x->ne[0], x->ne[1], x->ne[2], 17,
|
||||
x->nb[1], x->nb[2], x->nb[3],
|
||||
i * 17 * x->nb[3]);
|
||||
|
||||
auto chunk_padded = ggml_concat(ctx->ggml_ctx, zeros_3, chunk, 3);
|
||||
|
||||
auto chunk_out = encoder->forward(ctx, chunk_padded);
|
||||
// auto chunk_out = encoder->forward_seq(ctx, chunk_padded); // ~same vram usage, and straight-up slower. it's already sequential enough
|
||||
|
||||
to_concat.push_back(chunk_out);
|
||||
}
|
||||
out = ggml_ext_vec_concat(ctx->ggml_ctx, to_concat, 3);
|
||||
}
|
||||
|
||||
// Return x[:, :-3] - drop the last 3 elements in the T dimension
|
||||
int64_t out_T = out->ne[3];
|
||||
out = ggml_view_4d(ctx->ggml_ctx, out,
|
||||
out->ne[0], out->ne[1], out->ne[2], out_T - 3,
|
||||
out->nb[1], out->nb[2], out->nb[3], 0);
|
||||
|
||||
return ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, out, 0, 1, 3, 2));
|
||||
}
|
||||
|
||||
ggml_tensor* encode(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
if (sd_version_is_wan(version) || sd_version_is_hunyuan_video(version) || sd_version_is_ltxav(version) || (sd_version_is_minimax_h3(version) && x->ne[3] > 1)) {
|
||||
// (W, H, T, C) -> (W, H, C, T)
|
||||
x = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, x, 0, 1, 3, 2));
|
||||
}
|
||||
if (sd_version_is_minimax_h3(version)) {
|
||||
return encode_h3(ctx, x);
|
||||
}
|
||||
|
||||
auto encoder = std::dynamic_pointer_cast<TinyVideoEncoder>(blocks["encoder"]);
|
||||
|
||||
int64_t num_frames = x->ne[3];
|
||||
if (num_frames % encoder->t_downscale) {
|
||||
// pad to multiple of encoder->t_downscale at the end
|
||||
|
||||
@@ -156,7 +156,7 @@ public:
|
||||
output = _compute(n_threads, input, false);
|
||||
}
|
||||
|
||||
free_compute_buffer();
|
||||
runner_done();
|
||||
|
||||
if (output.empty()) {
|
||||
LOG_ERROR("vae encode compute failed");
|
||||
@@ -207,7 +207,7 @@ public:
|
||||
output = _compute(n_threads, input, true);
|
||||
}
|
||||
|
||||
free_compute_buffer();
|
||||
runner_done();
|
||||
|
||||
if (output.empty()) {
|
||||
LOG_ERROR("vae decode compute failed");
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <fstream>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
@@ -86,17 +87,59 @@ static ggml_type safetensors_dtype_to_ggml_type(const std::string& dtype) {
|
||||
} else if (dtype == "F64") {
|
||||
ttype = GGML_TYPE_F32;
|
||||
} else if (dtype == "F8_E4M3") {
|
||||
ttype = GGML_TYPE_F16;
|
||||
ttype = GGML_TYPE_F8_E4M3;
|
||||
} else if (dtype == "F8_E5M2") {
|
||||
ttype = GGML_TYPE_F16;
|
||||
ttype = GGML_TYPE_F8_E5M2;
|
||||
} else if (dtype == "I32") {
|
||||
ttype = GGML_TYPE_I32;
|
||||
} else if (dtype == "I64") {
|
||||
ttype = GGML_TYPE_I32;
|
||||
} else if (dtype == "I8") {
|
||||
ttype = GGML_TYPE_I8;
|
||||
}
|
||||
return ttype;
|
||||
}
|
||||
|
||||
struct ComfyQuantConfig {
|
||||
std::string format;
|
||||
bool convrot = false;
|
||||
int group_size = 0;
|
||||
};
|
||||
|
||||
static bool read_comfy_quant_config(std::ifstream& file,
|
||||
const std::string& file_path,
|
||||
const std::string& tensor_name,
|
||||
size_t offset,
|
||||
size_t size,
|
||||
ComfyQuantConfig& config,
|
||||
std::string* error) {
|
||||
static constexpr size_t MAX_COMFY_QUANT_CONFIG_SIZE = 64 * 1024;
|
||||
if (size == 0 || size > MAX_COMFY_QUANT_CONFIG_SIZE) {
|
||||
set_error(error, "invalid ComfyUI quantization metadata tensor '" + tensor_name + "' in '" + file_path + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<char> data(size + 1, '\0');
|
||||
file.clear();
|
||||
file.seekg((std::streamoff)offset, std::ios::beg);
|
||||
file.read(data.data(), (std::streamsize)size);
|
||||
if (!file) {
|
||||
set_error(error, "read ComfyUI quantization metadata tensor failed: '" + tensor_name + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const nlohmann::json json = nlohmann::json::parse(data.data(), data.data() + size);
|
||||
config.format = json.value("format", "");
|
||||
config.convrot = json.value("convrot", false);
|
||||
config.group_size = json.value("convrot_groupsize", 0);
|
||||
} catch (const std::exception&) {
|
||||
set_error(error, "parsing ComfyUI quantization metadata tensor failed: '" + tensor_name + "'");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://huggingface.co/docs/safetensors/index
|
||||
bool read_safetensors_file(const std::string& file_path,
|
||||
std::vector<TensorStorage>& tensor_storages,
|
||||
@@ -163,6 +206,33 @@ bool read_safetensors_file(const std::string& file_path,
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, ComfyQuantConfig> comfy_quant_configs;
|
||||
for (const auto& item : header_.items()) {
|
||||
const std::string& name = item.key();
|
||||
if (name == "__metadata__" || !ends_with(name, ".comfy_quant")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const nlohmann::json& tensor_info = item.value();
|
||||
if (tensor_info.value("dtype", "") != "U8") {
|
||||
set_error(error, "invalid dtype for ComfyUI quantization metadata tensor '" + name + "'");
|
||||
return false;
|
||||
}
|
||||
const size_t begin = tensor_info["data_offsets"][0].get<size_t>();
|
||||
const size_t end = tensor_info["data_offsets"][1].get<size_t>();
|
||||
if (begin > end || end > file_size_ - data_start) {
|
||||
set_error(error, "data offsets out of bounds for tensor '" + name + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
ComfyQuantConfig config;
|
||||
if (!read_comfy_quant_config(file, file_path, name, data_start + begin, end - begin, config, error)) {
|
||||
return false;
|
||||
}
|
||||
const std::string module_name = name.substr(0, name.size() - std::string(".comfy_quant").size());
|
||||
comfy_quant_configs.emplace(module_name, std::move(config));
|
||||
}
|
||||
|
||||
tensor_storages.clear();
|
||||
for (auto& item : header_.items()) {
|
||||
std::string name = item.key();
|
||||
@@ -220,17 +290,48 @@ bool read_safetensors_file(const std::string& file_path,
|
||||
TensorStorage tensor_storage(name, type, ne, n_dims, 0, data_start + begin);
|
||||
tensor_storage.reverse_ne();
|
||||
|
||||
if (ends_with(name, ".weight")) {
|
||||
const std::string module_name = name.substr(0, name.size() - std::string(".weight").size());
|
||||
auto config = comfy_quant_configs.find(module_name);
|
||||
if (config != comfy_quant_configs.end() && config->second.format == "int8_tensorwise") {
|
||||
if (type != GGML_TYPE_I8) {
|
||||
set_error(error, "ComfyUI int8_tensorwise weight is not I8: '" + name + "'");
|
||||
return false;
|
||||
}
|
||||
if (config->second.convrot) {
|
||||
int group_size_remainder = config->second.group_size;
|
||||
while (group_size_remainder > 1 && group_size_remainder % 4 == 0) {
|
||||
group_size_remainder /= 4;
|
||||
}
|
||||
if (group_size_remainder != 1 || tensor_storage.ne[0] % config->second.group_size != 0) {
|
||||
set_error(error, "invalid ComfyUI convrot group size for tensor '" + name + "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
tensor_storage.is_int8_tensorwise = true;
|
||||
tensor_storage.int8_convrot = config->second.convrot;
|
||||
tensor_storage.int8_convrot_group_size = config->second.group_size;
|
||||
}
|
||||
} else if (ends_with(name, ".weight_scale")) {
|
||||
const std::string module_name = name.substr(0, name.size() - std::string(".weight_scale").size());
|
||||
auto config = comfy_quant_configs.find(module_name);
|
||||
if (config != comfy_quant_configs.end() && config->second.format == "int8_tensorwise" &&
|
||||
tensor_storage.n_dims == 2 && tensor_storage.ne[0] == 1) {
|
||||
tensor_storage.ne[0] = tensor_storage.ne[1];
|
||||
tensor_storage.ne[1] = 1;
|
||||
tensor_storage.n_dims = 1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t tensor_data_size = end - begin;
|
||||
|
||||
bool tensor_size_ok;
|
||||
if (dtype == "F8_E4M3") {
|
||||
tensor_storage.is_f8_e4m3 = true;
|
||||
// f8 -> f16
|
||||
tensor_size_ok = (tensor_storage.nbytes() == tensor_data_size * 2);
|
||||
tensor_size_ok = (tensor_storage.nbytes() == tensor_data_size);
|
||||
} else if (dtype == "F8_E5M2") {
|
||||
tensor_storage.is_f8_e5m2 = true;
|
||||
// f8 -> f16
|
||||
tensor_size_ok = (tensor_storage.nbytes() == tensor_data_size * 2);
|
||||
tensor_size_ok = (tensor_storage.nbytes() == tensor_data_size);
|
||||
} else if (dtype == "F64") {
|
||||
tensor_storage.is_f64 = true;
|
||||
// f64 -> f32
|
||||
|
||||
@@ -15,14 +15,17 @@
|
||||
|
||||
struct TensorStorage {
|
||||
std::string name;
|
||||
ggml_type type = GGML_TYPE_F32;
|
||||
ggml_type expected_type = GGML_TYPE_COUNT;
|
||||
bool is_f8_e4m3 = false;
|
||||
bool is_f8_e5m2 = false;
|
||||
bool is_f64 = false;
|
||||
bool is_i64 = false;
|
||||
int64_t ne[SD_MAX_DIMS] = {1, 1, 1, 1, 1};
|
||||
int n_dims = 0;
|
||||
ggml_type type = GGML_TYPE_F32;
|
||||
ggml_type expected_type = GGML_TYPE_COUNT;
|
||||
bool is_f8_e4m3 = false;
|
||||
bool is_f8_e5m2 = false;
|
||||
bool is_f64 = false;
|
||||
bool is_i64 = false;
|
||||
bool is_int8_tensorwise = false;
|
||||
bool int8_convrot = false;
|
||||
int int8_convrot_group_size = 0;
|
||||
int64_t ne[SD_MAX_DIMS] = {1, 1, 1, 1, 1};
|
||||
int n_dims = 0;
|
||||
|
||||
std::string storage_key;
|
||||
size_t file_index = 0;
|
||||
@@ -51,9 +54,7 @@ struct TensorStorage {
|
||||
}
|
||||
|
||||
int64_t nbytes_to_read() const {
|
||||
if (is_f8_e4m3 || is_f8_e5m2) {
|
||||
return nbytes() / 2;
|
||||
} else if (is_f64 || is_i64) {
|
||||
if (is_f64 || is_i64) {
|
||||
return nbytes() * 2;
|
||||
} else {
|
||||
return nbytes();
|
||||
|
||||
+5
-68
@@ -78,66 +78,6 @@ bool is_unused_tensor(const std::string& name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t f8_e4m3_to_f16(uint8_t f8) {
|
||||
// do we need to support uz?
|
||||
|
||||
const uint32_t exponent_bias = 7;
|
||||
if (f8 == 0xff) {
|
||||
return ggml_fp32_to_fp16(-NAN);
|
||||
} else if (f8 == 0x7f) {
|
||||
return ggml_fp32_to_fp16(NAN);
|
||||
}
|
||||
|
||||
uint32_t sign = f8 & 0x80;
|
||||
uint32_t exponent = (f8 & 0x78) >> 3;
|
||||
uint32_t mantissa = f8 & 0x07;
|
||||
uint32_t result = sign << 24;
|
||||
if (exponent == 0) {
|
||||
if (mantissa > 0) {
|
||||
exponent = 0x7f - exponent_bias;
|
||||
|
||||
// yes, 2 times
|
||||
if ((mantissa & 0x04) == 0) {
|
||||
mantissa &= 0x03;
|
||||
mantissa <<= 1;
|
||||
exponent -= 1;
|
||||
}
|
||||
if ((mantissa & 0x04) == 0) {
|
||||
mantissa &= 0x03;
|
||||
mantissa <<= 1;
|
||||
exponent -= 1;
|
||||
}
|
||||
|
||||
result |= (mantissa & 0x03) << 21;
|
||||
result |= exponent << 23;
|
||||
}
|
||||
} else {
|
||||
result |= mantissa << 20;
|
||||
exponent += 0x7f - exponent_bias;
|
||||
result |= exponent << 23;
|
||||
}
|
||||
|
||||
return ggml_fp32_to_fp16(*reinterpret_cast<const float*>(&result));
|
||||
}
|
||||
|
||||
uint16_t f8_e5m2_to_f16(uint8_t fp8) {
|
||||
return static_cast<uint16_t>(fp8) << 8;
|
||||
}
|
||||
|
||||
void f8_e4m3_to_f16_vec(uint8_t* src, uint16_t* dst, int64_t n) {
|
||||
// support inplace op
|
||||
for (int64_t i = n - 1; i >= 0; i--) {
|
||||
dst[i] = f8_e4m3_to_f16(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void f8_e5m2_to_f16_vec(uint8_t* src, uint16_t* dst, int64_t n) {
|
||||
// support inplace op
|
||||
for (int64_t i = n - 1; i >= 0; i--) {
|
||||
dst[i] = f8_e5m2_to_f16(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void f64_to_f32_vec(double* src, float* dst, int64_t n) {
|
||||
// support inplace op
|
||||
for (int64_t i = 0; i < n; i++) {
|
||||
@@ -929,9 +869,7 @@ std::vector<MmapTensorStore> ModelLoader::mmap_tensors(std::map<std::string, ggm
|
||||
if (dst_tensor == nullptr)
|
||||
continue;
|
||||
|
||||
if (tensor_storage.is_f8_e4m3 ||
|
||||
tensor_storage.is_f8_e5m2 ||
|
||||
tensor_storage.is_f64 ||
|
||||
if (tensor_storage.is_f64 ||
|
||||
tensor_storage.is_i64 ||
|
||||
tensor_storage.type != dst_tensor->type) {
|
||||
continue;
|
||||
@@ -1215,11 +1153,7 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb,
|
||||
read_time_ms.fetch_add(t1 - t0);
|
||||
|
||||
t0 = ggml_time_ms();
|
||||
if (tensor_storage.is_f8_e4m3) {
|
||||
f8_e4m3_to_f16_vec((uint8_t*)read_buf, (uint16_t*)target_buf, tensor_storage.nelements());
|
||||
} else if (tensor_storage.is_f8_e5m2) {
|
||||
f8_e5m2_to_f16_vec((uint8_t*)read_buf, (uint16_t*)target_buf, tensor_storage.nelements());
|
||||
} else if (tensor_storage.is_f64) {
|
||||
if (tensor_storage.is_f64) {
|
||||
f64_to_f32_vec((double*)read_buf, (float*)target_buf, tensor_storage.nelements());
|
||||
} else if (tensor_storage.is_i64) {
|
||||
i64_to_i32_vec((int64_t*)read_buf, (int32_t*)target_buf, tensor_storage.nelements());
|
||||
@@ -1508,6 +1442,9 @@ bool ModelLoader::load_tensors(std::map<std::string, ggml_tensor*>& tensors,
|
||||
|
||||
bool ModelLoader::tensor_should_be_converted(const TensorStorage& tensor_storage, ggml_type type) {
|
||||
const std::string& name = tensor_storage.name;
|
||||
if (tensor_storage.is_int8_tensorwise) {
|
||||
return false;
|
||||
}
|
||||
if (type != GGML_TYPE_COUNT) {
|
||||
if (ggml_is_quantized(type) && tensor_storage.ne[0] % ggml_blck_size(type) != 0) {
|
||||
// Pass, do not convert
|
||||
|
||||
@@ -149,6 +149,7 @@ std::string convert_cond_stage_model_name(std::string name, std::string prefix)
|
||||
{"ffn_up.", "mlp.up_proj."},
|
||||
{"ffn_post_norm.", "post_ffw_norm."},
|
||||
{"ffn_norm.", "post_attention_layernorm."},
|
||||
{"layer_output_scale.weight", "layer_scalar"},
|
||||
{"output_norm.", "model.norm."},
|
||||
};
|
||||
|
||||
@@ -185,6 +186,20 @@ std::string convert_cond_stage_model_name(std::string name, std::string prefix)
|
||||
}
|
||||
|
||||
std::string convert_qwen3_vl_vision_name(std::string name) {
|
||||
static const std::vector<std::pair<std::string, std::string>> qwen3_vl_deepstack_name_map{
|
||||
{"v.deepstack_merger_list.", "deepstack_merger_list."},
|
||||
{"v.deepstack.5.", "deepstack_merger_list.0."},
|
||||
{"v.deepstack.8.", "deepstack_merger_list.0."},
|
||||
{"v.deepstack.11.", "deepstack_merger_list.1."},
|
||||
{"v.deepstack.16.", "deepstack_merger_list.1."},
|
||||
{"v.deepstack.17.", "deepstack_merger_list.2."},
|
||||
{"v.deepstack.24.", "deepstack_merger_list.2."},
|
||||
{"fc1.", "linear_fc1."},
|
||||
{"fc2.", "linear_fc2."},
|
||||
{"ffn_up.", "linear_fc1."},
|
||||
{"ffn_down.", "linear_fc2."},
|
||||
{"ffn_norm.", "norm."},
|
||||
};
|
||||
static const std::vector<std::pair<std::string, std::string>> qwen3_vl_vision_name_map{
|
||||
{"mm.0.", "merger.linear_fc1."},
|
||||
{"mm.2.", "merger.linear_fc2."},
|
||||
@@ -201,6 +216,10 @@ std::string convert_qwen3_vl_vision_name(std::string name) {
|
||||
{"ln1.", "norm1."},
|
||||
{"ln2.", "norm2."},
|
||||
};
|
||||
if (contains(name, "v.deepstack_merger_list.") || contains(name, "v.deepstack.")) {
|
||||
replace_with_name_map(name, qwen3_vl_deepstack_name_map);
|
||||
return name;
|
||||
}
|
||||
replace_with_name_map(name, qwen3_vl_vision_name_map);
|
||||
return name;
|
||||
}
|
||||
@@ -1181,6 +1200,7 @@ std::string convert_sep_to_dot(std::string name) {
|
||||
"x_embedder",
|
||||
"cross_attn",
|
||||
"output_proj",
|
||||
"token_refiner",
|
||||
};
|
||||
|
||||
// record the positions of underscores that should NOT be replaced
|
||||
@@ -1440,6 +1460,7 @@ std::string convert_tensor_name(std::string name, SDVersion version) {
|
||||
{"unet.", "model.diffusion_model."},
|
||||
{"transformer.", "model.diffusion_model."}, // dit
|
||||
{"vae.", "first_stage_model."},
|
||||
{"text_encoders.llm.text_embedding_projection.", "text_embedding_projection."},
|
||||
{"text_encoder.", "cond_stage_model.transformer."},
|
||||
{"te.", "cond_stage_model.transformer."},
|
||||
{"text_encoder.2.", "cond_stage_model.1.transformer."},
|
||||
@@ -1550,6 +1571,11 @@ std::string convert_tensor_name(std::string name, SDVersion version) {
|
||||
}
|
||||
}
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> generic_name_map = {
|
||||
{".scale_weight", ".weight_scale"},
|
||||
};
|
||||
replace_with_name_map(name, generic_name_map);
|
||||
|
||||
if (is_lora) {
|
||||
name = "lora." + name;
|
||||
}
|
||||
|
||||
+46
-22
@@ -2582,27 +2582,49 @@ static sd::Tensor<float> sample_lms(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
const std::vector<float>& sigmas,
|
||||
const SamplerExtraArgs& extra_sample_args) {
|
||||
// Linear Multi-Step from https://github.com/crowsonkb/k-diffusion
|
||||
|
||||
// Linear Multi-Step from https://github.com/crowsonkb/k-diffusion,
|
||||
// modified with "history shift" value, which seemingly needs less steps
|
||||
int divisions = 1000;
|
||||
int max_order = 4;
|
||||
int shift = 1; // 4, 0 - original; 4, 1 - PR #1843; 3, 1 - smoother image
|
||||
for (const auto& [key, value] : extra_sample_args) {
|
||||
int parsed = 0;
|
||||
if (key == "lms_max_order") {
|
||||
if (!parse_strict_int(value, parsed)) {
|
||||
LOG_WARN("ignoring invalid lms extra sample arg '%s=%s'", key.c_str(), value.c_str());
|
||||
continue;
|
||||
}
|
||||
max_order = std::max(1, parsed);
|
||||
// smaller values make the result softer, closer to Euler
|
||||
// higher values need more steps
|
||||
// values above 12 can produce NaNs, depending on steps and scheduler
|
||||
}
|
||||
if (key == "lms_shift") {
|
||||
if (!parse_strict_int(value, parsed)) {
|
||||
LOG_WARN("ignoring invalid lms extra sample arg '%s=%s'", key.c_str(), value.c_str());
|
||||
continue;
|
||||
}
|
||||
shift = std::max(0, parsed);
|
||||
// for a low number of steps, the value 1 works best
|
||||
}
|
||||
if (key == "lms_divisions") {
|
||||
if (!parse_strict_int(value, parsed)) {
|
||||
LOG_WARN("ignoring invalid lms extra sample arg '%s=%s'", key.c_str(), value.c_str());
|
||||
continue;
|
||||
}
|
||||
divisions = parsed; // std::max(1, parsed);
|
||||
// values above 35M produce noise, can be fixed by double precision
|
||||
// values < 1 always produce noise
|
||||
// values above 30M require double precision in the integrator
|
||||
// (they are needless and just slow the integration down, but
|
||||
// with single precision they softly produce noise
|
||||
// near the 35M, it can be used for distorted generations)
|
||||
}
|
||||
}
|
||||
LOG_DEBUG("linear multi-step sampler: integrating using %i division%s", divisions, (divisions == 1) ? "" : "s");
|
||||
|
||||
auto linear_multistep_coeff = [=](const int order, const int m, const int j) -> float {
|
||||
if (!divisions)
|
||||
return sigmas[m + 1] - sigmas[m]; // delta / 0 * 0
|
||||
#define LMS_PRECISION float // double
|
||||
#define LMS_PRECISION float // when divisions > 30 millions, the double precision fixes noise
|
||||
const LMS_PRECISION a = sigmas[m], dx = (sigmas[m + 1] - a) / divisions, s = sigmas[m - j];
|
||||
const LMS_PRECISION b0 = a + 0.5f * dx; // using Riemann middle integral
|
||||
LMS_PRECISION sum = 0.0f;
|
||||
@@ -2622,11 +2644,12 @@ static sd::Tensor<float> sample_lms(denoise_cb_t model,
|
||||
return sum * dx;
|
||||
};
|
||||
|
||||
const int max_order = 4;
|
||||
float lms_coeff[max_order];
|
||||
int steps = static_cast<int>(sigmas.size()) - 1;
|
||||
max_order = std::min(max_order, steps); // history can not be larger than steps
|
||||
LOG_DEBUG("linear multi-step sampler: lms_max_order = %i, lms_shift = %i, lms_divisions = %i", max_order, shift, divisions);
|
||||
std::vector<float> lms_coeff(max_order);
|
||||
std::vector<sd::Tensor<float>> hist = {};
|
||||
|
||||
int steps = static_cast<int>(sigmas.size()) - 1;
|
||||
for (int i = 0; i < steps; i++) {
|
||||
const float sigma = sigmas[i];
|
||||
|
||||
@@ -2637,25 +2660,26 @@ static sd::Tensor<float> sample_lms(denoise_cb_t model,
|
||||
sd::Tensor<float> denoised = std::move(denoised_opt.pred);
|
||||
|
||||
const int order = std::min(max_order, i + 1);
|
||||
|
||||
for (int c = 0; c < order; c++) // computing coefficients
|
||||
lms_coeff[c] = linear_multistep_coeff(order, i, c);
|
||||
|
||||
sd::Tensor<float> d_cur = (x - denoised) / sigma;
|
||||
switch (order) {
|
||||
case 4: // derivative + 3 history points
|
||||
x += hist[hist.size() - 2] * lms_coeff[3];
|
||||
case 3:
|
||||
x += hist[hist.size() - 1] * lms_coeff[2];
|
||||
case 2:
|
||||
x += hist.back() * lms_coeff[1];
|
||||
case 1:
|
||||
x += d_cur * lms_coeff[0];
|
||||
x += d_cur * lms_coeff[0];
|
||||
if (max_order > 1) { // if max_order == 1, the history is not used (order always < 2)
|
||||
int hist_size_p1 = hist.size() + 1;
|
||||
if (i) { // history does not exist at 1st step
|
||||
int hist_max = hist.size() - 1;
|
||||
for (int c = 2; c <= order; c++)
|
||||
x += hist[std::min(hist_max, hist_size_p1 - c + shift)] * lms_coeff[c - 1];
|
||||
// max_order == 4 => hist[] index = 2, 1, 0
|
||||
// shift == 1 => hist[] index = 2, 2, 1
|
||||
}
|
||||
if (hist_size_p1 == max_order) {
|
||||
hist.erase(hist.begin());
|
||||
}
|
||||
hist.push_back(std::move(d_cur));
|
||||
}
|
||||
|
||||
if (hist.size() == static_cast<size_t>(max_order - 1)) {
|
||||
hist.erase(hist.begin());
|
||||
}
|
||||
hist.push_back(std::move(d_cur));
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
+66
-16
@@ -150,6 +150,9 @@ const char* sampling_methods_str[] = {
|
||||
"LMS",
|
||||
};
|
||||
|
||||
static_assert(SAMPLE_METHOD_COUNT == sizeof(sampling_methods_str) / sizeof(sampling_methods_str[0]),
|
||||
"\nnumber of elements in sampling_methods_str[] != SAMPLE_METHOD_COUNT");
|
||||
|
||||
/*================================================== Helper Functions ================================================*/
|
||||
|
||||
static bool sd_version_supports_ref_latent_img_cfg(SDVersion version) {
|
||||
@@ -960,8 +963,16 @@ public:
|
||||
|
||||
LOG_DEBUG("ggml tensor size = %d bytes", (int)sizeof(ggml_tensor));
|
||||
|
||||
bool have_int8_tensorwise = false;
|
||||
for (const auto& [_, tensor_storage] : model_loader.get_tensor_storage_map()) {
|
||||
if (tensor_storage.is_int8_tensorwise) {
|
||||
have_int8_tensorwise = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sd_ctx_params->lora_apply_mode == LORA_APPLY_AUTO) {
|
||||
bool have_quantized_weight = false;
|
||||
bool have_quantized_weight = have_int8_tensorwise;
|
||||
for (const auto& [type, _] : wtype_stat) {
|
||||
if (ggml_is_quantized(type)) {
|
||||
have_quantized_weight = true;
|
||||
@@ -977,12 +988,19 @@ public:
|
||||
apply_lora_immediately = true;
|
||||
}
|
||||
} else if (sd_ctx_params->lora_apply_mode == LORA_APPLY_IMMEDIATELY) {
|
||||
if (row_split_active()) {
|
||||
if (have_int8_tensorwise) {
|
||||
LOG_WARN(
|
||||
"INT8 tensorwise weights do not support the immediately LoRA apply mode; "
|
||||
"using at_runtime instead");
|
||||
apply_lora_immediately = false;
|
||||
} else if (row_split_active()) {
|
||||
LOG_WARN(
|
||||
"row-split tensors do not support the immediately LoRA apply mode; "
|
||||
"LoRAs will not be applied to them (use --lora-apply-mode at_runtime)");
|
||||
apply_lora_immediately = false;
|
||||
} else {
|
||||
apply_lora_immediately = true;
|
||||
}
|
||||
apply_lora_immediately = true;
|
||||
} else {
|
||||
apply_lora_immediately = false;
|
||||
}
|
||||
@@ -1008,11 +1026,11 @@ public:
|
||||
tae_preview_only = false;
|
||||
use_tae = true;
|
||||
}
|
||||
if (sd_version_is_minimax_h3(version) && use_tae) {
|
||||
LOG_WARN("MiniMax-H3 does not have a compatible TAE; ignoring --taesd");
|
||||
tae_preview_only = false;
|
||||
use_tae = false;
|
||||
}
|
||||
// if (sd_version_is_minimax_h3(version) && use_tae) {
|
||||
// LOG_WARN("MiniMax-H3 does not have a compatible TAE; ignoring --taesd");
|
||||
// tae_preview_only = false;
|
||||
// use_tae = false;
|
||||
// }
|
||||
|
||||
auto& tensor_storage_map = model_loader.get_tensor_storage_map();
|
||||
|
||||
@@ -1387,7 +1405,7 @@ public:
|
||||
}
|
||||
|
||||
auto create_tae = [&](bool decode_only) -> std::shared_ptr<VAE> {
|
||||
if (sd_version_uses_wan_vae(version) || sd_version_is_hunyuan_video(version) || sd_version_is_ltxav(version)) {
|
||||
if (sd_version_uses_wan_vae(version) || sd_version_is_hunyuan_video(version) || sd_version_is_ltxav(version) || sd_version_is_minimax_h3(version)) {
|
||||
return std::make_shared<TinyVideoAutoEncoder>(backend_for(SDBackendModule::VAE),
|
||||
tensor_storage_map,
|
||||
"decoder",
|
||||
@@ -2297,7 +2315,7 @@ public:
|
||||
return;
|
||||
}
|
||||
} else if (channels == 24) {
|
||||
if(sd_version_is_minimax_h3(version)){
|
||||
if (sd_version_is_minimax_h3(version)) {
|
||||
latent_rgb_proj = minimax_latent_rgb_proj;
|
||||
latent_rgb_bias = minimax_latent_rgb_bias;
|
||||
} else {
|
||||
@@ -2652,7 +2670,9 @@ public:
|
||||
denoised = denoised * denoise_mask + init_latent * (1.0f - denoise_mask);
|
||||
}
|
||||
if (sd_should_preview_denoised() && preview.callback != nullptr) {
|
||||
preview_image(step, denoised, version, preview.mode, preview.callback, preview.data, false);
|
||||
if (step % sd_get_preview_interval() == 0) {
|
||||
preview_image(step, denoised, version, preview.mode, preview.callback, preview.data, false);
|
||||
}
|
||||
}
|
||||
report_sample_progress(step, steps, &last_progress_us);
|
||||
sd::guidance::GuiderOutput output;
|
||||
@@ -2661,7 +2681,9 @@ public:
|
||||
}
|
||||
|
||||
if (sd_should_preview_noisy() && preview.callback != nullptr) {
|
||||
preview_image(step, noised_input, version, preview.mode, preview.callback, preview.data, true);
|
||||
if (step % sd_get_preview_interval() == 0) {
|
||||
preview_image(step, noised_input, version, preview.mode, preview.callback, preview.data, true);
|
||||
}
|
||||
}
|
||||
|
||||
sd::Tensor<float> cond_out;
|
||||
@@ -2743,7 +2765,11 @@ public:
|
||||
condition.c_reference_blocks.empty() ? nullptr : &condition.c_reference_blocks,
|
||||
audio_length,
|
||||
std::isfinite(active_flow_shift) ? active_flow_shift : 12.f,
|
||||
3.f};
|
||||
3.f,
|
||||
method == EULER_SAMPLE_METHOD && step > 0 &&
|
||||
static_cast<size_t>(step) < sigmas.size()
|
||||
? sigmas[step]
|
||||
: -1.f};
|
||||
} else if (sd_version_is_ltxav(version)) {
|
||||
diffusion_params.extra = LTXAVDiffusionExtra{
|
||||
nullptr,
|
||||
@@ -2871,7 +2897,9 @@ public:
|
||||
denoised = denoised * denoise_mask + init_latent * (1.0f - denoise_mask);
|
||||
}
|
||||
if (sd_should_preview_denoised() && preview.callback != nullptr) {
|
||||
preview_image(step, denoised, version, preview.mode, preview.callback, preview.data, false);
|
||||
if (step % sd_get_preview_interval() == 0) {
|
||||
preview_image(step, denoised, version, preview.mode, preview.callback, preview.data, false);
|
||||
}
|
||||
}
|
||||
report_sample_progress(step, steps, &last_progress_us);
|
||||
output.pred = denoised;
|
||||
@@ -3291,6 +3319,9 @@ const char* sample_method_to_str[] = {
|
||||
"lms",
|
||||
};
|
||||
|
||||
static_assert(SAMPLE_METHOD_COUNT == sizeof(sample_method_to_str) / sizeof(sample_method_to_str[0]),
|
||||
"\nnumber of elements in sample_method_to_str[] != SAMPLE_METHOD_COUNT");
|
||||
|
||||
const char* sd_sample_method_name(enum sample_method_t sample_method) {
|
||||
if (sample_method < SAMPLE_METHOD_COUNT) {
|
||||
return sample_method_to_str[sample_method];
|
||||
@@ -3326,6 +3357,9 @@ const char* scheduler_to_str[] = {
|
||||
"beta",
|
||||
};
|
||||
|
||||
static_assert(SCHEDULER_COUNT == sizeof(scheduler_to_str) / sizeof(scheduler_to_str[0]),
|
||||
"\nnumber of elements in scheduler_to_str[] != SCHEDULER_COUNT");
|
||||
|
||||
const char* sd_scheduler_name(enum scheduler_t scheduler) {
|
||||
if (scheduler < SCHEDULER_COUNT) {
|
||||
return scheduler_to_str[scheduler];
|
||||
@@ -4713,7 +4747,11 @@ static sd::Tensor<float> prepare_minimax_h3_reference_waveform(const sd_audio_t&
|
||||
static_cast<long double>(audio.sample_count) * target_sample_rate / audio.sample_rate));
|
||||
output_samples = std::max<uint64_t>(1, output_samples);
|
||||
uint64_t padded_samples = (output_samples + 799) / 800 * 800;
|
||||
sd::Tensor<float> waveform({static_cast<int64_t>(padded_samples), 2, 1, 1});
|
||||
// Keep stereo streams planar for the mono-per-stream audio encoder:
|
||||
// [samples, 1, stereo, batch]. This avoids flattening interleaved L/R
|
||||
// storage into alternating samples when the encoder folds streams into
|
||||
// its batch dimension.
|
||||
sd::Tensor<float> waveform({static_cast<int64_t>(padded_samples), 1, 2, 1});
|
||||
|
||||
for (uint64_t i = 0; i < output_samples; ++i) {
|
||||
long double source_pos = static_cast<long double>(i) * audio.sample_rate / target_sample_rate;
|
||||
@@ -4724,7 +4762,7 @@ static sd::Tensor<float> prepare_minimax_h3_reference_waveform(const sd_audio_t&
|
||||
uint32_t source_channel = audio.channels == 1 ? 0 : std::min<uint32_t>(channel, audio.channels - 1);
|
||||
float a = audio.data[source0 * audio.channels + source_channel];
|
||||
float b = audio.data[source1 * audio.channels + source_channel];
|
||||
waveform.index(static_cast<int64_t>(i), channel, 0, 0) =
|
||||
waveform.index(static_cast<int64_t>(i), 0, channel, 0) =
|
||||
std::clamp(a + (b - a) * fraction, -1.f, 1.f);
|
||||
}
|
||||
}
|
||||
@@ -5584,6 +5622,18 @@ SD_API bool generate_image(sd_ctx_t* sd_ctx,
|
||||
return false;
|
||||
}
|
||||
|
||||
// MiniMax-H3 is video-only. Its denoiser always splits the packed latent into a video and an
|
||||
// audio half, and only generate_video ever computes the audio length, so reaching this
|
||||
// function with an H3 checkpoint is guaranteed to die on
|
||||
// GGML_ASSERT(!audio_input_cache.empty()) with a core dump, after the several minutes it
|
||||
// takes to load the weights, and with nothing in the output pointing at the missing --mode.
|
||||
// (The AnimateDiff path below routes vid_gen back through here, but that is SD1.5 plus a
|
||||
// motion module, never H3.)
|
||||
if (sd_version_is_minimax_h3(sd_ctx->sd->version)) {
|
||||
LOG_ERROR("MiniMax-H3 is a video model and cannot be run in img_gen mode; use --mode vid_gen");
|
||||
return false;
|
||||
}
|
||||
|
||||
sd_ctx->sd->reset_cancel_flag();
|
||||
|
||||
int64_t t0 = ggml_time_ms();
|
||||
|
||||
Reference in New Issue
Block a user