mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-09-21 21:47:49 -05:00
feat: add Wan2.2 S2V (audio+img-to-video) support (#1925)
Co-authored-by: leejet <leejet714@gmail.com>
This commit is contained in:
+49
@@ -34,6 +34,10 @@
|
||||
- Wan2.2 I2V A14B
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/tree/main/split_files/diffusion_models
|
||||
- gguf: https://huggingface.co/QuantStack/Wan2.2-I2V-A14B-GGUF/tree/main
|
||||
- Wan2.2 S2V 14B
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/tree/main/split_files/diffusion_models
|
||||
- gguf: https://huggingface.co/QuantStack/Wan2.2-S2V-14B-GGUF/tree/main
|
||||
- int8_convrot safetensors: https://huggingface.co/noctrex/Wan2.2-S2V-14B-int8_convrot
|
||||
- Download vae
|
||||
- wan_2.1_vae (for all the wan model except Wan2.2 TI2V 5B)
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/blob/main/split_files/vae/wan_2.1_vae.safetensors
|
||||
@@ -49,6 +53,9 @@
|
||||
- Download clip_vison_h (for Wan2.1 I2V/FLF2V only)
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/blob/main/split_files/clip_vision/clip_vision_h.safetensors
|
||||
|
||||
- Download audio_encoder (for Wan2.2 S2V only)
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/blob/main/split_files/audio_encoders/wav2vec2_large_english_fp16.safetensors
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -94,6 +101,48 @@
|
||||
|
||||
<video src=../assets/wan/Wan2.2_14B_i2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
|
||||
### Wan2.2 S2V 14B
|
||||
|
||||
Audio-driven video (speech-to-video). The reference image (`-i`) is the speaker
|
||||
portrait, `--audio` is the driving audio track and `--audio-encoder` is the
|
||||
wav2vec2 audio encoder. Wan2.2 S2V requires the wan_2.1 vae (16 channel), not
|
||||
the wan2.2 vae.
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.2_s2v-14B-Q8_0.gguf --audio-encoder ..\models\audio_encoders\wav2vec2_large_english_fp16.safetensors --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a person is talking" --cfg-scale 6.0 --steps 20 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --offload-to-cpu --vae-tiling --video-frames 81 -i ..\assets\cat_with_sd_cpp_42.png --audio .\input\speech.wav --flow-shift 3.0
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Recommended settings: `--sampling-method euler --steps 20 --cfg-scale 6.0`.
|
||||
`dpm++2m` produces heavy artifacts on S2V. 4 steps with the lightning LoRA
|
||||
(below) is the fast option.
|
||||
- Resolutions: width and height must be multiples of 16; the examples use
|
||||
multiples of 64. 832x480 is a fast starting point; generation cost scales
|
||||
with pixel area.
|
||||
- `--audio` accepts a WAV file; it is downmixed to mono and resampled to 16 kHz
|
||||
internally. Audio longer than the video is truncated, video longer than the
|
||||
audio is padded with silence. Pick `--video-frames` to match the audio:
|
||||
roughly `audio_seconds * 16` frames, capped at one chunk (77-81 frames,
|
||||
~5 s at the model's 16 fps). 33, 77 and 81 map to clean latent frame counts.
|
||||
- S2V always uses 16 fps. Other requested frame rates are automatically
|
||||
changed to 16 with a warning, including the CLI and server video output.
|
||||
`generate_video()` returns the actual frame rate through `fps_out`; C API
|
||||
callers should use that value when encoding the output video.
|
||||
- One generation covers the first S2V chunk window (`--video-frames` frames).
|
||||
Long-video chunked extend mode is not implemented yet.
|
||||
- Speed: the lightx2v lightning LoRA works with S2V at 4 steps and
|
||||
`--cfg-scale 1.0`. Use the **low_noise** variant;
|
||||
the high_noise variant produces artifacts on S2V:
|
||||
|
||||
```
|
||||
--lora-model-dir ..\models\loras
|
||||
-p "...<lora:lightx2v-Wan2.2-T2V-A14B-4steps-lora-rank64-Seko-V2.0-low_noise:1.0>"
|
||||
--cfg-scale 1.0 --steps 4
|
||||
```
|
||||
|
||||
Expect some quality/dynamics loss compared to the full 20-step run.
|
||||
|
||||
### Wan2.2 T2V A14B T2I
|
||||
|
||||
```
|
||||
|
||||
@@ -419,7 +419,8 @@ void step_callback(int step, int frame_count, sd_image_t* image, bool is_noisy,
|
||||
LOG_ERROR("save preview image to '%s' failed", path.string().c_str());
|
||||
}
|
||||
} else {
|
||||
if (create_video_from_sd_images(cli_params->preview_path.c_str(), image, frame_count, cli_params->preview_fps, cli_params->compression_quality) != 0) {
|
||||
int fps = cli_params->preview_method == PREVIEW_PROJ ? cli_params->preview_fps / 4 : cli_params->preview_fps;
|
||||
if (create_video_from_sd_images(cli_params->preview_path.c_str(), image, frame_count, fps, cli_params->compression_quality) != 0) {
|
||||
LOG_ERROR("save preview video to '%s' failed", cli_params->preview_path.c_str());
|
||||
}
|
||||
}
|
||||
@@ -687,8 +688,6 @@ int main(int argc, const char* argv[]) {
|
||||
}
|
||||
}
|
||||
cli_params.preview_fps = gen_params.fps;
|
||||
if (cli_params.preview_method == PREVIEW_PROJ)
|
||||
cli_params.preview_fps /= 4;
|
||||
|
||||
sd_set_preview_callback(step_callback,
|
||||
cli_params.preview_method,
|
||||
@@ -951,9 +950,10 @@ int main(int argc, const char* argv[]) {
|
||||
} else if (cli_params.mode == VID_GEN) {
|
||||
sd_vid_gen_params_t vid_gen_params = gen_params.to_sd_vid_gen_params_t();
|
||||
sd_image_t* generated_video = nullptr;
|
||||
if (!generate_video(sd_ctx.get(), &vid_gen_params, &generated_video, &num_results, &generated_audio)) {
|
||||
if (!generate_video(sd_ctx.get(), &vid_gen_params, &generated_video, &num_results, &generated_audio, &cli_params.preview_fps)) {
|
||||
generated_video = nullptr;
|
||||
}
|
||||
gen_params.fps = cli_params.preview_fps;
|
||||
results.adopt(generated_video, num_results);
|
||||
}
|
||||
|
||||
|
||||
@@ -460,6 +460,11 @@ ArgOptions SDContextParams::get_options() {
|
||||
"path to standalone LTX audio vae model",
|
||||
0,
|
||||
&audio_vae_path},
|
||||
{"",
|
||||
"--audio-encoder",
|
||||
"path to wav2vec2 audio encoder model (Wan2.2 S2V)",
|
||||
0,
|
||||
&audio_encoder_path},
|
||||
{"",
|
||||
"--taesd",
|
||||
"path to taesd. Using Tiny AutoEncoder for fast decoding (low quality)",
|
||||
@@ -898,6 +903,7 @@ std::string SDContextParams::to_string() const {
|
||||
<< " vae_path: \"" << vae_path << "\",\n"
|
||||
<< " vae_format: \"" << vae_format << "\",\n"
|
||||
<< " audio_vae_path: \"" << audio_vae_path << "\",\n"
|
||||
<< " audio_encoder_path: \"" << audio_encoder_path << "\",\n"
|
||||
<< " taesd_path: \"" << taesd_path << "\",\n"
|
||||
<< " esrgan_path: \"" << esrgan_path << "\",\n"
|
||||
<< " control_net_path: \"" << control_net_path << "\",\n"
|
||||
@@ -963,6 +969,7 @@ sd_ctx_params_t SDContextParams::to_sd_ctx_params_t(bool taesd_preview) {
|
||||
sd_ctx_params.embeddings_connectors_path = embeddings_connectors_path.c_str();
|
||||
sd_ctx_params.vae_path = vae_path.c_str();
|
||||
sd_ctx_params.audio_vae_path = audio_vae_path.c_str();
|
||||
sd_ctx_params.audio_encoder_path = audio_encoder_path.c_str();
|
||||
sd_ctx_params.taesd_path = taesd_path.c_str();
|
||||
sd_ctx_params.control_net_path = control_net_path.c_str();
|
||||
sd_ctx_params.ip_adapter_path = ip_adapter_path.c_str();
|
||||
@@ -1515,6 +1522,14 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
return 1;
|
||||
};
|
||||
|
||||
auto on_audio_arg = [&](int argc, const char** argv, int index) {
|
||||
if (++index >= argc) {
|
||||
return -1;
|
||||
}
|
||||
ref_audio_paths.push_back(argv[index]);
|
||||
return 1;
|
||||
};
|
||||
|
||||
auto on_cache_mode_arg = [&](int argc, const char** argv, int index) {
|
||||
if (++index >= argc) {
|
||||
return -1;
|
||||
@@ -1704,6 +1719,10 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
"--ref-audio",
|
||||
"standalone WAV reference for MiniMax-H3 Ref2VA (can be used multiple times)",
|
||||
on_ref_audio_arg},
|
||||
{"",
|
||||
"--audio",
|
||||
"driving audio track (Wan2.2 S2V; can be used once)",
|
||||
on_audio_arg},
|
||||
{"",
|
||||
"--cache-mode",
|
||||
"caching method: 'easycache' (DiT), 'ucache' (UNET), 'dbcache'/'taylorseer'/'cache-dit' (DiT block-level), 'spectrum' (UNET/DiT Chebyshev+Taylor forecasting)",
|
||||
|
||||
@@ -131,6 +131,7 @@ struct SDContextParams {
|
||||
std::string vae_path;
|
||||
std::string vae_format = "auto";
|
||||
std::string audio_vae_path;
|
||||
std::string audio_encoder_path;
|
||||
std::string taesd_path;
|
||||
std::string esrgan_path;
|
||||
std::string control_net_path;
|
||||
|
||||
@@ -245,7 +245,7 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
|
||||
sd_image_t* raw_results = nullptr;
|
||||
if (!generate_video(runtime.sd_ctx, ¶ms, &raw_results, &num_results, &generated_audio)) {
|
||||
if (!generate_video(runtime.sd_ctx, ¶ms, &raw_results, &num_results, &generated_audio, &output_fps)) {
|
||||
raw_results = nullptr;
|
||||
}
|
||||
results.adopt(raw_results, num_results);
|
||||
@@ -261,7 +261,7 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
std::vector<uint8_t> video_bytes = create_video_from_sd_images_to_vector(job.vid_gen.output_format,
|
||||
results.data(),
|
||||
num_results,
|
||||
job.vid_gen.gen_params.fps,
|
||||
output_fps,
|
||||
job.vid_gen.output_compression,
|
||||
generated_audio);
|
||||
free_sd_audio(generated_audio);
|
||||
@@ -273,7 +273,6 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
output_media_b64 = base64_encode(video_bytes);
|
||||
output_media_mime_type = video_mime_type(job.vid_gen.output_format);
|
||||
output_frame_count = num_results;
|
||||
output_fps = job.vid_gen.gen_params.fps;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -208,6 +208,7 @@ typedef struct {
|
||||
const char* embeddings_connectors_path;
|
||||
const char* vae_path;
|
||||
const char* audio_vae_path;
|
||||
const char* audio_encoder_path;
|
||||
const char* taesd_path;
|
||||
const char* control_net_path;
|
||||
const char* ip_adapter_path;
|
||||
@@ -521,11 +522,13 @@ enum sd_cancel_mode_t {
|
||||
SD_API void sd_cancel_generation(sd_ctx_t* sd_ctx, enum sd_cancel_mode_t mode);
|
||||
|
||||
SD_API void sd_vid_gen_params_init(sd_vid_gen_params_t* sd_vid_gen_params);
|
||||
// If non-NULL, fps_out receives the effective encoding frame rate before preview callbacks.
|
||||
SD_API bool generate_video(sd_ctx_t* sd_ctx,
|
||||
const sd_vid_gen_params_t* sd_vid_gen_params,
|
||||
sd_image_t** frames_out,
|
||||
int* num_frames_out,
|
||||
sd_audio_t** audio_out);
|
||||
sd_audio_t** audio_out,
|
||||
int* fps_out);
|
||||
|
||||
typedef struct upscaler_ctx_t upscaler_ctx_t;
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
#include "wan_audio.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
namespace sd::wan_audio {
|
||||
|
||||
static BucketPlan plan_buckets(int audio_frames, int batch_frames, int video_rate, int fps) {
|
||||
BucketPlan plan;
|
||||
plan.audio_frames = audio_frames;
|
||||
plan.batch_frames = batch_frames;
|
||||
plan.video_rate = video_rate;
|
||||
plan.fps = fps;
|
||||
const double scale = static_cast<double>(video_rate) / fps;
|
||||
// Keep a trailing chunk even when audio ends on a chunk boundary.
|
||||
plan.num_chunks = static_cast<int>(audio_frames / (batch_frames * scale)) + 1;
|
||||
plan.bucket_frames = plan.num_chunks * batch_frames;
|
||||
plan.padded_audio_frames = static_cast<int>(
|
||||
std::ceil(plan.bucket_frames / static_cast<double>(fps) * video_rate));
|
||||
return plan;
|
||||
}
|
||||
|
||||
// Match NumPy's round-half-even sampling.
|
||||
static int bucket_source_frame(int bucket_frame, int video_rate, int fps) {
|
||||
return static_cast<int>(std::nearbyint(static_cast<double>(bucket_frame) * video_rate / fps));
|
||||
}
|
||||
|
||||
static int interpolated_frame_count(int in_frames, int input_fps, int output_fps) {
|
||||
return static_cast<int>(in_frames / static_cast<double>(input_fps) * output_fps);
|
||||
}
|
||||
|
||||
// Match PyTorch linear interpolation with align_corners=True.
|
||||
static std::vector<float> linear_interpolate_frames(const std::vector<float>& in,
|
||||
int num_layers,
|
||||
int in_frames,
|
||||
int dim,
|
||||
int out_frames) {
|
||||
std::vector<float> out(static_cast<size_t>(num_layers) * out_frames * dim, 0.0f);
|
||||
if (in.empty() || in_frames <= 0 || out_frames <= 0 || num_layers <= 0 || dim <= 0) {
|
||||
return out;
|
||||
}
|
||||
const double scale = out_frames > 1 ? static_cast<double>(in_frames - 1) / (out_frames - 1) : 0.0;
|
||||
for (int layer = 0; layer < num_layers; ++layer) {
|
||||
for (int out_i = 0; out_i < out_frames; ++out_i) {
|
||||
const double pos = out_i * scale;
|
||||
const int src0 = static_cast<int>(pos);
|
||||
const int src1 = std::min(src0 + 1, in_frames - 1);
|
||||
const float frac = static_cast<float>(pos - src0);
|
||||
const float* in_row = &in[(static_cast<size_t>(layer) * in_frames + src0) * dim];
|
||||
const float* in_next = &in[(static_cast<size_t>(layer) * in_frames + src1) * dim];
|
||||
float* out_row = &out[(static_cast<size_t>(layer) * out_frames + out_i) * dim];
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
out_row[d] = in_row[d] * (1.0f - frac) + in_next[d] * frac;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<float> build_audio_buckets(const float* stacked_states,
|
||||
int num_layers,
|
||||
int in_frames,
|
||||
int dim,
|
||||
int batch_frames,
|
||||
BucketPlan* plan_out,
|
||||
int input_fps,
|
||||
int video_rate,
|
||||
int fps) {
|
||||
if (stacked_states == nullptr || num_layers <= 0 || in_frames <= 0 || dim <= 0 || batch_frames <= 0) {
|
||||
return {};
|
||||
}
|
||||
const int audio_frames = interpolated_frame_count(in_frames, input_fps, video_rate);
|
||||
if (audio_frames <= 0) {
|
||||
return {};
|
||||
}
|
||||
const std::vector<float> interpolated =
|
||||
linear_interpolate_frames(std::vector<float>(stacked_states,
|
||||
stacked_states + static_cast<size_t>(num_layers) * in_frames * dim),
|
||||
num_layers,
|
||||
in_frames,
|
||||
dim,
|
||||
audio_frames);
|
||||
const BucketPlan plan = plan_buckets(audio_frames, batch_frames, video_rate, fps);
|
||||
if (plan_out != nullptr) {
|
||||
*plan_out = plan;
|
||||
}
|
||||
std::vector<float> buckets(static_cast<size_t>(plan.bucket_frames) * num_layers * dim, 0.0f);
|
||||
for (int frame = 0; frame < plan.bucket_frames; ++frame) {
|
||||
const int src = bucket_source_frame(frame, video_rate, fps);
|
||||
if (src >= plan.audio_frames) {
|
||||
continue;
|
||||
}
|
||||
for (int layer = 0; layer < num_layers; ++layer) {
|
||||
std::copy_n(interpolated.data() + (static_cast<size_t>(layer) * audio_frames + src) * dim,
|
||||
static_cast<size_t>(dim),
|
||||
buckets.data() + (static_cast<size_t>(frame) * num_layers + layer) * dim);
|
||||
}
|
||||
}
|
||||
return buckets;
|
||||
}
|
||||
|
||||
} // namespace sd::wan_audio
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef __SD_CONDITIONING_WAN_AUDIO_H__
|
||||
#define __SD_CONDITIONING_WAN_AUDIO_H__
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace sd::wan_audio {
|
||||
|
||||
struct BucketPlan {
|
||||
int audio_frames; // frames at video_rate
|
||||
int batch_frames; // latent_t * 4
|
||||
int video_rate;
|
||||
int fps; // bucket frame rate
|
||||
int num_chunks; // includes trailing padding
|
||||
int bucket_frames;
|
||||
int padded_audio_frames;
|
||||
};
|
||||
|
||||
// [layers, frames, dim] at input_fps -> [bucket_frames, layers, dim] at fps.
|
||||
// Pads past the audio end; returns an empty vector on invalid input.
|
||||
std::vector<float> build_audio_buckets(const float* stacked_states,
|
||||
int num_layers,
|
||||
int in_frames,
|
||||
int dim,
|
||||
int batch_frames,
|
||||
BucketPlan* plan_out = nullptr,
|
||||
int input_fps = 50,
|
||||
int video_rate = 30,
|
||||
int fps = 16);
|
||||
|
||||
} // namespace sd::wan_audio
|
||||
|
||||
#endif // __SD_CONDITIONING_WAN_AUDIO_H__
|
||||
@@ -325,6 +325,76 @@ ggml_tensor* ggml_ext_pad(ggml_context* ctx,
|
||||
return ggml_ext_pad_ext(ctx, nullptr, x, 0, p0, 0, p1, 0, p2, 0, p3, circular_x, circular_y);
|
||||
}
|
||||
|
||||
static ggml_tensor* conv_1d(ggml_context* ctx, ggml_tensor* x, ggml_tensor* w, int s0, int p0, int d0, bool force_prec_f32) {
|
||||
ggml_tensor* result;
|
||||
if (force_prec_f32) {
|
||||
ggml_tensor* patches = ggml_im2col(ctx, w, x, s0, 0, p0, 0, d0, 0, false, GGML_TYPE_F32);
|
||||
result = ggml_mul_mat(ctx,
|
||||
ggml_reshape_2d(ctx, patches, patches->ne[0], patches->ne[2] * patches->ne[1]),
|
||||
ggml_reshape_2d(ctx, w, w->ne[0] * w->ne[1], w->ne[2]));
|
||||
result = ggml_reshape_3d(ctx, result, patches->ne[1], w->ne[2], patches->ne[2]);
|
||||
} else {
|
||||
result = ggml_conv_1d(ctx, w, x, s0, p0, d0);
|
||||
}
|
||||
if (x->ne[2] > 1) {
|
||||
// mul_mat packs positions and batches before output channels: [OL, N, OC].
|
||||
result = ggml_reshape_3d(ctx, result, result->ne[0], x->ne[2], w->ne[2]);
|
||||
result = ggml_cont(ctx, ggml_permute(ctx, result, 0, 2, 1, 3));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ggml_tensor* ggml_ext_conv_1d(ggml_context* ctx,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* w,
|
||||
ggml_tensor* b,
|
||||
int s0,
|
||||
int p0,
|
||||
int d0,
|
||||
int64_t groups,
|
||||
bool force_prec_f32) {
|
||||
GGML_ASSERT(s0 > 0 && p0 >= 0 && d0 > 0 && groups > 0);
|
||||
GGML_ASSERT(x->type == GGML_TYPE_F32 && x->ne[3] == 1 && w->ne[3] == 1);
|
||||
GGML_ASSERT(x->ne[1] % groups == 0 && w->ne[2] % groups == 0);
|
||||
GGML_ASSERT(w->ne[1] == x->ne[1] / groups);
|
||||
GGML_ASSERT(b == nullptr || (b->type == GGML_TYPE_F32 && ggml_is_vector(b) && b->ne[0] == w->ne[2]));
|
||||
|
||||
// im2col requires contiguous time rows; group views must retain the real channel and batch strides.
|
||||
if (!ggml_is_contiguous(x)) {
|
||||
x = ggml_cont(ctx, x);
|
||||
}
|
||||
if (force_prec_f32 && w->type != GGML_TYPE_F32) {
|
||||
w = ggml_cast(ctx, w, GGML_TYPE_F32);
|
||||
}
|
||||
if (!ggml_is_contiguous(w)) {
|
||||
w = ggml_cont(ctx, w);
|
||||
}
|
||||
|
||||
ggml_tensor* result = nullptr;
|
||||
if (groups == 1) {
|
||||
result = conv_1d(ctx, x, w, s0, p0, d0, force_prec_f32);
|
||||
} else {
|
||||
const int64_t ic_g = x->ne[1] / groups;
|
||||
const int64_t oc_g = w->ne[2] / groups;
|
||||
std::vector<ggml_tensor*> outputs;
|
||||
outputs.reserve(groups);
|
||||
for (int64_t group = 0; group < groups; ++group) {
|
||||
ggml_tensor* x_i = ggml_view_3d(ctx, x, x->ne[0], ic_g, x->ne[2], x->nb[1], x->nb[2], group * ic_g * x->nb[1]);
|
||||
ggml_tensor* w_i = ggml_view_3d(ctx, w, w->ne[0], ic_g, oc_g, w->nb[1], w->nb[2], group * oc_g * w->nb[2]);
|
||||
outputs.push_back(conv_1d(ctx, x_i, w_i, s0, p0, d0, force_prec_f32));
|
||||
}
|
||||
result = ggml_ext_vec_concat(ctx, outputs, 1);
|
||||
}
|
||||
if (b != nullptr) {
|
||||
if (!ggml_is_contiguous(b)) {
|
||||
b = ggml_cont(ctx, b);
|
||||
}
|
||||
b = ggml_reshape_3d(ctx, b, 1, w->ne[2], 1);
|
||||
result = ggml_add_inplace(ctx, result, b);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ggml_tensor* ggml_ext_conv_2d(ggml_context* ctx,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* w,
|
||||
@@ -683,17 +753,16 @@ ggml_tensor* ggml_ext_group_norm(ggml_context* ctx,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* w,
|
||||
ggml_tensor* b,
|
||||
int num_groups) {
|
||||
int num_groups,
|
||||
float eps) {
|
||||
if (ggml_n_dims(x) >= 3 && w != nullptr && b != nullptr) {
|
||||
w = ggml_reshape_4d(ctx, w, 1, 1, w->ne[0], 1);
|
||||
b = ggml_reshape_4d(ctx, b, 1, 1, b->ne[0], 1);
|
||||
}
|
||||
|
||||
const float eps = 1e-6f; // default eps parameter
|
||||
x = ggml_group_norm(ctx, x, num_groups, eps);
|
||||
x = ggml_group_norm(ctx, x, num_groups, eps);
|
||||
if (w != nullptr && b != nullptr) {
|
||||
x = ggml_mul_inplace(ctx, x, w);
|
||||
// b = ggml_repeat(ctx, b, x);
|
||||
x = ggml_add_inplace(ctx, x, b);
|
||||
}
|
||||
return x;
|
||||
|
||||
+14
-1
@@ -103,6 +103,18 @@ ggml_tensor* ggml_ext_pad(ggml_context* ctx,
|
||||
bool circular_x = false,
|
||||
bool circular_y = false);
|
||||
|
||||
// ggml layout: x [L, IC, N], w [K, IC/groups, OC], b [OC], result [OL, OC, N].
|
||||
// force_prec_f32 keeps both input patches and weights in F32.
|
||||
ggml_tensor* ggml_ext_conv_1d(ggml_context* ctx,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* w,
|
||||
ggml_tensor* b,
|
||||
int s0 = 1,
|
||||
int p0 = 0,
|
||||
int d0 = 1,
|
||||
int64_t groups = 1,
|
||||
bool force_prec_f32 = false);
|
||||
|
||||
// w: [OC,IC, KH, KW]
|
||||
// x: [N, IC, IH, IW]
|
||||
// b: [OC,]
|
||||
@@ -219,7 +231,8 @@ ggml_tensor* ggml_ext_group_norm(ggml_context* ctx,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* w,
|
||||
ggml_tensor* b,
|
||||
int num_groups = 32);
|
||||
int num_groups = 32,
|
||||
float eps = 1e-6f);
|
||||
|
||||
ggml_tensor* ggml_ext_timestep_embedding(
|
||||
ggml_context* ctx,
|
||||
|
||||
@@ -87,6 +87,10 @@ static bool parse_backend_module(const std::string& raw_name, SDBackendModule* m
|
||||
*module = SDBackendModule::DETECTOR;
|
||||
return true;
|
||||
}
|
||||
if (name == "audioencoder" || name == "audio") {
|
||||
*module = SDBackendModule::AUDIO_ENCODER;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -968,6 +972,8 @@ const char* sd_backend_module_name(SDBackendModule module) {
|
||||
return "upscaler";
|
||||
case SDBackendModule::DETECTOR:
|
||||
return "detector";
|
||||
case SDBackendModule::AUDIO_ENCODER:
|
||||
return "audio_encoder";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ enum class SDBackendModule {
|
||||
PHOTOMAKER,
|
||||
UPSCALER,
|
||||
DETECTOR,
|
||||
AUDIO_ENCODER,
|
||||
};
|
||||
|
||||
struct SDBackendAssignment {
|
||||
|
||||
+2
-1
@@ -35,6 +35,7 @@ enum SDVersion {
|
||||
VERSION_WAN2,
|
||||
VERSION_WAN2_2_I2V,
|
||||
VERSION_WAN2_2_TI2V,
|
||||
VERSION_WAN2_2_S2V,
|
||||
VERSION_LINGBOT_VIDEO,
|
||||
VERSION_QWEN_IMAGE,
|
||||
VERSION_QWEN_IMAGE_LAYERED,
|
||||
@@ -130,7 +131,7 @@ static inline bool sd_version_is_minimax_h3(SDVersion version) {
|
||||
}
|
||||
|
||||
static inline bool sd_version_is_wan(SDVersion version) {
|
||||
if (version == VERSION_WAN2 || version == VERSION_WAN2_2_I2V || version == VERSION_WAN2_2_TI2V) {
|
||||
if (version == VERSION_WAN2 || version == VERSION_WAN2_2_I2V || version == VERSION_WAN2_2_TI2V || version == VERSION_WAN2_2_S2V) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
#ifndef __SD_MODEL_AUDIO_WAV2VEC2_HPP__
|
||||
#define __SD_MODEL_AUDIO_WAV2VEC2_HPP__
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/ggml_extend.h"
|
||||
#include "core/ggml_runner.h"
|
||||
#include "model.h"
|
||||
#include "model/common/ggml_block.hpp"
|
||||
|
||||
namespace Wav2Vec2 {
|
||||
|
||||
struct Wav2Vec2Config {
|
||||
int64_t embed_dim = 1024;
|
||||
int64_t conv_dim = 512;
|
||||
int num_heads = 16;
|
||||
int num_layers = 24;
|
||||
std::string feat_extract_norm = "layer";
|
||||
bool conv_bias = true;
|
||||
bool do_normalize = true;
|
||||
bool do_stable_layer_norm = true;
|
||||
|
||||
static Wav2Vec2Config detect_from_weights(const String2TensorStorage& tensor_storage_map, const std::string& prefix) {
|
||||
Wav2Vec2Config config;
|
||||
auto it = tensor_storage_map.find(prefix + "encoder.layer_norm.bias");
|
||||
if (it == tensor_storage_map.end()) {
|
||||
LOG_WARN("wav2vec2: %sencoder.layer_norm.bias not found, using large defaults", prefix.c_str());
|
||||
return config;
|
||||
}
|
||||
config.embed_dim = it->second.ne[0];
|
||||
if (config.embed_dim == 1024) {
|
||||
config.embed_dim = 1024;
|
||||
config.num_heads = 16;
|
||||
config.num_layers = 24;
|
||||
config.feat_extract_norm = "layer";
|
||||
config.conv_bias = true;
|
||||
config.do_normalize = true;
|
||||
config.do_stable_layer_norm = true;
|
||||
} else if (config.embed_dim == 768) {
|
||||
config.embed_dim = 768;
|
||||
config.num_heads = 12;
|
||||
config.num_layers = 12;
|
||||
config.feat_extract_norm = "group";
|
||||
config.conv_bias = false;
|
||||
config.do_normalize = false;
|
||||
config.do_stable_layer_norm = false;
|
||||
} else {
|
||||
LOG_WARN("wav2vec2: unsupported embed_dim %" PRId64 ", using large defaults", config.embed_dim);
|
||||
config.embed_dim = 1024;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
struct Wav2Vec2NoLayerNormConvLayer : public UnaryBlock {
|
||||
Wav2Vec2NoLayerNormConvLayer(int64_t in_channels, int64_t out_channels, int kernel_size, int stride, bool bias) {
|
||||
blocks["conv"] = std::make_shared<Conv1d>(in_channels, out_channels, kernel_size, stride, 0, 1, 1, bias, true);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
auto conv = std::dynamic_pointer_cast<Conv1d>(blocks["conv"]);
|
||||
x = conv->forward(ctx, x);
|
||||
return ggml_gelu_erf_inplace(ctx->ggml_ctx, ggml_ext_cont(ctx->ggml_ctx, x));
|
||||
}
|
||||
};
|
||||
|
||||
struct Wav2Vec2LayerNormConvLayer : public UnaryBlock {
|
||||
Wav2Vec2LayerNormConvLayer(int64_t in_channels, int64_t out_channels, int kernel_size, int stride, bool bias) {
|
||||
blocks["conv"] = std::make_shared<Conv1d>(in_channels, out_channels, kernel_size, stride, 0, 1, 1, bias, true);
|
||||
blocks["layer_norm"] = std::make_shared<LayerNorm>(out_channels);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
auto conv = std::dynamic_pointer_cast<Conv1d>(blocks["conv"]);
|
||||
auto layer_norm = std::dynamic_pointer_cast<LayerNorm>(blocks["layer_norm"]);
|
||||
x = conv->forward(ctx, x);
|
||||
// LayerNorm normalizes channels: [N, C, L] -> [N, L, C].
|
||||
x = ggml_permute(ctx->ggml_ctx, x, 1, 0, 2, 3);
|
||||
x = layer_norm->forward(ctx, x);
|
||||
x = ggml_permute(ctx->ggml_ctx, x, 1, 0, 2, 3);
|
||||
return ggml_gelu_erf_inplace(ctx->ggml_ctx, ggml_ext_cont(ctx->ggml_ctx, x));
|
||||
}
|
||||
};
|
||||
|
||||
struct Wav2Vec2GroupNormConvLayer : public UnaryBlock {
|
||||
Wav2Vec2GroupNormConvLayer(int64_t in_channels, int64_t out_channels, int kernel_size, int stride, bool bias) {
|
||||
blocks["conv"] = std::make_shared<Conv1d>(in_channels, out_channels, kernel_size, stride, 0, 1, 1, bias, true);
|
||||
blocks["layer_norm"] = std::make_shared<GroupNorm>((int)out_channels, out_channels, 1e-05f);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
auto conv = std::dynamic_pointer_cast<Conv1d>(blocks["conv"]);
|
||||
auto layer_norm = std::dynamic_pointer_cast<GroupNorm>(blocks["layer_norm"]);
|
||||
x = conv->forward(ctx, x);
|
||||
// ggml GroupNorm needs [N, C, H, W], with H=1 for audio.
|
||||
x = ggml_reshape_4d(ctx->ggml_ctx, x, x->ne[0], 1, x->ne[1], x->ne[2]);
|
||||
x = layer_norm->forward(ctx, x);
|
||||
x = ggml_reshape_3d(ctx->ggml_ctx, x, x->ne[0], x->ne[2], x->ne[3]);
|
||||
return ggml_gelu_erf_inplace(ctx->ggml_ctx, ggml_ext_cont(ctx->ggml_ctx, x));
|
||||
}
|
||||
};
|
||||
|
||||
struct Wav2Vec2FeatureEncoder : public UnaryBlock {
|
||||
Wav2Vec2FeatureEncoder(const Wav2Vec2Config& config) {
|
||||
GGML_ASSERT(config.feat_extract_norm == "layer" || config.feat_extract_norm == "group");
|
||||
const int kernels[7] = {10, 3, 3, 3, 3, 2, 2};
|
||||
const int strides[7] = {5, 2, 2, 2, 2, 2, 2};
|
||||
int64_t in_channels = 1;
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
const std::string name = "conv_layers." + std::to_string(i);
|
||||
if (config.feat_extract_norm == "layer") {
|
||||
blocks[name] = std::make_shared<Wav2Vec2LayerNormConvLayer>(in_channels, config.conv_dim, kernels[i], strides[i], config.conv_bias);
|
||||
} else if (i == 0) {
|
||||
blocks[name] = std::make_shared<Wav2Vec2GroupNormConvLayer>(in_channels, config.conv_dim, kernels[i], strides[i], config.conv_bias);
|
||||
} else {
|
||||
blocks[name] = std::make_shared<Wav2Vec2NoLayerNormConvLayer>(in_channels, config.conv_dim, kernels[i], strides[i], config.conv_bias);
|
||||
}
|
||||
in_channels = config.conv_dim;
|
||||
}
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
auto conv = std::dynamic_pointer_cast<UnaryBlock>(blocks["conv_layers." + std::to_string(i)]);
|
||||
x = conv->forward(ctx, x);
|
||||
}
|
||||
return ggml_permute(ctx->ggml_ctx, x, 1, 0, 2, 3);
|
||||
}
|
||||
};
|
||||
|
||||
struct Wav2Vec2FeatureProjection : public UnaryBlock {
|
||||
Wav2Vec2FeatureProjection(const Wav2Vec2Config& config) {
|
||||
blocks["layer_norm"] = std::make_shared<LayerNorm>(config.conv_dim);
|
||||
blocks["projection"] = std::make_shared<Linear>(config.conv_dim, config.embed_dim);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
auto ln = std::dynamic_pointer_cast<LayerNorm>(blocks["layer_norm"]);
|
||||
auto projection = std::dynamic_pointer_cast<Linear>(blocks["projection"]);
|
||||
x = ln->forward(ctx, x);
|
||||
x = projection->forward(ctx, x);
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
class Wav2Vec2PositionalConvEmbedding : public UnaryBlock {
|
||||
private:
|
||||
int64_t embed_dim_;
|
||||
static constexpr int groups_ = 16;
|
||||
static constexpr int kernel_size_ = 128;
|
||||
std::string weight_g_name_;
|
||||
std::string weight_v_name_;
|
||||
|
||||
ggml_tensor* weight(GGMLRunnerContext* ctx) {
|
||||
auto g = params[weight_g_name_];
|
||||
auto v = ggml_cast(ctx->ggml_ctx, params[weight_v_name_], GGML_TYPE_F32);
|
||||
auto squared = ggml_mul(ctx->ggml_ctx, v, v);
|
||||
// PyTorch weight_norm(dim=2) reduces both channel axes, retaining each kernel tap.
|
||||
squared = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, squared, 2, 0, 1, 3));
|
||||
squared = ggml_reshape_2d(ctx->ggml_ctx, squared, embed_dim_ / groups_ * embed_dim_, kernel_size_);
|
||||
auto norm = ggml_sqrt(ctx->ggml_ctx, ggml_sum_rows(ctx->ggml_ctx, squared));
|
||||
norm = ggml_reshape_3d(ctx->ggml_ctx, norm, kernel_size_, 1, 1);
|
||||
return ggml_mul(ctx->ggml_ctx, v, ggml_div(ctx->ggml_ctx, g, norm));
|
||||
}
|
||||
|
||||
public:
|
||||
Wav2Vec2PositionalConvEmbedding(const Wav2Vec2Config& config)
|
||||
: embed_dim_(config.embed_dim) {
|
||||
GGML_ASSERT(embed_dim_ > 0 && embed_dim_ % groups_ == 0);
|
||||
}
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map = {}, const std::string prefix = "") override {
|
||||
bool legacy = tensor_storage_map.count(prefix + "conv.weight_g") > 0;
|
||||
weight_g_name_ = legacy ? "conv.weight_g" : "conv.parametrizations.weight.original0";
|
||||
weight_v_name_ = legacy ? "conv.weight_v" : "conv.parametrizations.weight.original1";
|
||||
auto g = tensor_storage_map.find(prefix + weight_g_name_);
|
||||
auto v = tensor_storage_map.find(prefix + weight_v_name_);
|
||||
GGML_ASSERT(g != tensor_storage_map.end() && v != tensor_storage_map.end());
|
||||
GGML_ASSERT(g->second.ne[0] == kernel_size_ && g->second.ne[1] == 1 && g->second.ne[2] == 1 && g->second.ne[3] == 1);
|
||||
GGML_ASSERT(v->second.ne[0] == kernel_size_ && v->second.ne[1] == embed_dim_ / groups_ && v->second.ne[2] == embed_dim_ && v->second.ne[3] == 1);
|
||||
|
||||
params[weight_g_name_] = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, kernel_size_, 1, 1);
|
||||
params[weight_v_name_] = ggml_new_tensor_3d(ctx, get_type(prefix + weight_v_name_, tensor_storage_map, GGML_TYPE_F16),
|
||||
kernel_size_, embed_dim_ / groups_, embed_dim_);
|
||||
if (tensor_storage_map.count(prefix + "conv.bias") > 0) {
|
||||
params["conv.bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, embed_dim_);
|
||||
}
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
auto w = weight(ctx);
|
||||
auto b = params.count("conv.bias") > 0 ? params["conv.bias"] : nullptr;
|
||||
x = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, x, 1, 0, 2, 3));
|
||||
x = ggml_ext_conv_1d(ctx->ggml_ctx, x, w, b, 1, kernel_size_ / 2, 1, groups_, true);
|
||||
// Apply GELU out of place before cropping to keep graph buffer reuse safe.
|
||||
x = ggml_gelu_erf(ctx->ggml_ctx, x);
|
||||
x = ggml_view_3d(ctx->ggml_ctx, x, x->ne[0] - 1, x->ne[1], x->ne[2], x->nb[1], x->nb[2], 0);
|
||||
return ggml_permute(ctx->ggml_ctx, x, 1, 0, 2, 3);
|
||||
}
|
||||
};
|
||||
|
||||
struct Wav2Vec2FeedForward : public UnaryBlock {
|
||||
Wav2Vec2FeedForward(const Wav2Vec2Config& config) {
|
||||
blocks["intermediate_dense"] = std::make_shared<Linear>(config.embed_dim, config.embed_dim * 4);
|
||||
blocks["output_dense"] = std::make_shared<Linear>(config.embed_dim * 4, config.embed_dim);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
auto intermediate_dense = std::dynamic_pointer_cast<Linear>(blocks["intermediate_dense"]);
|
||||
auto output_dense = std::dynamic_pointer_cast<Linear>(blocks["output_dense"]);
|
||||
x = intermediate_dense->forward(ctx, x);
|
||||
x = ggml_ext_gelu(ctx->ggml_ctx, x, true);
|
||||
x = output_dense->forward(ctx, x);
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
struct Wav2Vec2EncoderLayer : public UnaryBlock {
|
||||
bool do_stable_layer_norm;
|
||||
|
||||
Wav2Vec2EncoderLayer(const Wav2Vec2Config& config)
|
||||
: do_stable_layer_norm(config.do_stable_layer_norm) {
|
||||
blocks["attention"] = std::make_shared<MultiheadAttention>(config.embed_dim, config.num_heads, true, true);
|
||||
blocks["layer_norm"] = std::make_shared<LayerNorm>(config.embed_dim);
|
||||
blocks["feed_forward"] = std::make_shared<Wav2Vec2FeedForward>(config);
|
||||
blocks["final_layer_norm"] = std::make_shared<LayerNorm>(config.embed_dim);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
auto attention = std::dynamic_pointer_cast<MultiheadAttention>(blocks["attention"]);
|
||||
auto layer_norm = std::dynamic_pointer_cast<LayerNorm>(blocks["layer_norm"]);
|
||||
auto feed_forward = std::dynamic_pointer_cast<Wav2Vec2FeedForward>(blocks["feed_forward"]);
|
||||
auto final_layer_norm = std::dynamic_pointer_cast<LayerNorm>(blocks["final_layer_norm"]);
|
||||
|
||||
ggml_tensor* residual = x;
|
||||
if (do_stable_layer_norm) {
|
||||
x = layer_norm->forward(ctx, x);
|
||||
x = attention->forward(ctx, x);
|
||||
x = ggml_add(ctx->ggml_ctx, residual, x);
|
||||
x = ggml_add(ctx->ggml_ctx, x, feed_forward->forward(ctx, final_layer_norm->forward(ctx, x)));
|
||||
} else {
|
||||
x = attention->forward(ctx, x);
|
||||
x = ggml_add(ctx->ggml_ctx, residual, x);
|
||||
x = layer_norm->forward(ctx, x);
|
||||
x = final_layer_norm->forward(ctx, ggml_add(ctx->ggml_ctx, x, feed_forward->forward(ctx, x)));
|
||||
}
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
struct Wav2Vec2Encoder : public GGMLBlock {
|
||||
int num_layers;
|
||||
bool do_stable_layer_norm;
|
||||
|
||||
Wav2Vec2Encoder(const Wav2Vec2Config& config)
|
||||
: num_layers(config.num_layers), do_stable_layer_norm(config.do_stable_layer_norm) {
|
||||
blocks["pos_conv_embed"] = std::make_shared<Wav2Vec2PositionalConvEmbedding>(config);
|
||||
for (int i = 0; i < config.num_layers; ++i) {
|
||||
blocks["layers." + std::to_string(i)] = std::make_shared<Wav2Vec2EncoderLayer>(config);
|
||||
}
|
||||
blocks["layer_norm"] = std::make_shared<LayerNorm>(config.embed_dim);
|
||||
}
|
||||
|
||||
// For N == 1, all_layers stacks pre-layer states and the final state as [embed_dim, L, num_layers + 1].
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor** all_layers = nullptr) {
|
||||
auto pos_conv_embed = std::dynamic_pointer_cast<Wav2Vec2PositionalConvEmbedding>(blocks["pos_conv_embed"]);
|
||||
auto layer_norm = std::dynamic_pointer_cast<LayerNorm>(blocks["layer_norm"]);
|
||||
|
||||
std::vector<ggml_tensor*> collected;
|
||||
if (all_layers != nullptr) {
|
||||
collected.reserve(num_layers + 1);
|
||||
}
|
||||
|
||||
x = ggml_add(ctx->ggml_ctx, x, pos_conv_embed->forward(ctx, x));
|
||||
if (!do_stable_layer_norm) {
|
||||
x = layer_norm->forward(ctx, x);
|
||||
}
|
||||
for (int i = 0; i < num_layers; ++i) {
|
||||
if (all_layers != nullptr) {
|
||||
collected.push_back(x);
|
||||
}
|
||||
auto layer = std::dynamic_pointer_cast<Wav2Vec2EncoderLayer>(blocks["layers." + std::to_string(i)]);
|
||||
x = layer->forward(ctx, x);
|
||||
}
|
||||
if (do_stable_layer_norm) {
|
||||
x = layer_norm->forward(ctx, x);
|
||||
}
|
||||
if (all_layers != nullptr) {
|
||||
collected.push_back(x);
|
||||
ggml_tensor* stack = collected[0];
|
||||
for (size_t i = 1; i < collected.size(); ++i) {
|
||||
stack = ggml_concat(ctx->ggml_ctx, stack, collected[i], 2);
|
||||
}
|
||||
*all_layers = stack;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
struct Wav2Vec2Model : public GGMLBlock {
|
||||
Wav2Vec2Config config;
|
||||
|
||||
Wav2Vec2Model() = default;
|
||||
Wav2Vec2Model(const Wav2Vec2Config& config_)
|
||||
: config(config_) {
|
||||
blocks["feature_extractor"] = std::make_shared<Wav2Vec2FeatureEncoder>(config);
|
||||
blocks["feature_projection"] = std::make_shared<Wav2Vec2FeatureProjection>(config);
|
||||
blocks["encoder"] = std::make_shared<Wav2Vec2Encoder>(config);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor** all_layers = nullptr) {
|
||||
auto feature_extractor = std::dynamic_pointer_cast<Wav2Vec2FeatureEncoder>(blocks["feature_extractor"]);
|
||||
auto feature_projection = std::dynamic_pointer_cast<Wav2Vec2FeatureProjection>(blocks["feature_projection"]);
|
||||
auto encoder = std::dynamic_pointer_cast<Wav2Vec2Encoder>(blocks["encoder"]);
|
||||
|
||||
x = feature_extractor->forward(ctx, x);
|
||||
x = feature_projection->forward(ctx, x);
|
||||
x = encoder->forward(ctx, x, all_layers);
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
class Wav2Vec2ModelRunner : public GGMLRunner {
|
||||
private:
|
||||
Wav2Vec2Config config;
|
||||
|
||||
public:
|
||||
Wav2Vec2Model model;
|
||||
std::string weight_prefix;
|
||||
|
||||
Wav2Vec2ModelRunner(ggml_backend_t backend,
|
||||
const String2TensorStorage& tensor_storage_map = {},
|
||||
const std::string prefix = "wav2vec2.",
|
||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
: GGMLRunner(backend, weight_manager),
|
||||
config(Wav2Vec2Config::detect_from_weights(tensor_storage_map, prefix)),
|
||||
model(config),
|
||||
weight_prefix(prefix) {
|
||||
// GGMLBlock appends its own separator; loader prefixes already include one.
|
||||
std::string block_prefix = weight_prefix;
|
||||
if (!block_prefix.empty() && block_prefix.back() == '.') {
|
||||
block_prefix.pop_back();
|
||||
}
|
||||
model.init(params_ctx, tensor_storage_map, block_prefix);
|
||||
LOG_INFO("%s", get_desc().c_str());
|
||||
}
|
||||
|
||||
std::string get_desc() override {
|
||||
return "wav2vec2";
|
||||
}
|
||||
|
||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) {
|
||||
std::string block_prefix = weight_prefix;
|
||||
if (!block_prefix.empty() && block_prefix.back() == '.') {
|
||||
block_prefix.pop_back();
|
||||
}
|
||||
model.get_param_tensors(tensors, block_prefix);
|
||||
}
|
||||
|
||||
ggml_cgraph* build_graph(const sd::Tensor<float>& waveform_tensor) {
|
||||
ggml_cgraph* gf = ggml_new_graph(compute_ctx);
|
||||
ggml_tensor* waveform = make_input(waveform_tensor);
|
||||
auto runner_ctx = get_context();
|
||||
ggml_tensor* all_layers = nullptr;
|
||||
model.forward(&runner_ctx, waveform, &all_layers);
|
||||
GGML_ASSERT(all_layers != nullptr);
|
||||
ggml_build_forward_expand(gf, all_layers);
|
||||
return gf;
|
||||
}
|
||||
|
||||
sd::Tensor<float> compute(const int n_threads, const std::vector<float>& mono_waveform) {
|
||||
GGML_ASSERT(!mono_waveform.empty());
|
||||
const int64_t num_samples = (int64_t)mono_waveform.size();
|
||||
sd::Tensor<float> waveform({num_samples, 1, 1});
|
||||
std::copy(mono_waveform.begin(), mono_waveform.end(), waveform.data());
|
||||
normalize(waveform.data(), num_samples);
|
||||
|
||||
auto get_graph = [&]() -> ggml_cgraph* {
|
||||
return build_graph(waveform);
|
||||
};
|
||||
return take_or_empty(GGMLRunner::compute(get_graph, n_threads, true));
|
||||
}
|
||||
|
||||
private:
|
||||
static void normalize(float* x, int64_t n) {
|
||||
double mean = 0.0;
|
||||
for (int64_t i = 0; i < n; ++i) {
|
||||
mean += x[i];
|
||||
}
|
||||
mean /= n;
|
||||
double var = 0.0;
|
||||
for (int64_t i = 0; i < n; ++i) {
|
||||
const double d = x[i] - mean;
|
||||
var += d * d;
|
||||
}
|
||||
var /= n;
|
||||
const float scale = (float)(1.0 / std::sqrt(var + 1e-7));
|
||||
for (int64_t i = 0; i < n; ++i) {
|
||||
x[i] = (float)((x[i] - mean) * scale);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Wav2Vec2
|
||||
|
||||
#endif // __SD_MODEL_AUDIO_WAV2VEC2_HPP__
|
||||
@@ -368,6 +368,61 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class Conv1d : public UnaryBlock {
|
||||
protected:
|
||||
int64_t in_channels;
|
||||
int64_t out_channels;
|
||||
int64_t groups;
|
||||
int kernel_size;
|
||||
int stride;
|
||||
int padding;
|
||||
int dilation;
|
||||
bool bias;
|
||||
bool force_prec_f32;
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map = {}, const std::string prefix = "") override {
|
||||
ggml_type wtype = get_type(prefix + "weight", tensor_storage_map, GGML_TYPE_F16);
|
||||
params["weight"] = ggml_new_tensor_3d(ctx, wtype, kernel_size, in_channels / groups, out_channels);
|
||||
if (bias) {
|
||||
params["bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, out_channels);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
Conv1d(int64_t in_channels,
|
||||
int64_t out_channels,
|
||||
int kernel_size,
|
||||
int stride = 1,
|
||||
int padding = 0,
|
||||
int dilation = 1,
|
||||
int64_t groups = 1,
|
||||
bool bias = true,
|
||||
bool force_prec_f32 = false)
|
||||
: in_channels(in_channels),
|
||||
out_channels(out_channels),
|
||||
groups(groups),
|
||||
kernel_size(kernel_size),
|
||||
stride(stride),
|
||||
padding(padding),
|
||||
dilation(dilation),
|
||||
bias(bias),
|
||||
force_prec_f32(force_prec_f32) {
|
||||
GGML_ASSERT(in_channels > 0 && out_channels > 0 && groups > 0);
|
||||
GGML_ASSERT(in_channels % groups == 0 && out_channels % groups == 0);
|
||||
GGML_ASSERT(kernel_size > 0 && stride > 0 && padding >= 0 && dilation > 0);
|
||||
}
|
||||
|
||||
std::string get_desc() override {
|
||||
return "Conv1d";
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
GGML_ASSERT(x->ne[1] == in_channels);
|
||||
return ggml_ext_conv_1d(ctx->ggml_ctx, x, params["weight"], bias ? params["bias"] : nullptr,
|
||||
stride, padding, dilation, groups, force_prec_f32);
|
||||
}
|
||||
};
|
||||
|
||||
class Conv2d : public UnaryBlock {
|
||||
protected:
|
||||
int64_t in_channels;
|
||||
@@ -766,7 +821,7 @@ public:
|
||||
b = ctx->weight_adapter->patch_weight(ctx->ggml_ctx, ctx->backend, b, prefix + "bias");
|
||||
}
|
||||
}
|
||||
return ggml_ext_group_norm(ctx->ggml_ctx, x, w, b, num_groups);
|
||||
return ggml_ext_group_norm(ctx->ggml_ctx, x, w, b, num_groups, eps);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -818,8 +818,9 @@ namespace Rope {
|
||||
int pw,
|
||||
int bs,
|
||||
int theta,
|
||||
const std::vector<int>& axes_dim) {
|
||||
std::vector<std::vector<float>> ids = gen_vid_ids(t, h, w, pt, ph, pw, bs);
|
||||
const std::vector<int>& axes_dim,
|
||||
int t_offset = 0) {
|
||||
std::vector<std::vector<float>> ids = gen_vid_ids(t, h, w, pt, ph, pw, bs, t_offset);
|
||||
return embed_nd(ids, bs, static_cast<float>(theta), axes_dim);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,8 @@ struct AnimaDiffusionExtra {
|
||||
struct WanDiffusionExtra {
|
||||
const sd::Tensor<float>* vace_context = nullptr;
|
||||
float vace_strength = 1.f;
|
||||
// S2V audio, sd::Tensor layout: [dim, T_latent*4, layers].
|
||||
const sd::Tensor<float>* audio_embed = nullptr;
|
||||
};
|
||||
|
||||
struct HiDreamO1DiffusionExtra {
|
||||
|
||||
+158
-32
@@ -1,6 +1,7 @@
|
||||
#ifndef __SD_MODEL_DIFFUSION_WAN_HPP__
|
||||
#define __SD_MODEL_DIFFUSION_WAN_HPP__
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -33,11 +34,16 @@ namespace WAN {
|
||||
int vace_layers = 0;
|
||||
int64_t vace_in_dim = 96;
|
||||
std::map<int, int> vace_layers_mapping = {};
|
||||
bool qk_norm = true;
|
||||
bool cross_attn_norm = true;
|
||||
float eps = 1e-6f;
|
||||
int64_t flf_pos_embed_token_number = 0;
|
||||
int theta = 10000;
|
||||
int64_t audio_dim = 1024;
|
||||
int num_audio_token = 4; // excludes the learned padding token
|
||||
std::vector<int> audio_inject_layers = {};
|
||||
std::map<int, int> audio_inject_mapping = {}; // block index -> injector index
|
||||
std::string adain_mode = "attn_norm";
|
||||
bool qk_norm = true;
|
||||
bool cross_attn_norm = true;
|
||||
float eps = 1e-6f;
|
||||
int64_t flf_pos_embed_token_number = 0;
|
||||
int theta = 10000;
|
||||
// wan2.1 1.3B: 1536/12, wan2.1/2.2 14B: 5120/40, wan2.2 5B: 3074/24
|
||||
std::vector<int> axes_dim = {44, 42, 42};
|
||||
int64_t axes_dim_sum = 128;
|
||||
@@ -74,6 +80,10 @@ namespace WAN {
|
||||
if (name.find("img_emb") != std::string::npos) {
|
||||
config.model_type = "i2v";
|
||||
}
|
||||
if (name.find("audio_injector") != std::string::npos || name.find("casual_audio_encoder") != std::string::npos) {
|
||||
config.model_type = "s2v";
|
||||
config.audio_inject_layers = {0, 4, 8, 12, 16, 20, 24, 27, 30, 33, 36, 39};
|
||||
}
|
||||
if (name.find("img_emb.emb_pos") != std::string::npos) {
|
||||
config.flf_pos_embed_token_number = 514;
|
||||
}
|
||||
@@ -265,6 +275,13 @@ namespace WAN {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace WAN
|
||||
|
||||
// Audio injection reuses WanT2VCrossAttention defined above.
|
||||
#include "model/diffusion/wan_audio.hpp"
|
||||
|
||||
namespace WAN {
|
||||
|
||||
static ggml_tensor* modulate_add(ggml_context* ctx, ggml_tensor* x, ggml_tensor* e) {
|
||||
// x: [N, n_token, dim]
|
||||
// e: [N, 1, dim] or [N, T, 1, dim]
|
||||
@@ -532,6 +549,13 @@ namespace WAN {
|
||||
protected:
|
||||
WanConfig config;
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map = {}, const std::string prefix = "") override {
|
||||
if (config.model_type == "s2v") {
|
||||
enum ggml_type wtype = GGML_TYPE_F32; // elementwise add vs F32 activations
|
||||
params["trainable_cond_mask.weight"] = ggml_new_tensor_2d(ctx, wtype, config.dim, 3);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
Wan() {}
|
||||
Wan(WanConfig config)
|
||||
@@ -554,7 +578,7 @@ namespace WAN {
|
||||
|
||||
// blocks
|
||||
for (int i = 0; i < config.num_layers; i++) {
|
||||
auto block = std::shared_ptr<GGMLBlock>(new WanAttentionBlock(config.model_type == "t2v",
|
||||
auto block = std::shared_ptr<GGMLBlock>(new WanAttentionBlock(config.model_type != "i2v",
|
||||
config.dim,
|
||||
config.ffn_dim,
|
||||
config.num_heads,
|
||||
@@ -595,6 +619,14 @@ namespace WAN {
|
||||
|
||||
blocks["vace_patch_embedding"] = std::shared_ptr<GGMLBlock>(new Conv3d(config.vace_in_dim, config.dim, config.patch_size, config.patch_size));
|
||||
}
|
||||
|
||||
if (config.model_type == "s2v") {
|
||||
blocks["casual_audio_encoder"] = std::make_shared<WanCausalAudioEncoder>(config.audio_dim, config.dim, config.num_audio_token);
|
||||
blocks["audio_injector"] = std::make_shared<WanAudioInjector>(config.dim, config.num_heads, (int)config.audio_inject_layers.size(), config.qk_norm, config.eps);
|
||||
for (size_t i = 0; i < config.audio_inject_layers.size(); i++) {
|
||||
config.audio_inject_mapping[config.audio_inject_layers[i]] = (int)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ggml_tensor* pad_to_patch_size(GGMLRunnerContext* ctx,
|
||||
@@ -642,18 +674,24 @@ namespace WAN {
|
||||
ggml_tensor* timestep,
|
||||
ggml_tensor* context,
|
||||
ggml_tensor* pe,
|
||||
ggml_tensor* clip_fea = nullptr,
|
||||
ggml_tensor* vace_context = nullptr,
|
||||
float vace_strength = 1.f,
|
||||
int64_t N = 1) {
|
||||
ggml_tensor* clip_fea = nullptr,
|
||||
ggml_tensor* vace_context = nullptr,
|
||||
float vace_strength = 1.f,
|
||||
int64_t N = 1,
|
||||
ggml_tensor* audio_embed = nullptr,
|
||||
ggml_tensor* reference_latent = nullptr) {
|
||||
// x: [N*C, T, H, W], C => in_dim
|
||||
// vace_context: [N*vace_in_dim, T, H, W]
|
||||
// timestep: [N,] or [T]
|
||||
// context: [N, L, text_dim]
|
||||
// return: [N, t_len*h_len*w_len, out_dim*pt*ph*pw]
|
||||
// audio_embed: [layers, T*4, audio_dim]
|
||||
// reference_latent: [N*C, T_ref, H, W]
|
||||
// return: [N, (t_len [+ t_ref_len]) * h_len*w_len, out_dim*pt*ph*pw]
|
||||
|
||||
GGML_ASSERT(N == 1);
|
||||
|
||||
int64_t T = x->ne[2];
|
||||
|
||||
auto patch_embedding = std::dynamic_pointer_cast<Conv3d>(blocks["patch_embedding"]);
|
||||
|
||||
auto text_embedding_0 = std::dynamic_pointer_cast<Linear>(blocks["text_embedding.0"]);
|
||||
@@ -670,6 +708,40 @@ namespace WAN {
|
||||
x = ggml_reshape_3d(ctx->ggml_ctx, x, x->ne[0] * x->ne[1] * x->ne[2], x->ne[3] / N, N); // [N, dim, t_len*h_len*w_len]
|
||||
x = ggml_ext_cont(ctx->ggml_ctx, ggml_ext_torch_permute(ctx->ggml_ctx, x, 1, 0, 2, 3)); // [N, t_len*h_len*w_len, dim]
|
||||
|
||||
ggml_tensor* audio_local = nullptr;
|
||||
ggml_tensor* audio_global = nullptr;
|
||||
int64_t seq_len = x->ne[1];
|
||||
int64_t t_ref_len = 0;
|
||||
if (config.model_type == "s2v") {
|
||||
if (audio_embed != nullptr) {
|
||||
GGML_ASSERT(audio_embed->ne[1] == T * 4);
|
||||
auto audio_encoder = std::dynamic_pointer_cast<WanCausalAudioEncoder>(blocks["casual_audio_encoder"]);
|
||||
auto audio_emb = audio_encoder->forward(ctx, audio_embed);
|
||||
audio_local = audio_emb.first;
|
||||
audio_global = audio_emb.second;
|
||||
GGML_ASSERT(audio_local->ne[2] == T);
|
||||
}
|
||||
|
||||
// video tokens get cond_mask[0], reference tokens cond_mask[1]
|
||||
auto cond_mask = params["trainable_cond_mask.weight"];
|
||||
auto cm0 = ggml_reshape_3d(ctx->ggml_ctx, ggml_ext_slice(ctx->ggml_ctx, cond_mask, 1, 0, 1), config.dim, 1, 1);
|
||||
x = ggml_add(ctx->ggml_ctx, x, cm0);
|
||||
|
||||
if (reference_latent != nullptr) {
|
||||
t_ref_len = reference_latent->ne[2];
|
||||
auto ref = patch_embedding->forward(ctx, reference_latent);
|
||||
ref = ggml_reshape_3d(ctx->ggml_ctx, ref, ref->ne[0] * ref->ne[1] * ref->ne[2], ref->ne[3] / N, N);
|
||||
ref = ggml_ext_cont(ctx->ggml_ctx, ggml_ext_torch_permute(ctx->ggml_ctx, ref, 1, 0, 2, 3)); // [N, t_ref*h_len*w_len, dim]
|
||||
auto cm1 = ggml_reshape_3d(ctx->ggml_ctx, ggml_ext_slice(ctx->ggml_ctx, cond_mask, 1, 1, 2), config.dim, 1, 1);
|
||||
ref = ggml_add(ctx->ggml_ctx, ref, cm1);
|
||||
x = ggml_concat(ctx->ggml_ctx, x, ref, 1);
|
||||
|
||||
// Reference tokens use timestep 0.
|
||||
GGML_ASSERT(timestep->ne[0] == T);
|
||||
timestep = ggml_ext_pad(ctx->ggml_ctx, timestep, (int)t_ref_len, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// time_embedding
|
||||
auto e = ggml_ext_timestep_embedding(ctx->ggml_ctx, timestep, config.freq_dim);
|
||||
e = time_embedding_0->forward(ctx, e);
|
||||
@@ -714,6 +786,11 @@ namespace WAN {
|
||||
|
||||
auto x_orig = x;
|
||||
|
||||
std::shared_ptr<WanAudioInjector> audio_injector;
|
||||
if (audio_local != nullptr) {
|
||||
audio_injector = std::dynamic_pointer_cast<WanAudioInjector>(blocks["audio_injector"]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < config.num_layers; i++) {
|
||||
auto block = std::dynamic_pointer_cast<WanAttentionBlock>(blocks["blocks." + std::to_string(i)]);
|
||||
|
||||
@@ -731,6 +808,13 @@ namespace WAN {
|
||||
c_skip = ggml_ext_scale(ctx->ggml_ctx, c_skip, vace_strength);
|
||||
x = ggml_add(ctx->ggml_ctx, x, c_skip);
|
||||
}
|
||||
|
||||
if (audio_injector != nullptr) {
|
||||
auto inject_iter = config.audio_inject_mapping.find(i);
|
||||
if (inject_iter != config.audio_inject_mapping.end()) {
|
||||
x = audio_injector->forward(ctx, x, seq_len, T, inject_iter->second, audio_local, audio_global);
|
||||
}
|
||||
}
|
||||
sd::ggml_graph_cut::mark_graph_cut(x, "wan.blocks." + std::to_string(i), "x");
|
||||
if (c != nullptr) {
|
||||
sd::ggml_graph_cut::mark_graph_cut(c, "wan.blocks." + std::to_string(i), "c");
|
||||
@@ -747,11 +831,13 @@ namespace WAN {
|
||||
ggml_tensor* timestep,
|
||||
ggml_tensor* context,
|
||||
ggml_tensor* pe,
|
||||
ggml_tensor* clip_fea = nullptr,
|
||||
ggml_tensor* time_dim_concat = nullptr,
|
||||
ggml_tensor* vace_context = nullptr,
|
||||
float vace_strength = 1.f,
|
||||
int64_t N = 1) {
|
||||
ggml_tensor* clip_fea = nullptr,
|
||||
ggml_tensor* time_dim_concat = nullptr,
|
||||
ggml_tensor* vace_context = nullptr,
|
||||
float vace_strength = 1.f,
|
||||
int64_t N = 1,
|
||||
ggml_tensor* audio_embed = nullptr,
|
||||
ggml_tensor* reference_latent = nullptr) {
|
||||
// Forward pass of DiT.
|
||||
// x: [N*C, T, H, W]
|
||||
// timestep: [N,]
|
||||
@@ -779,7 +865,12 @@ namespace WAN {
|
||||
t_len = ((x->ne[2] + (std::get<0>(config.patch_size) / 2)) / std::get<0>(config.patch_size));
|
||||
}
|
||||
|
||||
auto out = forward_orig(ctx, x, timestep, context, pe, clip_fea, vace_context, vace_strength, N); // [N, t_len*h_len*w_len, pt*ph*pw*C]
|
||||
auto out = forward_orig(ctx, x, timestep, context, pe, clip_fea, vace_context, vace_strength, N, audio_embed, reference_latent); // [N, (t_len [+t_ref]) *h_len*w_len, pt*ph*pw*C]
|
||||
|
||||
if (reference_latent != nullptr) {
|
||||
// Exclude reference tokens from the generated video.
|
||||
out = ggml_ext_slice(ctx->ggml_ctx, out, 1, 0, t_len * h_len * w_len);
|
||||
}
|
||||
|
||||
out = unpatchify(ctx->ggml_ctx, out, t_len, h_len, w_len); // [N*C, (T+pad_t) + (T2+pad_t2), H + pad_h, W + pad_w]
|
||||
|
||||
@@ -839,7 +930,10 @@ namespace WAN {
|
||||
config.text_len = 512;
|
||||
}
|
||||
} else if (config.num_layers == 40) {
|
||||
if (config.model_type == "t2v") {
|
||||
if (version == VERSION_WAN2_2_S2V) {
|
||||
desc = "Wan2.2-S2V-14B";
|
||||
config.in_dim = 16;
|
||||
} else if (config.model_type == "t2v") {
|
||||
if (version == VERSION_WAN2_2_I2V) {
|
||||
desc = "Wan2.2-I2V-14B";
|
||||
config.in_dim = 36;
|
||||
@@ -891,7 +985,9 @@ namespace WAN {
|
||||
const sd::Tensor<float>& c_concat_tensor = {},
|
||||
const sd::Tensor<float>& time_dim_concat_tensor = {},
|
||||
const sd::Tensor<float>& vace_context_tensor = {},
|
||||
float vace_strength = 1.f) {
|
||||
float vace_strength = 1.f,
|
||||
const sd::Tensor<float>& audio_embed_tensor = {},
|
||||
const sd::Tensor<float>& ref_latent_tensor = {}) {
|
||||
ggml_cgraph* gf = new_graph_custom(WAN_GRAPH_SIZE);
|
||||
|
||||
ggml_tensor* x = make_input(x_tensor);
|
||||
@@ -901,16 +997,33 @@ namespace WAN {
|
||||
ggml_tensor* c_concat = make_optional_input(c_concat_tensor);
|
||||
ggml_tensor* time_dim_concat = make_optional_input(time_dim_concat_tensor);
|
||||
ggml_tensor* vace_context = make_optional_input(vace_context_tensor);
|
||||
ggml_tensor* audio_embed = make_optional_input(audio_embed_tensor);
|
||||
ggml_tensor* ref_latent = make_optional_input(ref_latent_tensor);
|
||||
|
||||
pe_vec = Rope::gen_wan_pe(static_cast<int>(x->ne[2]),
|
||||
static_cast<int>(x->ne[1]),
|
||||
static_cast<int>(x->ne[0]),
|
||||
std::get<0>(config.patch_size),
|
||||
std::get<1>(config.patch_size),
|
||||
std::get<2>(config.patch_size),
|
||||
1,
|
||||
config.theta,
|
||||
config.axes_dim);
|
||||
pe_vec = Rope::gen_wan_pe(static_cast<int>(x->ne[2]),
|
||||
static_cast<int>(x->ne[1]),
|
||||
static_cast<int>(x->ne[0]),
|
||||
std::get<0>(config.patch_size),
|
||||
std::get<1>(config.patch_size),
|
||||
std::get<2>(config.patch_size),
|
||||
1,
|
||||
config.theta,
|
||||
config.axes_dim);
|
||||
if (ref_latent != nullptr) {
|
||||
// Match S2V's reference-frame temporal offset.
|
||||
int t_start = std::max(30, static_cast<int>(x->ne[2]) + 9);
|
||||
auto ref_pe = Rope::gen_wan_pe(static_cast<int>(ref_latent->ne[2]),
|
||||
static_cast<int>(ref_latent->ne[1]),
|
||||
static_cast<int>(ref_latent->ne[0]),
|
||||
std::get<0>(config.patch_size),
|
||||
std::get<1>(config.patch_size),
|
||||
std::get<2>(config.patch_size),
|
||||
1,
|
||||
config.theta,
|
||||
config.axes_dim,
|
||||
t_start);
|
||||
pe_vec.insert(pe_vec.end(), ref_pe.begin(), ref_pe.end());
|
||||
}
|
||||
int pos_len = static_cast<int>(pe_vec.size() / config.axes_dim_sum / 2);
|
||||
// LOG_VERBOSE("pos_len %d", pos_len);
|
||||
auto pe = ggml_new_tensor_4d(compute_ctx, GGML_TYPE_F32, 2, 2, config.axes_dim_sum / 2, pos_len);
|
||||
@@ -933,7 +1046,10 @@ namespace WAN {
|
||||
clip_fea,
|
||||
time_dim_concat,
|
||||
vace_context,
|
||||
vace_strength);
|
||||
vace_strength,
|
||||
1,
|
||||
audio_embed,
|
||||
ref_latent);
|
||||
|
||||
ggml_build_forward_expand(gf, out);
|
||||
|
||||
@@ -948,9 +1064,11 @@ namespace WAN {
|
||||
const sd::Tensor<float>& c_concat = {},
|
||||
const sd::Tensor<float>& time_dim_concat = {},
|
||||
const sd::Tensor<float>& vace_context = {},
|
||||
float vace_strength = 1.f) {
|
||||
float vace_strength = 1.f,
|
||||
const sd::Tensor<float>& audio_embed = {},
|
||||
const sd::Tensor<float>& ref_latent = {}) {
|
||||
auto get_graph = [&]() -> ggml_cgraph* {
|
||||
return build_graph(x, timesteps, context, clip_fea, c_concat, time_dim_concat, vace_context, vace_strength);
|
||||
return build_graph(x, timesteps, context, clip_fea, c_concat, time_dim_concat, vace_context, vace_strength, audio_embed, ref_latent);
|
||||
};
|
||||
|
||||
return restore_trailing_singleton_dims(GGMLRunner::compute(get_graph, n_threads, false), x.dim());
|
||||
@@ -961,6 +1079,12 @@ namespace WAN {
|
||||
GGML_ASSERT(diffusion_params.x != nullptr);
|
||||
GGML_ASSERT(diffusion_params.timesteps != nullptr);
|
||||
const auto* extra = diffusion_extra_as<WanDiffusionExtra>(diffusion_params);
|
||||
static const std::vector<sd::Tensor<float>> no_ref_latents;
|
||||
const auto& ref_latents = config.model_type == "s2v" && diffusion_params.ref_latents != nullptr
|
||||
? *diffusion_params.ref_latents
|
||||
: no_ref_latents;
|
||||
const sd::Tensor<float> empty_tensor;
|
||||
const sd::Tensor<float>& ref_latent = ref_latents.empty() ? empty_tensor : ref_latents[0];
|
||||
return compute(n_threads,
|
||||
*diffusion_params.x,
|
||||
*diffusion_params.timesteps,
|
||||
@@ -969,7 +1093,9 @@ namespace WAN {
|
||||
tensor_or_empty(diffusion_params.c_concat),
|
||||
sd::Tensor<float>(),
|
||||
tensor_or_empty(extra->vace_context),
|
||||
extra->vace_strength);
|
||||
extra->vace_strength,
|
||||
tensor_or_empty(extra->audio_embed),
|
||||
ref_latent);
|
||||
}
|
||||
|
||||
void test() {
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
#ifndef __SD_MODEL_DIFFUSION_WAN_AUDIO_HPP__
|
||||
#define __SD_MODEL_DIFFUSION_WAN_AUDIO_HPP__
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "model/common/ggml_block.hpp"
|
||||
|
||||
namespace WAN {
|
||||
|
||||
class WanCausalConv1d : public UnaryBlock {
|
||||
private:
|
||||
int kernel_size_;
|
||||
|
||||
public:
|
||||
WanCausalConv1d(int64_t in_dim,
|
||||
int64_t out_dim,
|
||||
int kernel_size = 3,
|
||||
int stride = 1)
|
||||
: kernel_size_(kernel_size) {
|
||||
blocks["conv"] = std::make_shared<Conv1d>(in_dim, out_dim, kernel_size, stride, 0, 1, 1, true, true);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
// Replicate the first sample for causal left padding.
|
||||
if (kernel_size_ > 1) {
|
||||
auto first = ggml_ext_slice(ctx->ggml_ctx, x, 0, 0, 1);
|
||||
for (int i = 0; i < kernel_size_ - 1; i++) {
|
||||
x = ggml_concat(ctx->ggml_ctx, first, x, 0);
|
||||
}
|
||||
}
|
||||
return std::dynamic_pointer_cast<Conv1d>(blocks["conv"])->forward(ctx, x);
|
||||
}
|
||||
};
|
||||
|
||||
class WanMotionEncoder : public GGMLBlock {
|
||||
private:
|
||||
int64_t hidden_dim_;
|
||||
int num_token_;
|
||||
bool need_global_;
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map = {}, const std::string prefix = "") override {
|
||||
// The padding token is combined with F32 activations.
|
||||
params["padding_tokens"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, hidden_dim_);
|
||||
}
|
||||
|
||||
ggml_tensor* conv_norm_silu(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* x,
|
||||
const std::string& conv_key,
|
||||
const std::string& norm_key,
|
||||
bool to_conv_layout) {
|
||||
x = std::dynamic_pointer_cast<WanCausalConv1d>(blocks[conv_key])->forward(ctx, x);
|
||||
x = ggml_permute(ctx->ggml_ctx, x, 1, 0, 2, 3);
|
||||
x = std::dynamic_pointer_cast<LayerNorm>(blocks[norm_key])->forward(ctx, x);
|
||||
x = ggml_silu(ctx->ggml_ctx, x);
|
||||
if (to_conv_layout) {
|
||||
x = ggml_ext_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, x, 1, 0, 2, 3));
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
public:
|
||||
WanMotionEncoder(int64_t in_dim,
|
||||
int64_t hidden_dim,
|
||||
int num_token,
|
||||
bool need_global = true)
|
||||
: hidden_dim_(hidden_dim), num_token_(num_token), need_global_(need_global) {
|
||||
blocks["conv1_local"] = std::make_shared<WanCausalConv1d>(in_dim, hidden_dim / 4 * num_token);
|
||||
if (need_global) {
|
||||
blocks["conv1_global"] = std::make_shared<WanCausalConv1d>(in_dim, hidden_dim / 4);
|
||||
}
|
||||
blocks["norm1"] = std::make_shared<LayerNorm>(hidden_dim / 4, 1e-6f, false);
|
||||
blocks["conv2"] = std::make_shared<WanCausalConv1d>(hidden_dim / 4, hidden_dim / 2, 3, 2);
|
||||
blocks["norm2"] = std::make_shared<LayerNorm>(hidden_dim / 2, 1e-6f, false);
|
||||
blocks["conv3"] = std::make_shared<WanCausalConv1d>(hidden_dim / 2, hidden_dim, 3, 2);
|
||||
blocks["norm3"] = std::make_shared<LayerNorm>(hidden_dim, 1e-6f, false);
|
||||
if (need_global) {
|
||||
blocks["final_linear"] = std::make_shared<Linear>(hidden_dim, hidden_dim);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<ggml_tensor*, ggml_tensor*> forward(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
auto local = std::dynamic_pointer_cast<WanCausalConv1d>(blocks["conv1_local"])->forward(ctx, x);
|
||||
auto norm1 = std::dynamic_pointer_cast<LayerNorm>(blocks["norm1"]);
|
||||
std::vector<ggml_tensor*> tokens;
|
||||
// Each token group is normalized independently over channels.
|
||||
for (auto& group : ggml_ext_chunk(ctx->ggml_ctx, local, num_token_, 1)) {
|
||||
ggml_tensor* s = ggml_permute(ctx->ggml_ctx, group, 1, 0, 2, 3);
|
||||
s = norm1->forward(ctx, s);
|
||||
s = ggml_silu(ctx->ggml_ctx, s);
|
||||
s = ggml_ext_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, s, 1, 0, 2, 3));
|
||||
s = conv_norm_silu(ctx, s, "conv2", "norm2", true);
|
||||
s = conv_norm_silu(ctx, s, "conv3", "norm3", false);
|
||||
tokens.push_back(ggml_reshape_3d(ctx->ggml_ctx, s, s->ne[0], 1, s->ne[1]));
|
||||
}
|
||||
auto padding = ggml_reshape_3d(ctx->ggml_ctx, params["padding_tokens"], hidden_dim_, 1, 1);
|
||||
padding = ggml_repeat(ctx->ggml_ctx, padding, tokens[0]);
|
||||
tokens.push_back(padding);
|
||||
ggml_tensor* local_out = ggml_ext_vec_concat(ctx->ggml_ctx, tokens, 1);
|
||||
|
||||
if (!need_global_) {
|
||||
return {local_out, nullptr};
|
||||
}
|
||||
ggml_tensor* g = conv_norm_silu(ctx, x, "conv1_global", "norm1", true);
|
||||
g = conv_norm_silu(ctx, g, "conv2", "norm2", true);
|
||||
g = conv_norm_silu(ctx, g, "conv3", "norm3", false);
|
||||
g = std::dynamic_pointer_cast<Linear>(blocks["final_linear"])->forward(ctx, g);
|
||||
return {local_out, g};
|
||||
}
|
||||
};
|
||||
|
||||
class WanCausalAudioEncoder : public GGMLBlock {
|
||||
private:
|
||||
int num_layers_;
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map = {}, const std::string prefix = "") override {
|
||||
// Preserve the checkpoint shape for loading; layer mixing requires F32.
|
||||
auto it = tensor_storage_map.find(prefix + "weights");
|
||||
if (it != tensor_storage_map.end()) {
|
||||
params["weights"] = ggml_new_tensor(ctx, GGML_TYPE_F32, it->second.n_dims, it->second.ne);
|
||||
} else {
|
||||
params["weights"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, num_layers_);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
WanCausalAudioEncoder(int64_t audio_dim,
|
||||
int64_t dim,
|
||||
int num_token,
|
||||
int num_layers = 25)
|
||||
: num_layers_(num_layers) {
|
||||
blocks["encoder"] = std::make_shared<WanMotionEncoder>(audio_dim, dim, num_token, true);
|
||||
}
|
||||
|
||||
// features: [layers, frames, audio_dim]; outputs: [T, tokens+1, dim] and [T, dim].
|
||||
std::pair<ggml_tensor*, ggml_tensor*> forward(GGMLRunnerContext* ctx, ggml_tensor* features) {
|
||||
auto weights = ggml_silu(ctx->ggml_ctx, params["weights"]);
|
||||
auto x = ggml_mul(ctx->ggml_ctx, features, ggml_reshape_3d(ctx->ggml_ctx, weights, 1, 1, num_layers_));
|
||||
x = ggml_div(ctx->ggml_ctx, x, ggml_sum(ctx->ggml_ctx, weights));
|
||||
// Move the layer axis to ggml dimension 0 for reduction.
|
||||
x = ggml_ext_cont(ctx->ggml_ctx, ggml_ext_torch_permute(ctx->ggml_ctx, x, 2, 0, 1, 3));
|
||||
x = ggml_sum_rows(ctx->ggml_ctx, x);
|
||||
x = ggml_reshape_2d(ctx->ggml_ctx, x, x->ne[1], x->ne[2]);
|
||||
x = ggml_ext_cont(ctx->ggml_ctx, ggml_ext_torch_permute(ctx->ggml_ctx, x, 1, 0, 2, 3));
|
||||
return std::dynamic_pointer_cast<WanMotionEncoder>(blocks["encoder"])->forward(ctx, x);
|
||||
}
|
||||
};
|
||||
|
||||
class WanAudioInjector : public GGMLBlock {
|
||||
private:
|
||||
int64_t dim_;
|
||||
|
||||
public:
|
||||
WanAudioInjector(int64_t dim,
|
||||
int64_t num_heads,
|
||||
int count,
|
||||
bool qk_norm = true,
|
||||
float eps = 1e-6f)
|
||||
: dim_(dim) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
blocks["injector." + std::to_string(i)] =
|
||||
std::make_shared<WanT2VCrossAttention>(dim, num_heads, qk_norm, eps);
|
||||
blocks["injector_adain_layers." + std::to_string(i) + ".linear"] =
|
||||
std::make_shared<Linear>(dim, dim * 2);
|
||||
}
|
||||
// S2V AdaLayerNorm uses its own epsilon, independent of attention norms.
|
||||
blocks["adain_norm"] = std::make_shared<LayerNorm>(dim, 1e-5f, false);
|
||||
}
|
||||
|
||||
// Inject into the video prefix; trailing reference tokens pass through unchanged.
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* x,
|
||||
int64_t seq_len,
|
||||
int64_t T,
|
||||
int injector_id,
|
||||
ggml_tensor* audio_local,
|
||||
ggml_tensor* audio_global) {
|
||||
int64_t n_tok = seq_len / T;
|
||||
int64_t n_token = x->ne[1];
|
||||
|
||||
auto adain_linear = std::dynamic_pointer_cast<Linear>(blocks["injector_adain_layers." + std::to_string(injector_id) + ".linear"]);
|
||||
auto injector = std::dynamic_pointer_cast<WanT2VCrossAttention>(blocks["injector." + std::to_string(injector_id)]);
|
||||
auto adain_norm = std::dynamic_pointer_cast<LayerNorm>(blocks["adain_norm"]);
|
||||
|
||||
auto temb = ggml_silu(ctx->ggml_ctx, audio_global);
|
||||
temb = adain_linear->forward(ctx, temb);
|
||||
auto shift = ggml_ext_slice(ctx->ggml_ctx, temb, 0, 0, dim_);
|
||||
auto scale = ggml_ext_slice(ctx->ggml_ctx, temb, 0, dim_, dim_ * 2);
|
||||
shift = ggml_reshape_3d(ctx->ggml_ctx, shift, dim_, 1, T);
|
||||
scale = ggml_reshape_3d(ctx->ggml_ctx, scale, dim_, 1, T);
|
||||
|
||||
auto x_vid = ggml_ext_slice(ctx->ggml_ctx, x, 1, 0, seq_len);
|
||||
auto h = ggml_reshape_3d(ctx->ggml_ctx, x_vid, dim_, n_tok, T);
|
||||
h = adain_norm->forward(ctx, h);
|
||||
h = ggml_add(ctx->ggml_ctx, h, ggml_mul(ctx->ggml_ctx, h, scale));
|
||||
h = ggml_add(ctx->ggml_ctx, h, shift);
|
||||
|
||||
auto res = injector->forward(ctx, h, audio_local, 0);
|
||||
res = ggml_reshape_2d(ctx->ggml_ctx, res, dim_, seq_len);
|
||||
|
||||
auto x_head = ggml_add(ctx->ggml_ctx, x_vid, res);
|
||||
if (seq_len < n_token) {
|
||||
auto x_tail = ggml_ext_slice(ctx->ggml_ctx, x, 1, seq_len, n_token);
|
||||
return ggml_concat(ctx->ggml_ctx, x_head, x_tail, 1);
|
||||
}
|
||||
return x_head;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace WAN
|
||||
|
||||
#endif // __SD_MODEL_DIFFUSION_WAN_AUDIO_HPP__
|
||||
@@ -10,6 +10,7 @@ enum class ModelComponent {
|
||||
VAE,
|
||||
PreviewVAE,
|
||||
AudioVAE,
|
||||
AudioEncoder,
|
||||
ControlNet,
|
||||
PhotoMaker,
|
||||
PuLID,
|
||||
@@ -38,6 +39,8 @@ inline const char* model_component_name(ModelComponent component) {
|
||||
return "preview VAE";
|
||||
case ModelComponent::AudioVAE:
|
||||
return "audio VAE";
|
||||
case ModelComponent::AudioEncoder:
|
||||
return "audio encoder";
|
||||
case ModelComponent::ControlNet:
|
||||
return "ControlNet";
|
||||
case ModelComponent::PhotoMaker:
|
||||
|
||||
@@ -247,6 +247,11 @@ bool read_safetensors_file(const std::string& file_path,
|
||||
std::string dtype = tensor_info["dtype"];
|
||||
nlohmann::json shape = tensor_info["shape"];
|
||||
|
||||
// ComfyUI FP8 activation scales cancel when inference uses F16/F32 activations.
|
||||
if (ends_with(name, ".scale_input")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t begin = tensor_info["data_offsets"][0].get<size_t>();
|
||||
size_t end = tensor_info["data_offsets"][1].get<size_t>();
|
||||
if (begin > end || end > file_size_ - data_start) {
|
||||
|
||||
@@ -435,6 +435,7 @@ SDVersion ModelLoader::get_sd_version() const {
|
||||
bool is_flux2 = false;
|
||||
bool has_single_block_47 = false;
|
||||
bool is_wan = false;
|
||||
bool is_s2v = false;
|
||||
int64_t patch_embedding_channels = 0;
|
||||
bool has_img_emb = false;
|
||||
bool has_middle_block_1 = false;
|
||||
@@ -524,6 +525,11 @@ SDVersion ModelLoader::get_sd_version() const {
|
||||
if (tensor_storage.name.find("model.diffusion_model.blocks.0.cross_attn.norm_k.weight") != std::string::npos) {
|
||||
is_wan = true;
|
||||
}
|
||||
if (tensor_storage.name.find("casual_audio_encoder.weights") != std::string::npos ||
|
||||
tensor_storage.name.find("audio_injector.injector.0.q.weight") != std::string::npos) {
|
||||
// S2V and T2V-14B share patch_embedding shapes.
|
||||
is_s2v = true;
|
||||
}
|
||||
if (tensor_storage.name.find("model.diffusion_model.patch_embedder.weight") != std::string::npos) {
|
||||
return VERSION_LINGBOT_VIDEO;
|
||||
}
|
||||
@@ -587,6 +593,9 @@ SDVersion ModelLoader::get_sd_version() const {
|
||||
}
|
||||
if (is_wan) {
|
||||
LOG_VERBOSE("patch_embedding_channels %d", patch_embedding_channels);
|
||||
if (is_s2v) {
|
||||
return VERSION_WAN2_2_S2V;
|
||||
}
|
||||
if (patch_embedding_channels == 184320 && !has_img_emb) {
|
||||
return VERSION_WAN2_2_I2V;
|
||||
}
|
||||
|
||||
@@ -33,12 +33,14 @@
|
||||
#include "extensions/generation_extension.h"
|
||||
#include "model/adapter/ip_adapter.hpp"
|
||||
#include "model/adapter/lora.hpp"
|
||||
#include "model/audio/wav2vec2.hpp"
|
||||
#include "model/diffusion/animatediff.hpp"
|
||||
#include "model/diffusion/control.hpp"
|
||||
#include "model/diffusion/model.hpp"
|
||||
#include "model/vae/audio_vae.hpp"
|
||||
#include "model/vae/ltx_vae.hpp"
|
||||
#include "model/vae/vae.hpp"
|
||||
#include "runtime/audio_processing.h"
|
||||
#include "runtime/denoiser.hpp"
|
||||
#include "runtime/guidance.h"
|
||||
#include "runtime/preview_interval.h"
|
||||
@@ -74,6 +76,7 @@ const char* model_version_to_str[] = {
|
||||
"Wan 2.x",
|
||||
"Wan 2.2 I2V",
|
||||
"Wan 2.2 TI2V",
|
||||
"Wan 2.2 S2V",
|
||||
"LingBot Video",
|
||||
"Qwen Image",
|
||||
"Qwen Image Layered",
|
||||
@@ -136,7 +139,7 @@ StableDiffusionGGML::~StableDiffusionGGML() = default;
|
||||
|
||||
const std::map<StableDiffusionGGML::RunnerGroup, std::set<ModelComponent>>& StableDiffusionGGML::runner_components() {
|
||||
static const std::map<RunnerGroup, std::set<ModelComponent>> components{
|
||||
{RunnerGroup::Core, {ModelComponent::Conditioner, ModelComponent::Diffusion, ModelComponent::HighNoiseDiffusion, ModelComponent::CLIPVision, ModelComponent::IPAdapter}},
|
||||
{RunnerGroup::Core, {ModelComponent::Conditioner, ModelComponent::Diffusion, ModelComponent::HighNoiseDiffusion, ModelComponent::CLIPVision, ModelComponent::IPAdapter, ModelComponent::AudioEncoder}},
|
||||
{RunnerGroup::VAE, {ModelComponent::VAE, ModelComponent::PreviewVAE, ModelComponent::AudioVAE}},
|
||||
{RunnerGroup::ControlNet, {ModelComponent::ControlNet}},
|
||||
{RunnerGroup::Extensions, {ModelComponent::PhotoMaker, ModelComponent::PuLID}},
|
||||
@@ -804,6 +807,13 @@ bool StableDiffusionGGML::init_model_loader(ModelLoader& model_loader, ModelConf
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen(SAFE_STR(sd_ctx_params->audio_encoder_path)) > 0) {
|
||||
LOG_INFO("loading audio encoder (wav2vec2) from '%s'", sd_ctx_params->audio_encoder_path);
|
||||
if (!model_loader.init_from_file(sd_ctx_params->audio_encoder_path, "wav2vec2.")) {
|
||||
LOG_WARN("loading audio encoder weights from '%s' failed", sd_ctx_params->audio_encoder_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen(SAFE_STR(sd_ctx_params->motion_module_path)) > 0) {
|
||||
LOG_INFO("loading motion module (AnimateDiff) from '%s'", sd_ctx_params->motion_module_path);
|
||||
if (!model_loader.init_from_file(sd_ctx_params->motion_module_path,
|
||||
@@ -1010,6 +1020,7 @@ bool StableDiffusionGGML::build_core_runners() {
|
||||
high_noise_diffusion_model = std::move(runners.high_noise_diffusion);
|
||||
clip_vision = std::move(runners.clip_vision);
|
||||
ip_adapter = std::move(runners.ip_adapter);
|
||||
audio_encoder = std::move(runners.audio_encoder);
|
||||
|
||||
cond_stage_model->set_max_graph_vram_bytes(max_graph_vram_bytes_for_module(SDBackendModule::TE));
|
||||
diffusion_model->set_max_graph_vram_bytes(max_graph_vram_bytes_for_module(SDBackendModule::DIFFUSION));
|
||||
@@ -1019,11 +1030,15 @@ bool StableDiffusionGGML::build_core_runners() {
|
||||
if (clip_vision) {
|
||||
clip_vision->set_max_graph_vram_bytes(max_graph_vram_bytes_for_module(SDBackendModule::CLIP_VISION));
|
||||
}
|
||||
if (audio_encoder) {
|
||||
audio_encoder->set_max_graph_vram_bytes(max_graph_vram_bytes_for_module(SDBackendModule::AUDIO_ENCODER));
|
||||
}
|
||||
return register_runner_params(ModelComponent::Conditioner, cond_stage_model, SDBackendModule::TE) &&
|
||||
register_runner_params(ModelComponent::Diffusion, diffusion_model, SDBackendModule::DIFFUSION) &&
|
||||
register_runner_params(ModelComponent::HighNoiseDiffusion, high_noise_diffusion_model, SDBackendModule::DIFFUSION) &&
|
||||
register_runner_params(ModelComponent::CLIPVision, clip_vision, SDBackendModule::CLIP_VISION) &&
|
||||
register_runner_params(ModelComponent::IPAdapter, ip_adapter, SDBackendModule::DIFFUSION);
|
||||
register_runner_params(ModelComponent::IPAdapter, ip_adapter, SDBackendModule::DIFFUSION) &&
|
||||
register_runner_params(ModelComponent::AudioEncoder, audio_encoder, SDBackendModule::AUDIO_ENCODER);
|
||||
}
|
||||
|
||||
bool StableDiffusionGGML::build_vae_runners() {
|
||||
@@ -1121,6 +1136,12 @@ bool StableDiffusionGGML::validate_and_load_runners() {
|
||||
ignore_tensors.insert("model.diffusion_model.__32x32__");
|
||||
ignore_tensors.insert("model.diffusion_model.__index_timestep_zero__");
|
||||
|
||||
if (audio_encoder != nullptr) {
|
||||
// These wav2vec2 tensors are unused during feature extraction.
|
||||
ignore_tensors.insert("wav2vec2.lm_head.");
|
||||
ignore_tensors.insert("wav2vec2.masked_spec_embed");
|
||||
}
|
||||
|
||||
if (audio_vae_model) {
|
||||
if (!sd_version_is_minimax_h3(version)) {
|
||||
ignore_tensors.insert("audio_vae.encoder");
|
||||
@@ -1755,6 +1776,29 @@ sd::Tensor<float> StableDiffusionGGML::get_clip_vision_output(const sd::Tensor<f
|
||||
return output;
|
||||
}
|
||||
|
||||
// Returns 50 Hz wav2vec2 states in sd::Tensor layout: [dim, frames, layers].
|
||||
sd::Tensor<float> StableDiffusionGGML::get_audio_embedding(const sd_audio_t& audio) {
|
||||
if (audio_encoder == nullptr) {
|
||||
LOG_ERROR("audio encoder model is not loaded");
|
||||
return {};
|
||||
}
|
||||
if (audio.data == nullptr || audio.sample_count == 0 || audio.channels == 0 || audio.sample_rate == 0) {
|
||||
LOG_ERROR("invalid driving audio");
|
||||
return {};
|
||||
}
|
||||
auto mono = sd::audio::downmix_to_mono(audio.data, audio.sample_count, audio.channels);
|
||||
if (mono.empty()) {
|
||||
LOG_ERROR("audio mono downmix failed");
|
||||
return {};
|
||||
}
|
||||
mono = sd::audio::resample_audio(mono.data(), mono.size(), audio.sample_rate, 16000);
|
||||
if (mono.empty()) {
|
||||
LOG_ERROR("audio resample to 16 kHz failed");
|
||||
return {};
|
||||
}
|
||||
return audio_encoder->compute(n_threads, mono);
|
||||
}
|
||||
|
||||
void StableDiffusionGGML::compute_ip_adapter_tokens(const sd_image_t& image, float strength) {
|
||||
ip_adapter_tokens = {};
|
||||
ip_adapter_uncond_tokens = {};
|
||||
@@ -1810,6 +1854,10 @@ std::vector<float> StableDiffusionGGML::process_timesteps(const std::vector<floa
|
||||
}
|
||||
}
|
||||
return new_timesteps;
|
||||
}
|
||||
if (diffusion_model->get_desc() == "Wan2.2-S2V-14B") {
|
||||
int64_t frame_count = init_latent.shape()[2];
|
||||
return std::vector<float>(static_cast<size_t>(frame_count), timesteps[0]);
|
||||
} else {
|
||||
return timesteps;
|
||||
}
|
||||
@@ -2318,7 +2366,8 @@ sd::Tensor<float> StableDiffusionGGML::sample(const std::shared_ptr<DiffusionMod
|
||||
condition.c_t5_weights.empty() ? nullptr : &condition.c_t5_weights};
|
||||
} else if (sd_version_is_wan(version)) {
|
||||
diffusion_params.extra = WanDiffusionExtra{vace_context.empty() ? nullptr : &vace_context,
|
||||
vace_strength};
|
||||
vace_strength,
|
||||
condition.c_ref_audios.empty() ? nullptr : &condition.c_ref_audios[0]};
|
||||
} else if (sd_version_is_hunyuan_video(version)) {
|
||||
diffusion_params.extra = HunyuanVideoDiffusionExtra{
|
||||
&guidance_tensor,
|
||||
|
||||
@@ -27,6 +27,9 @@ struct LoraModel;
|
||||
struct ConditionerParams;
|
||||
struct SDCondition;
|
||||
struct RefImageParams;
|
||||
namespace Wav2Vec2 {
|
||||
class Wav2Vec2ModelRunner;
|
||||
}
|
||||
|
||||
extern const char* model_version_to_str[];
|
||||
|
||||
@@ -63,6 +66,7 @@ public:
|
||||
std::shared_ptr<VAE> first_stage_model;
|
||||
std::shared_ptr<VAE> preview_vae;
|
||||
std::shared_ptr<AudioVAERunner> audio_vae_model;
|
||||
std::shared_ptr<Wav2Vec2::Wav2Vec2ModelRunner> audio_encoder;
|
||||
std::shared_ptr<ControlNet> control_net;
|
||||
std::shared_ptr<IPAdapter::IPAdapterRunner> ip_adapter;
|
||||
sd::Tensor<float> ip_adapter_tokens;
|
||||
@@ -363,6 +367,8 @@ public:
|
||||
int clip_skip = -1,
|
||||
bool zero_out_masked = false);
|
||||
|
||||
sd::Tensor<float> get_audio_embedding(const sd_audio_t& audio);
|
||||
|
||||
void compute_ip_adapter_tokens(const sd_image_t& image, float strength);
|
||||
|
||||
std::vector<float> process_timesteps(const std::vector<float>& timesteps,
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace sd::pipeline {
|
||||
sd::Tensor<float> denoise_mask;
|
||||
sd::Tensor<float> clip_vision_output;
|
||||
sd::Tensor<float> vace_context;
|
||||
sd::Tensor<float> s2v_audio_embed;
|
||||
int64_t ref_image_num = 0;
|
||||
int64_t video_conditioning_frame_count = 0;
|
||||
int64_t video_target_frame_count = 0;
|
||||
@@ -59,7 +60,8 @@ namespace sd::pipeline {
|
||||
const sd_vid_gen_params_t* sd_vid_gen_params,
|
||||
sd_image_t** frames_out,
|
||||
int* num_frames_out,
|
||||
sd_audio_t** audio_out);
|
||||
sd_audio_t** audio_out,
|
||||
int* fps_out);
|
||||
|
||||
sd::Tensor<float> upscale_ltx_spatial_video_latent(StableDiffusionGGML* sd,
|
||||
const char* model_path,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "core/util.h"
|
||||
#include "extensions/generation_extension.h"
|
||||
#include "model/adapter/ip_adapter.hpp"
|
||||
#include "model/audio/wav2vec2.hpp"
|
||||
#include "model/diffusion/anima.hpp"
|
||||
#include "model/diffusion/boogu.hpp"
|
||||
#include "model/diffusion/control.hpp"
|
||||
@@ -234,6 +235,16 @@ namespace sd::model_builders {
|
||||
tensor_storage_map,
|
||||
weight_manager);
|
||||
}
|
||||
if (version == VERSION_WAN2_2_S2V &&
|
||||
tensor_storage_map.count("wav2vec2.encoder.layer_norm.bias") > 0) {
|
||||
if (!ensure_backend_pair(ctx.backends, SDBackendModule::AUDIO_ENCODER)) {
|
||||
return false;
|
||||
}
|
||||
result.audio_encoder = std::make_shared<Wav2Vec2::Wav2Vec2ModelRunner>(ctx.backends.runtime_backend(SDBackendModule::AUDIO_ENCODER),
|
||||
tensor_storage_map,
|
||||
"wav2vec2.",
|
||||
weight_manager);
|
||||
}
|
||||
} else if (sd_version_is_lingbot_video(version)) {
|
||||
bool enable_vision = false;
|
||||
for (const auto& [name, _] : tensor_storage_map) {
|
||||
|
||||
@@ -15,6 +15,9 @@ struct DiffusionModelRunner;
|
||||
struct VAE;
|
||||
struct AudioVAERunner;
|
||||
struct ControlNet;
|
||||
namespace Wav2Vec2 {
|
||||
class Wav2Vec2ModelRunner;
|
||||
}
|
||||
struct GenerationExtension;
|
||||
struct GenerationExtensionInitContext;
|
||||
namespace IPAdapter {
|
||||
@@ -37,6 +40,7 @@ namespace sd::model_builders {
|
||||
std::shared_ptr<DiffusionModelRunner> high_noise_diffusion;
|
||||
std::shared_ptr<FrozenCLIPVisionEmbedder> clip_vision;
|
||||
std::shared_ptr<IPAdapter::IPAdapterRunner> ip_adapter;
|
||||
std::shared_ptr<Wav2Vec2::Wav2Vec2ModelRunner> audio_encoder;
|
||||
};
|
||||
|
||||
struct VAEOptions {
|
||||
|
||||
@@ -161,7 +161,10 @@ namespace sd::pipeline {
|
||||
frames = sd->align_video_frames(requested_frames);
|
||||
clip_skip = sd_vid_gen_params->clip_skip;
|
||||
fps = std::max(1, sd_vid_gen_params->fps);
|
||||
if (sd_version_is_minimax_h3(sd->version) && fps != 24) {
|
||||
if (sd->version == VERSION_WAN2_2_S2V && sd_vid_gen_params->fps != 16) {
|
||||
LOG_WARN("Wan2.2 S2V uses 16 fps; overriding requested fps %d", sd_vid_gen_params->fps);
|
||||
fps = 16;
|
||||
} else if (sd_version_is_minimax_h3(sd->version) && fps != 24) {
|
||||
LOG_WARN("MiniMax-H3 uses 24 fps; overriding requested fps %d", fps);
|
||||
fps = 24;
|
||||
}
|
||||
|
||||
+149
-1
@@ -5,6 +5,7 @@
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
|
||||
#include "conditioning/wan_audio.h"
|
||||
#include "core/rng.hpp"
|
||||
#include "core/rng_philox.hpp"
|
||||
#include "diffusion_engine.h"
|
||||
@@ -420,6 +421,45 @@ namespace sd::pipeline {
|
||||
return audio;
|
||||
}
|
||||
|
||||
// Build the first 16 fps audio window, zero-padding past the track end.
|
||||
static sd::Tensor<float> build_s2v_audio_window(const sd::Tensor<float>& stacked, int64_t batch_frames) {
|
||||
const int64_t embed_dim = stacked.shape()[0];
|
||||
const int64_t in_frames = stacked.shape()[1];
|
||||
const int64_t num_layers = stacked.shape()[2];
|
||||
if (embed_dim <= 0 || in_frames <= 0 || num_layers <= 0 || batch_frames <= 0) {
|
||||
return {};
|
||||
}
|
||||
std::vector<float> layer_first(static_cast<size_t>(num_layers) * in_frames * embed_dim);
|
||||
for (int64_t l = 0; l < num_layers; ++l) {
|
||||
for (int64_t f = 0; f < in_frames; ++f) {
|
||||
const float* src = stacked.data() + l * embed_dim * in_frames + f * embed_dim;
|
||||
std::copy_n(src,
|
||||
static_cast<size_t>(embed_dim),
|
||||
layer_first.data() + (static_cast<size_t>(l) * in_frames + f) * embed_dim);
|
||||
}
|
||||
}
|
||||
sd::wan_audio::BucketPlan plan;
|
||||
std::vector<float> buckets = sd::wan_audio::build_audio_buckets(layer_first.data(),
|
||||
static_cast<int>(num_layers),
|
||||
static_cast<int>(in_frames),
|
||||
static_cast<int>(embed_dim),
|
||||
static_cast<int>(batch_frames),
|
||||
&plan);
|
||||
if (buckets.empty() || plan.bucket_frames < batch_frames) {
|
||||
return {};
|
||||
}
|
||||
// Reorder frame-major buckets into sd::Tensor's [dim, frame, layer] layout.
|
||||
sd::Tensor<float> window({embed_dim, batch_frames, num_layers});
|
||||
for (int64_t f = 0; f < batch_frames; ++f) {
|
||||
for (int64_t l = 0; l < num_layers; ++l) {
|
||||
const float* src = buckets.data() + (static_cast<size_t>(f) * num_layers + l) * embed_dim;
|
||||
float* dst = window.data() + l * embed_dim * batch_frames + f * embed_dim;
|
||||
std::copy_n(src, static_cast<size_t>(embed_dim), dst);
|
||||
}
|
||||
}
|
||||
return window;
|
||||
}
|
||||
|
||||
static std::optional<ImageGenerationLatents> prepare_video_generation_latents(StableDiffusionGGML* sd,
|
||||
const sd_vid_gen_params_t* sd_vid_gen_params,
|
||||
GenerationRequest* request) {
|
||||
@@ -1033,6 +1073,53 @@ namespace sd::pipeline {
|
||||
latents.vace_context = sd::ops::concat(vace_context, mask_context, 3); // [b, 2*c + vae_scale_factor*vae_scale_factor, t + 1 or t, h/vae_scale_factor, w/vae_scale_factor]
|
||||
int64_t t2 = ggml_time_ms();
|
||||
LOG_INFO("encode_first_stage completed, taking %" PRId64 " ms", t2 - t1);
|
||||
} else if (sd->diffusion_model->get_desc() == "Wan2.2-S2V-14B") {
|
||||
LOG_INFO("S2V");
|
||||
if (!end_image.empty()) {
|
||||
LOG_WARN("Wan2.2 S2V ignores end_image");
|
||||
}
|
||||
if (sd_vid_gen_params->ref_audios_count > 1) {
|
||||
LOG_ERROR("Wan2.2 S2V supports a single driving audio track");
|
||||
return std::nullopt;
|
||||
}
|
||||
int64_t t1 = ggml_time_ms();
|
||||
if (!start_image.empty()) {
|
||||
auto ref_img = start_image.reshape({start_image.shape()[0],
|
||||
start_image.shape()[1],
|
||||
1,
|
||||
start_image.shape()[2],
|
||||
1});
|
||||
auto encoded_ref = sd->encode_first_stage(ref_img);
|
||||
if (encoded_ref.empty()) {
|
||||
LOG_ERROR("failed to encode S2V reference image");
|
||||
return std::nullopt;
|
||||
}
|
||||
// Wan consumes reference latents in 4D.
|
||||
latents.ref_latents.push_back(encoded_ref.reshape({encoded_ref.shape()[0],
|
||||
encoded_ref.shape()[1],
|
||||
encoded_ref.shape()[2],
|
||||
encoded_ref.shape()[3]}));
|
||||
}
|
||||
if (sd_vid_gen_params->ref_audios_count == 1) {
|
||||
if (sd->audio_encoder == nullptr) {
|
||||
LOG_ERROR("S2V audio conditioning requires --audio-encoder (wav2vec2)");
|
||||
return std::nullopt;
|
||||
}
|
||||
auto stacked = sd->get_audio_embedding(sd_vid_gen_params->ref_audios[0]);
|
||||
if (stacked.empty()) {
|
||||
LOG_ERROR("failed to compute wav2vec2 embedding for driving audio");
|
||||
return std::nullopt;
|
||||
}
|
||||
int64_t latent_t = sd->video_frames_to_latent_frames(request->frames);
|
||||
int64_t batch_frames = latent_t * 4;
|
||||
latents.s2v_audio_embed = build_s2v_audio_window(stacked, batch_frames);
|
||||
if (latents.s2v_audio_embed.empty()) {
|
||||
LOG_ERROR("failed to build S2V audio window");
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
int64_t t2 = ggml_time_ms();
|
||||
LOG_INFO("s2v conditioning prepared, taking %" PRId64 " ms", t2 - t1);
|
||||
}
|
||||
|
||||
if (latents.init_latent.empty()) {
|
||||
@@ -1084,6 +1171,12 @@ namespace sd::pipeline {
|
||||
latents.keyframe_indices);
|
||||
}
|
||||
}
|
||||
if (sd->version == VERSION_WAN2_2_S2V) {
|
||||
embeds.cond.c_ref_images = latents.ref_latents;
|
||||
if (!latents.s2v_audio_embed.empty()) {
|
||||
embeds.cond.c_ref_audios = {latents.s2v_audio_embed};
|
||||
}
|
||||
}
|
||||
if (request.use_uncond) {
|
||||
condition_params.text = request.negative_prompt;
|
||||
embeds.uncond = sd->cond_stage_model->get_learned_condition(sd->n_threads,
|
||||
@@ -1096,6 +1189,12 @@ namespace sd::pipeline {
|
||||
embeds.uncond.c_reference_blocks = latents.minimax_reference_blocks;
|
||||
embeds.uncond.c_position_ids = embeds.cond.c_position_ids;
|
||||
}
|
||||
if (sd->version == VERSION_WAN2_2_S2V) {
|
||||
embeds.uncond.c_ref_images = latents.ref_latents;
|
||||
if (!latents.s2v_audio_embed.empty()) {
|
||||
embeds.uncond.c_ref_audios = {sd::Tensor<float>::zeros_like(latents.s2v_audio_embed)};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64_t t1 = ggml_time_ms();
|
||||
@@ -1422,10 +1521,14 @@ namespace sd::pipeline {
|
||||
const sd_vid_gen_params_t* sd_vid_gen_params,
|
||||
sd_image_t** frames_out,
|
||||
int* num_frames_out,
|
||||
sd_audio_t** audio_out) {
|
||||
sd_audio_t** audio_out,
|
||||
int* fps_out) {
|
||||
if (sd->config_->animatediff_loaded && sd_version_supports_animatediff(sd->version)) {
|
||||
LOG_INFO("AnimateDiff dispatch: %d frames, %dx%d",
|
||||
sd_vid_gen_params->video_frames, sd_vid_gen_params->width, sd_vid_gen_params->height);
|
||||
if (fps_out != nullptr) {
|
||||
*fps_out = std::max(1, sd_vid_gen_params->fps);
|
||||
}
|
||||
return generate_animatediff_video(sd, sd_vid_gen_params, frames_out, num_frames_out);
|
||||
}
|
||||
|
||||
@@ -1437,6 +1540,9 @@ namespace sd::pipeline {
|
||||
sd->vae_tiling_params = sd_vid_gen_params->vae_tiling_params;
|
||||
sd->apply_circular_axes(sd_vid_gen_params->circular_x, sd_vid_gen_params->circular_y);
|
||||
GenerationRequest request(sd, sd_vid_gen_params);
|
||||
if (fps_out != nullptr) {
|
||||
*fps_out = request.fps;
|
||||
}
|
||||
bool latent_upscale_enabled = request.hires.enabled;
|
||||
GenerationRequest hires_request = request;
|
||||
if (latent_upscale_enabled) {
|
||||
@@ -1725,6 +1831,33 @@ namespace sd::pipeline {
|
||||
LOG_INFO("generating latent video completed, taking %.2fs", (latent_end - latent_start) * 1.0f / 1000);
|
||||
|
||||
sd_audio_t* generated_audio = nullptr;
|
||||
if (sd->version == VERSION_WAN2_2_S2V && sd_vid_gen_params->ref_audios_count > 0) {
|
||||
// Return the driving track for muxing with the generated video.
|
||||
const sd_audio_t& driving = sd_vid_gen_params->ref_audios[0];
|
||||
generated_audio = (sd_audio_t*)malloc(sizeof(sd_audio_t));
|
||||
if (generated_audio != nullptr) {
|
||||
generated_audio->sample_rate = driving.sample_rate;
|
||||
generated_audio->channels = driving.channels;
|
||||
generated_audio->sample_count = driving.sample_count;
|
||||
generated_audio->data = (float*)malloc(sizeof(float) * driving.sample_count * driving.channels);
|
||||
if (generated_audio->data == nullptr) {
|
||||
free(generated_audio);
|
||||
generated_audio = nullptr;
|
||||
} else {
|
||||
memcpy(generated_audio->data,
|
||||
driving.data,
|
||||
sizeof(float) * driving.sample_count * driving.channels);
|
||||
}
|
||||
}
|
||||
if (generated_audio != nullptr) {
|
||||
LOG_DEBUG("s2v output audio: %u Hz, %u channels, %llu samples",
|
||||
generated_audio->sample_rate,
|
||||
generated_audio->channels,
|
||||
(unsigned long long)generated_audio->sample_count);
|
||||
} else {
|
||||
LOG_DEBUG("s2v output audio copy failed (out of memory)");
|
||||
}
|
||||
}
|
||||
if ((sd_version_is_ltxav(sd->version) || sd_version_is_minimax_h3(sd->version)) &&
|
||||
latents.audio_length > 0 &&
|
||||
sd->audio_vae_model != nullptr) {
|
||||
@@ -1774,6 +1907,7 @@ namespace sd::pipeline {
|
||||
return false;
|
||||
}
|
||||
auto result = decode_video_outputs(sd, latent_upscale_enabled ? hires_request : request, final_latent, num_frames_out);
|
||||
LOG_DEBUG("decode_video_outputs returned %s", result == nullptr ? "nullptr (failed)" : "frames");
|
||||
if (result == nullptr) {
|
||||
free_sd_audio(generated_audio);
|
||||
return false;
|
||||
@@ -1786,6 +1920,20 @@ namespace sd::pipeline {
|
||||
if (frames_out != nullptr) {
|
||||
*frames_out = result;
|
||||
}
|
||||
if (sd->version == VERSION_WAN2_2_S2V && generated_audio != nullptr) {
|
||||
// Limit the driving track to the generated video's duration.
|
||||
int fps = request.fps;
|
||||
uint64_t video_frames = num_frames_out != nullptr ? (uint64_t)*num_frames_out : 0;
|
||||
uint64_t want_samples = (uint64_t)((double)video_frames / fps * generated_audio->sample_rate);
|
||||
LOG_DEBUG("s2v audio truncate: %llu samples -> %llu (video %llu frames @ %d fps)",
|
||||
(unsigned long long)generated_audio->sample_count,
|
||||
(unsigned long long)want_samples,
|
||||
(unsigned long long)video_frames,
|
||||
fps);
|
||||
if (want_samples > 0 && want_samples < generated_audio->sample_count) {
|
||||
generated_audio->sample_count = want_samples;
|
||||
}
|
||||
}
|
||||
if (audio_out != nullptr) {
|
||||
*audio_out = generated_audio;
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#include "audio_processing.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <numeric>
|
||||
|
||||
namespace sd::audio {
|
||||
|
||||
// Match torchaudio's Hann-windowed sinc resampler.
|
||||
std::vector<float> resample_audio(const float* samples,
|
||||
uint64_t sample_count,
|
||||
uint32_t orig_sample_rate,
|
||||
uint32_t target_sample_rate) {
|
||||
if (samples == nullptr || sample_count == 0 || orig_sample_rate == 0 || target_sample_rate == 0) {
|
||||
return {};
|
||||
}
|
||||
if (orig_sample_rate == target_sample_rate) {
|
||||
return std::vector<float>(samples, samples + sample_count);
|
||||
}
|
||||
|
||||
constexpr int kLowpassFilterWidth = 6;
|
||||
constexpr double kRolloff = 0.99;
|
||||
constexpr double kPi = 3.14159265358979323846;
|
||||
|
||||
const uint64_t gcd = std::gcd(static_cast<uint64_t>(orig_sample_rate),
|
||||
static_cast<uint64_t>(target_sample_rate));
|
||||
const int64_t orig_freq = static_cast<int64_t>(orig_sample_rate / gcd);
|
||||
const int64_t new_freq = static_cast<int64_t>(target_sample_rate / gcd);
|
||||
const double base_freq = static_cast<double>(std::min(orig_freq, new_freq)) * kRolloff;
|
||||
const int64_t width = static_cast<int64_t>(std::ceil(kLowpassFilterWidth * orig_freq / base_freq));
|
||||
const int64_t kernel_size = 2 * width + orig_freq;
|
||||
|
||||
std::vector<double> kernel(static_cast<size_t>(new_freq) * kernel_size);
|
||||
for (int64_t j = 0; j < new_freq; ++j) {
|
||||
for (int64_t i = 0; i < kernel_size; ++i) {
|
||||
double t = -static_cast<double>(j) / new_freq + static_cast<double>(i - width) / orig_freq;
|
||||
t *= base_freq;
|
||||
t = std::clamp(t, -static_cast<double>(kLowpassFilterWidth), static_cast<double>(kLowpassFilterWidth));
|
||||
const double cos_arg = std::cos(t * kPi / kLowpassFilterWidth / 2);
|
||||
const double window = cos_arg * cos_arg;
|
||||
double s = t * kPi;
|
||||
const double sinc = (s == 0.0) ? 1.0 : std::sin(s) / s;
|
||||
kernel[j * kernel_size + i] = sinc * window * (base_freq / orig_freq);
|
||||
}
|
||||
}
|
||||
|
||||
const uint64_t num_phases = static_cast<uint64_t>(sample_count / orig_freq) + 1;
|
||||
const uint64_t target_length = (static_cast<uint64_t>(new_freq) * sample_count +
|
||||
static_cast<uint64_t>(orig_freq) - 1) /
|
||||
static_cast<uint64_t>(orig_freq);
|
||||
std::vector<float> out(target_length);
|
||||
for (uint64_t phase = 0; phase < num_phases; ++phase) {
|
||||
const int64_t src_base = static_cast<int64_t>(phase * orig_freq) - width;
|
||||
for (int64_t j = 0; j < new_freq; ++j) {
|
||||
const uint64_t out_index = phase * new_freq + j;
|
||||
if (out_index >= target_length) {
|
||||
break;
|
||||
}
|
||||
const double* k = &kernel[j * kernel_size];
|
||||
double acc = 0.0;
|
||||
for (int64_t i = 0; i < kernel_size; ++i) {
|
||||
const int64_t src = src_base + i;
|
||||
if (src >= 0 && src < static_cast<int64_t>(sample_count)) {
|
||||
acc += samples[src] * k[i];
|
||||
}
|
||||
}
|
||||
out[out_index] = static_cast<float>(acc);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<float> downmix_to_mono(const float* interleaved_samples,
|
||||
uint64_t sample_count,
|
||||
uint32_t channels) {
|
||||
std::vector<float> mono;
|
||||
if (interleaved_samples == nullptr || sample_count == 0 || channels == 0) {
|
||||
return mono;
|
||||
}
|
||||
mono.resize(static_cast<size_t>(sample_count));
|
||||
if (channels == 1) {
|
||||
std::memcpy(mono.data(), interleaved_samples, static_cast<size_t>(sample_count) * sizeof(float));
|
||||
return mono;
|
||||
}
|
||||
const float scale = 1.0f / static_cast<float>(channels);
|
||||
for (uint64_t i = 0; i < sample_count; ++i) {
|
||||
float sum = 0.0f;
|
||||
for (uint32_t c = 0; c < channels; ++c) {
|
||||
sum += interleaved_samples[i * channels + c];
|
||||
}
|
||||
mono[static_cast<size_t>(i)] = sum * scale;
|
||||
}
|
||||
return mono;
|
||||
}
|
||||
|
||||
} // namespace sd::audio
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef __SD_RUNTIME_AUDIO_PROCESSING_H__
|
||||
#define __SD_RUNTIME_AUDIO_PROCESSING_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace sd::audio {
|
||||
|
||||
// Returns the input unchanged when sample rates are equal, and an empty vector on invalid input.
|
||||
std::vector<float> resample_audio(const float* samples,
|
||||
uint64_t sample_count,
|
||||
uint32_t orig_sample_rate,
|
||||
uint32_t target_sample_rate);
|
||||
|
||||
// Average interleaved channels; return an empty vector on invalid input.
|
||||
std::vector<float> downmix_to_mono(const float* interleaved_samples,
|
||||
uint64_t sample_count,
|
||||
uint32_t channels);
|
||||
|
||||
} // namespace sd::audio
|
||||
|
||||
#endif // __SD_RUNTIME_AUDIO_PROCESSING_H__
|
||||
@@ -355,6 +355,7 @@ char* sd_ctx_params_to_str(const sd_ctx_params_t* sd_ctx_params) {
|
||||
"embeddings_connectors_path: %s\n"
|
||||
"vae_path: %s\n"
|
||||
"audio_vae_path: %s\n"
|
||||
"audio_encoder_path: %s\n"
|
||||
"taesd_path: %s\n"
|
||||
"control_net_path: %s\n"
|
||||
"photo_maker_path: %s\n"
|
||||
@@ -392,6 +393,7 @@ char* sd_ctx_params_to_str(const sd_ctx_params_t* sd_ctx_params) {
|
||||
SAFE_STR(sd_ctx_params->embeddings_connectors_path),
|
||||
SAFE_STR(sd_ctx_params->vae_path),
|
||||
SAFE_STR(sd_ctx_params->audio_vae_path),
|
||||
SAFE_STR(sd_ctx_params->audio_encoder_path),
|
||||
SAFE_STR(sd_ctx_params->taesd_path),
|
||||
SAFE_STR(sd_ctx_params->control_net_path),
|
||||
SAFE_STR(sd_ctx_params->photo_maker_path),
|
||||
@@ -736,8 +738,12 @@ SD_API bool generate_video(sd_ctx_t* sd_ctx,
|
||||
const sd_vid_gen_params_t* sd_vid_gen_params,
|
||||
sd_image_t** frames_out,
|
||||
int* num_frames_out,
|
||||
sd_audio_t** audio_out) {
|
||||
sd_audio_t** audio_out,
|
||||
int* fps_out) {
|
||||
if (sd_ctx == nullptr || sd_ctx->sd == nullptr || sd_vid_gen_params == nullptr) {
|
||||
if (fps_out != nullptr) {
|
||||
*fps_out = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -753,10 +759,13 @@ SD_API bool generate_video(sd_ctx_t* sd_ctx,
|
||||
|
||||
StableDiffusionGGML::ExecutionScope execution(*sd_ctx->sd);
|
||||
if (!execution.ready) {
|
||||
if (fps_out != nullptr) {
|
||||
*fps_out = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return sd::pipeline::generate_video(sd_ctx->sd, sd_vid_gen_params, frames_out, num_frames_out, audio_out);
|
||||
return sd::pipeline::generate_video(sd_ctx->sd, sd_vid_gen_params, frames_out, num_frames_out, audio_out, fps_out);
|
||||
}
|
||||
|
||||
SD_API void free_sd_images(sd_image_t* result_images, int num_images) {
|
||||
|
||||
Reference in New Issue
Block a user