mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-09-21 21:47:49 -05:00
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9b6e27e9f | ||
|
|
134c8212de | ||
|
|
2540a4fc25 | ||
|
|
dc4000d9f8 | ||
|
|
c797899732 | ||
|
|
afd5306d88 | ||
|
|
2c929495ab | ||
|
|
be0e34480d | ||
|
|
50d6405683 | ||
|
|
0a565f2950 | ||
|
|
12ee60dc02 | ||
|
|
97d2990807 | ||
|
|
16304cc3fd | ||
|
|
760717a060 | ||
|
|
88b044be7f | ||
|
|
1706b32813 | ||
|
|
58b6cb6b0c | ||
|
|
6100d8339b | ||
|
|
de298c225b | ||
|
|
fabe481212 | ||
|
|
06c359f17a |
@@ -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.
|
||||
|
||||
+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
|
||||
|
||||
|
||||
+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.
|
||||
+12
-5
@@ -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";
|
||||
|
||||
@@ -93,12 +94,16 @@ struct SDCliParams {
|
||||
options.int_options = {
|
||||
{"",
|
||||
"--preview-interval",
|
||||
"interval in denoising steps between consecutive updates of the image preview file (default is 1, meaning updating at every step)",
|
||||
"preview interval: in each sampling pass, positive N updates every Nth denoiser step and -N previews only completed logical step N; 0 previews the final completed step of the first pass (base-resolution or high-noise). Default: 1",
|
||||
&preview_interval},
|
||||
{"",
|
||||
"--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: 3f85508cdd...e20c3a14aa
@@ -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 {
|
||||
@@ -439,6 +446,9 @@ typedef bool (*sd_graph_eval_callback_t)(struct ggml_tensor* t, bool ask, void*
|
||||
|
||||
SD_API void sd_set_log_callback(sd_log_cb_t sd_log_cb, void* data);
|
||||
SD_API void sd_set_progress_callback(sd_progress_cb_t cb, void* data);
|
||||
// In each sampling pass, a positive interval previews every Nth denoiser step, while a
|
||||
// negative interval previews only completed logical step -interval. Zero previews the final
|
||||
// completed step of the first sampling pass (base-resolution or high-noise).
|
||||
SD_API void sd_set_preview_callback(sd_preview_cb_t cb, enum preview_t mode, int interval, bool denoised, bool noisy, void* data);
|
||||
SD_API void sd_set_backend_eval_callback(sd_graph_eval_callback_t cb, void* data);
|
||||
SD_API int32_t sd_get_num_physical_cores();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+54
-19
@@ -2456,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(),
|
||||
@@ -2479,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3404,7 +3407,6 @@ protected:
|
||||
bool bias;
|
||||
bool force_f32;
|
||||
bool force_prec_f32;
|
||||
bool allow_weight_scale;
|
||||
bool has_weight_scale = false;
|
||||
bool int8_convrot = false;
|
||||
int int8_convrot_group_size = 0;
|
||||
@@ -3427,8 +3429,11 @@ protected:
|
||||
}
|
||||
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;
|
||||
if ((allow_weight_scale || is_int8_tensorwise) && 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_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) {
|
||||
@@ -3442,17 +3447,15 @@ protected:
|
||||
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_) {
|
||||
@@ -3464,7 +3467,21 @@ 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) {
|
||||
bool supports_fp8_matmul = false;
|
||||
if (ctx->backend != nullptr) {
|
||||
ggml_tensor* fp8_matmul = ggml_mul_mat(ctx->ggml_ctx, w, x);
|
||||
if (force_prec_f32) {
|
||||
ggml_mul_mat_set_prec(fp8_matmul, GGML_PREC_F32);
|
||||
}
|
||||
supports_fp8_matmul = ggml_backend_supports_op(ctx->backend, fp8_matmul);
|
||||
}
|
||||
if (!supports_fp8_matmul) {
|
||||
w = ggml_cast(ctx->ggml_ctx, w, GGML_TYPE_BF16);
|
||||
}
|
||||
}
|
||||
ggml_tensor* b = nullptr;
|
||||
if (bias) {
|
||||
b = params["bias"];
|
||||
@@ -3495,7 +3512,7 @@ public:
|
||||
out = ggml_ext_linear_i8_tensorwise(ctx->ggml_ctx,
|
||||
x,
|
||||
w,
|
||||
params["weight_scale"],
|
||||
weight_scale,
|
||||
b,
|
||||
int8_convrot ? int8_convrot_group_size : 0,
|
||||
scale);
|
||||
@@ -3514,6 +3531,30 @@ public:
|
||||
}
|
||||
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;
|
||||
@@ -3523,12 +3564,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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -87,9 +87,9 @@ 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") {
|
||||
@@ -328,12 +328,10 @@ bool read_safetensors_file(const std::string& file_path,
|
||||
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
|
||||
|
||||
@@ -54,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();
|
||||
|
||||
+2
-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());
|
||||
|
||||
@@ -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."},
|
||||
};
|
||||
|
||||
@@ -1459,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."},
|
||||
@@ -1569,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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef __SD_RUNTIME_PREVIEW_INTERVAL_H__
|
||||
#define __SD_RUNTIME_PREVIEW_INTERVAL_H__
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace sd::preview {
|
||||
|
||||
constexpr std::uint64_t logical_sample_step(int step) {
|
||||
return step < 0 ? static_cast<std::uint64_t>(-static_cast<std::int64_t>(step))
|
||||
: static_cast<std::uint64_t>(step);
|
||||
}
|
||||
|
||||
constexpr bool sample_step_is_complete(int step,
|
||||
std::size_t total_steps,
|
||||
bool terminal_sigma_is_zero) {
|
||||
return step > 0 ||
|
||||
(terminal_sigma_is_zero &&
|
||||
step < 0 &&
|
||||
logical_sample_step(step) == static_cast<std::uint64_t>(total_steps));
|
||||
}
|
||||
|
||||
constexpr bool should_preview_sample_step(int step,
|
||||
std::size_t total_steps,
|
||||
bool terminal_sigma_is_zero,
|
||||
int interval,
|
||||
bool preview_final_step) {
|
||||
if (interval > 0) {
|
||||
return step % interval == 0;
|
||||
}
|
||||
if (!sample_step_is_complete(step, total_steps, terminal_sigma_is_zero)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint64_t logical_step = logical_sample_step(step);
|
||||
if (interval < 0) {
|
||||
std::uint64_t requested_step = static_cast<std::uint64_t>(-static_cast<std::int64_t>(interval));
|
||||
return logical_step == requested_step;
|
||||
}
|
||||
return preview_final_step && logical_step == static_cast<std::uint64_t>(total_steps);
|
||||
}
|
||||
} // namespace sd::preview
|
||||
|
||||
#endif // __SD_RUNTIME_PREVIEW_INTERVAL_H__
|
||||
+56
-21
@@ -61,6 +61,7 @@
|
||||
#include "model/vae/wan_vae.hpp"
|
||||
#include "runtime/denoiser.hpp"
|
||||
#include "runtime/guidance.h"
|
||||
#include "runtime/preview_interval.h"
|
||||
#include "runtime/sample-cache.h"
|
||||
#include "upscaler.h"
|
||||
|
||||
@@ -150,6 +151,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) {
|
||||
@@ -1023,11 +1027,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();
|
||||
|
||||
@@ -1402,7 +1406,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",
|
||||
@@ -2312,7 +2316,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 {
|
||||
@@ -2464,8 +2468,11 @@ public:
|
||||
sd_get_preview_mode()};
|
||||
}
|
||||
|
||||
void report_sample_progress(int step, size_t total_steps, int64_t* last_progress_us) {
|
||||
if (step > 0 || step == -(int)total_steps) {
|
||||
void report_sample_progress(int step,
|
||||
size_t total_steps,
|
||||
bool terminal_sigma_is_zero,
|
||||
int64_t* last_progress_us) {
|
||||
if (sd::preview::sample_step_is_complete(step, total_steps, terminal_sigma_is_zero)) {
|
||||
int64_t now = ggml_time_us();
|
||||
int showstep = std::abs(step);
|
||||
float step_seconds = last_progress_us != nullptr && *last_progress_us > 0
|
||||
@@ -2527,6 +2534,7 @@ public:
|
||||
int audio_length,
|
||||
float frame_rate,
|
||||
const sd_cache_params_t* cache_params,
|
||||
bool preview_final_step,
|
||||
const sd::Tensor<float>& video_positions = {}) {
|
||||
struct RunnerDoneOnExit {
|
||||
GGMLRunner* runner = nullptr;
|
||||
@@ -2586,8 +2594,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
size_t steps = sigmas.size() - 1;
|
||||
bool has_skiplayer = (slg_scale != 0.0f || slg_uncond) && !skip_layers.empty();
|
||||
size_t steps = sigmas.size() - 1;
|
||||
bool terminal_sigma_is_zero = sigmas.back() == 0.f;
|
||||
bool has_skiplayer = (slg_scale != 0.0f || slg_uncond) && !skip_layers.empty();
|
||||
if (has_skiplayer && !sd_version_is_dit(version)) {
|
||||
has_skiplayer = false;
|
||||
LOG_WARN("SLG is incompatible with this model type");
|
||||
@@ -2636,6 +2645,13 @@ public:
|
||||
float c_out = scaling[1];
|
||||
float c_in = scaling[2];
|
||||
|
||||
bool preview_needed = preview.callback != nullptr &&
|
||||
sd::preview::should_preview_sample_step(step,
|
||||
steps,
|
||||
terminal_sigma_is_zero,
|
||||
sd_get_preview_interval(),
|
||||
preview_final_step);
|
||||
|
||||
std::vector<float> base_timesteps_vec = prepare_sample_timesteps(sigma, shifted_timestep);
|
||||
std::vector<float> timesteps_vec = base_timesteps_vec;
|
||||
sd::Tensor<float> audio_timesteps_tensor;
|
||||
@@ -2666,16 +2682,16 @@ public:
|
||||
if (!denoise_mask.empty()) {
|
||||
denoised = denoised * denoise_mask + init_latent * (1.0f - denoise_mask);
|
||||
}
|
||||
if (sd_should_preview_denoised() && preview.callback != nullptr) {
|
||||
if (preview_needed && sd_should_preview_denoised()) {
|
||||
preview_image(step, denoised, version, preview.mode, preview.callback, preview.data, false);
|
||||
}
|
||||
report_sample_progress(step, steps, &last_progress_us);
|
||||
report_sample_progress(step, steps, terminal_sigma_is_zero, &last_progress_us);
|
||||
sd::guidance::GuiderOutput output;
|
||||
output.pred = denoised;
|
||||
return output;
|
||||
}
|
||||
|
||||
if (sd_should_preview_noisy() && preview.callback != nullptr) {
|
||||
if (preview_needed && sd_should_preview_noisy()) {
|
||||
preview_image(step, noised_input, version, preview.mode, preview.callback, preview.data, true);
|
||||
}
|
||||
|
||||
@@ -2758,7 +2774,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,
|
||||
@@ -2885,10 +2905,10 @@ public:
|
||||
if (!denoise_mask.empty()) {
|
||||
denoised = denoised * denoise_mask + init_latent * (1.0f - denoise_mask);
|
||||
}
|
||||
if (sd_should_preview_denoised() && preview.callback != nullptr) {
|
||||
if (preview_needed && sd_should_preview_denoised()) {
|
||||
preview_image(step, denoised, version, preview.mode, preview.callback, preview.data, false);
|
||||
}
|
||||
report_sample_progress(step, steps, &last_progress_us);
|
||||
report_sample_progress(step, steps, terminal_sigma_is_zero, &last_progress_us);
|
||||
output.pred = denoised;
|
||||
return output;
|
||||
};
|
||||
@@ -3306,6 +3326,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];
|
||||
@@ -3341,6 +3364,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];
|
||||
@@ -4728,7 +4754,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;
|
||||
@@ -4739,7 +4769,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);
|
||||
}
|
||||
}
|
||||
@@ -5696,7 +5726,8 @@ SD_API bool generate_image(sd_ctx_t* sd_ctx,
|
||||
1.f,
|
||||
0,
|
||||
static_cast<float>(request.fps),
|
||||
request.cache_params);
|
||||
request.cache_params,
|
||||
true);
|
||||
int64_t sampling_end = ggml_time_ms();
|
||||
if (!x_0.empty()) {
|
||||
LOG_INFO("sampling completed, taking %.2fs", (sampling_end - sampling_start) * 1.0f / 1000);
|
||||
@@ -5817,7 +5848,8 @@ SD_API bool generate_image(sd_ctx_t* sd_ctx,
|
||||
1.f,
|
||||
0,
|
||||
static_cast<float>(request.fps),
|
||||
request.cache_params);
|
||||
request.cache_params,
|
||||
false);
|
||||
int64_t hires_sample_end = ggml_time_ms();
|
||||
if (!x_0.empty()) {
|
||||
LOG_INFO("hires sampling %d/%d completed, taking %.2fs",
|
||||
@@ -6951,6 +6983,7 @@ SD_API bool generate_video(sd_ctx_t* sd_ctx,
|
||||
latents.audio_length,
|
||||
static_cast<float>(request.fps),
|
||||
request.cache_params,
|
||||
true,
|
||||
latents.video_positions);
|
||||
int64_t sampling_end = ggml_time_ms();
|
||||
if (x_t_sampled.empty()) {
|
||||
@@ -6993,6 +7026,7 @@ SD_API bool generate_video(sd_ctx_t* sd_ctx,
|
||||
latents.audio_length,
|
||||
static_cast<float>(request.fps),
|
||||
request.cache_params,
|
||||
plan.high_noise_sample_steps <= 0,
|
||||
latents.video_positions);
|
||||
|
||||
int64_t sampling_end = ggml_time_ms();
|
||||
@@ -7131,6 +7165,7 @@ SD_API bool generate_video(sd_ctx_t* sd_ctx,
|
||||
latents.audio_length,
|
||||
static_cast<float>(hires_request.fps),
|
||||
hires_request.cache_params,
|
||||
false,
|
||||
hires_video_positions);
|
||||
sampling_end = ggml_time_ms();
|
||||
if (final_latent.empty()) {
|
||||
|
||||
Reference in New Issue
Block a user