diff --git a/docs/wan.md b/docs/wan.md
index 02b46d62..c7cf1dd9 100644
--- a/docs/wan.md
+++ b/docs/wan.md
@@ -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 @@
+### 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 "..."
+ --cfg-scale 1.0 --steps 4
+ ```
+
+ Expect some quality/dynamics loss compared to the full 20-step run.
+
### Wan2.2 T2V A14B T2I
```
diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp
index 22b26da7..a3cc1dde 100644
--- a/examples/cli/main.cpp
+++ b/examples/cli/main.cpp
@@ -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);
}
diff --git a/examples/common/common.cpp b/examples/common/common.cpp
index 2357dcbb..04bd89cc 100644
--- a/examples/common/common.cpp
+++ b/examples/common/common.cpp
@@ -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)",
diff --git a/examples/common/common.h b/examples/common/common.h
index 660fef4f..8a33edcf 100644
--- a/examples/common/common.h
+++ b/examples/common/common.h
@@ -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;
diff --git a/examples/server/async_jobs.cpp b/examples/server/async_jobs.cpp
index 1933dec1..8c4adf27 100644
--- a/examples/server/async_jobs.cpp
+++ b/examples/server/async_jobs.cpp
@@ -245,7 +245,7 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
{
std::lock_guard 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 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;
}
diff --git a/include/stable-diffusion.h b/include/stable-diffusion.h
index cf69b48d..31a87e85 100644
--- a/include/stable-diffusion.h
+++ b/include/stable-diffusion.h
@@ -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;
diff --git a/src/conditioning/wan_audio.cpp b/src/conditioning/wan_audio.cpp
new file mode 100644
index 00000000..d4d59ca1
--- /dev/null
+++ b/src/conditioning/wan_audio.cpp
@@ -0,0 +1,103 @@
+#include "wan_audio.h"
+
+#include
+#include
+#include
+
+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(video_rate) / fps;
+ // Keep a trailing chunk even when audio ends on a chunk boundary.
+ plan.num_chunks = static_cast(audio_frames / (batch_frames * scale)) + 1;
+ plan.bucket_frames = plan.num_chunks * batch_frames;
+ plan.padded_audio_frames = static_cast(
+ std::ceil(plan.bucket_frames / static_cast(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(std::nearbyint(static_cast(bucket_frame) * video_rate / fps));
+ }
+
+ static int interpolated_frame_count(int in_frames, int input_fps, int output_fps) {
+ return static_cast(in_frames / static_cast(input_fps) * output_fps);
+ }
+
+ // Match PyTorch linear interpolation with align_corners=True.
+ static std::vector linear_interpolate_frames(const std::vector& in,
+ int num_layers,
+ int in_frames,
+ int dim,
+ int out_frames) {
+ std::vector out(static_cast(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(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(pos);
+ const int src1 = std::min(src0 + 1, in_frames - 1);
+ const float frac = static_cast(pos - src0);
+ const float* in_row = &in[(static_cast(layer) * in_frames + src0) * dim];
+ const float* in_next = &in[(static_cast(layer) * in_frames + src1) * dim];
+ float* out_row = &out[(static_cast(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 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 interpolated =
+ linear_interpolate_frames(std::vector(stacked_states,
+ stacked_states + static_cast(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 buckets(static_cast(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(layer) * audio_frames + src) * dim,
+ static_cast(dim),
+ buckets.data() + (static_cast(frame) * num_layers + layer) * dim);
+ }
+ }
+ return buckets;
+ }
+
+} // namespace sd::wan_audio
diff --git a/src/conditioning/wan_audio.h b/src/conditioning/wan_audio.h
new file mode 100644
index 00000000..1b245ed5
--- /dev/null
+++ b/src/conditioning/wan_audio.h
@@ -0,0 +1,32 @@
+#ifndef __SD_CONDITIONING_WAN_AUDIO_H__
+#define __SD_CONDITIONING_WAN_AUDIO_H__
+
+#include
+
+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 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__
diff --git a/src/core/ggml_extend.cpp b/src/core/ggml_extend.cpp
index 7cf58539..79e3ea9b 100644
--- a/src/core/ggml_extend.cpp
+++ b/src/core/ggml_extend.cpp
@@ -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 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;
diff --git a/src/core/ggml_extend.h b/src/core/ggml_extend.h
index 585d4582..6f3fe89e 100644
--- a/src/core/ggml_extend.h
+++ b/src/core/ggml_extend.h
@@ -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,
diff --git a/src/core/ggml_extend_backend.cpp b/src/core/ggml_extend_backend.cpp
index 10179a8e..b85fc5cb 100644
--- a/src/core/ggml_extend_backend.cpp
+++ b/src/core/ggml_extend_backend.cpp
@@ -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";
}
diff --git a/src/core/ggml_extend_backend.h b/src/core/ggml_extend_backend.h
index 3430ccc5..a66bf51c 100644
--- a/src/core/ggml_extend_backend.h
+++ b/src/core/ggml_extend_backend.h
@@ -21,6 +21,7 @@ enum class SDBackendModule {
PHOTOMAKER,
UPSCALER,
DETECTOR,
+ AUDIO_ENCODER,
};
struct SDBackendAssignment {
diff --git a/src/model.h b/src/model.h
index 4b3ed316..7a8bc757 100644
--- a/src/model.h
+++ b/src/model.h
@@ -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;
diff --git a/src/model/audio/wav2vec2.hpp b/src/model/audio/wav2vec2.hpp
new file mode 100644
index 00000000..3f3c969e
--- /dev/null
+++ b/src/model/audio/wav2vec2.hpp
@@ -0,0 +1,413 @@
+#ifndef __SD_MODEL_AUDIO_WAV2VEC2_HPP__
+#define __SD_MODEL_AUDIO_WAV2VEC2_HPP__
+
+#include
+#include
+#include
+#include
+#include