diff --git a/Makefile b/Makefile index 64e6684a4da3..351ea6b39c0c 100644 --- a/Makefile +++ b/Makefile @@ -688,7 +688,7 @@ budget.o: common/reasoning-budget.cpp common/reasoning-budget.h chat.o: common/chat.cpp common/chat.h $(CXX) $(CXXFLAGS) -c $< -o $@ -SDCPP_COMMON_BASENAMES := include/stable-diffusion.h src/conditioning/conditioner.hpp src/core/backend_fit.cpp src/core/backend_fit.h src/core/ggml_extend_backend.cpp src/core/ggml_extend_backend.h src/core/ggml_extend.hpp src/core/ggml_graph_cut.cpp src/core/ggml_graph_cut.h src/core/layer_split_partition.cpp src/core/layer_split_partition.h src/core/ordered_map.hpp src/core/rng.hpp src/core/rng_mt19937.hpp src/core/rng_philox.hpp src/core/tensor_ggml.hpp src/core/tensor.hpp src/core/util.cpp src/core/util.h src/detailer.cpp src/detailer.h src/extensions/generation_extension.h src/extensions/photomaker_extension.cpp src/extensions/pulid_extension.cpp src/kcpp_sd_extensions.h src/model/adapter/ip_adapter.hpp src/model/adapter/lora.hpp src/model/adapter/pmid.hpp src/model/adapter/pulid.hpp src/model/common/block.hpp src/model/common/rope.hpp src/model/detector/yolov8.h src/model/diffusion/anima.hpp src/model/diffusion/animatediff.hpp src/model/diffusion/boogu.hpp src/model/diffusion/control.hpp src/model/diffusion/dit.hpp src/model/diffusion/ernie_image.hpp src/model/diffusion/flux.hpp src/model/diffusion/hidream_o1.hpp src/model/diffusion/hunyuan.hpp src/model/diffusion/ideogram4.hpp src/model/diffusion/krea2.hpp src/model/diffusion/lens.hpp src/model/diffusion/lingbot_video.hpp src/model/diffusion/ltxv.hpp src/model/diffusion/mage_flow.hpp src/model/diffusion/minit2i.hpp src/model/diffusion/mmdit.hpp src/model/diffusion/model.hpp src/model/diffusion/pid.hpp src/model/diffusion/qwen_image.hpp src/model/diffusion/sefi_image.hpp src/model/diffusion/unet.hpp src/model/diffusion/wan.hpp src/model/diffusion/z_image.hpp src/model.h src/model_io/binary_io.h src/model_io/gguf_io.cpp src/model_io/gguf_io.h src/model_io/gguf_reader_ext.h src/model_io/kcpp_sdcpp_quantized_safetensors.hpp src/model_io/pickle_io.cpp src/model_io/pickle_io.h src/model_io/safetensors_io.cpp src/model_io/safetensors_io.h src/model_io/streaming_writer.h src/model_io/tensor_storage.h src/model_io/torch_legacy_io.cpp src/model_io/torch_legacy_io.h src/model_io/torch_zip_io.cpp src/model_io/torch_zip_io.h src/model_loader.cpp src/model_loader.h src/model_manager.cpp src/model_manager.h src/model/te/clip.hpp src/model/te/llm.hpp src/model/te/t5.hpp src/model/upscaler/esrgan.hpp src/model/upscaler/ltx_latent_upscaler.hpp src/model/vae/auto_encoder_kl.hpp src/model/vae/hunyuan_vae.hpp src/model/vae/ltx_audio_vae.hpp src/model/vae/ltx_vae.hpp src/model/vae/mage_vae.hpp src/model/vae/tae.hpp src/model/vae/vae.hpp src/model/vae/wan_vae.hpp src/name_conversion.cpp src/name_conversion.h src/runtime/cache_dit.hpp src/runtime/condition_cache_utils.hpp src/runtime/denoiser.hpp src/runtime/easycache.hpp src/runtime/gits_noise.h src/runtime/guidance.cpp src/runtime/guidance.h src/runtime/imatrix.cpp src/runtime/imatrix.h src/runtime/latent-preview.h src/runtime/preprocessing.hpp src/runtime/sample-cache.cpp src/runtime/sample-cache.h src/runtime/spectrum.hpp src/runtime/ucache.hpp src/stable-diffusion.cpp src/tokenizers/bpe_tokenizer.cpp src/tokenizers/bpe_tokenizer.h src/tokenizers/clip_tokenizer.cpp src/tokenizers/clip_tokenizer.h src/tokenizers/gemma_tokenizer.cpp src/tokenizers/gemma_tokenizer.h src/tokenizers/gpt_oss_tokenizer.cpp src/tokenizers/gpt_oss_tokenizer.h src/tokenizers/mistral_tokenizer.cpp src/tokenizers/mistral_tokenizer.h src/tokenizers/qwen2_tokenizer.cpp src/tokenizers/qwen2_tokenizer.h src/tokenizers/t5_unigram_tokenizer.cpp src/tokenizers/t5_unigram_tokenizer.h src/tokenizers/tokenizer.cpp src/tokenizers/tokenizer.h src/tokenizers/tokenize_util.cpp src/tokenizers/tokenize_util.h src/tokenizers/vocab/vocab.h src/upscaler.cpp src/upscaler.h src/weight_manager.h +SDCPP_COMMON_BASENAMES := include/stable-diffusion.h src/conditioning/conditioner.hpp src/core/backend_fit.cpp src/core/backend_fit.h src/core/ggml_extend_backend.cpp src/core/ggml_extend_backend.h src/core/ggml_extend.hpp src/core/ggml_graph_cut.cpp src/core/ggml_graph_cut.h src/core/layer_split_partition.cpp src/core/layer_split_partition.h src/core/ordered_map.hpp src/core/rng.hpp src/core/rng_mt19937.hpp src/core/rng_philox.hpp src/core/tensor_ggml.hpp src/core/tensor.hpp src/core/util.cpp src/core/util.h src/detailer.cpp src/detailer.h src/extensions/generation_extension.h src/extensions/photomaker_extension.cpp src/extensions/pulid_extension.cpp src/kcpp_sd_extensions.h src/model/adapter/ip_adapter.hpp src/model/adapter/lora.hpp src/model/adapter/pmid.hpp src/model/adapter/pulid.hpp src/model/common/block.hpp src/model/common/rope.hpp src/model/detector/yolov8.h src/model/diffusion/anima.hpp src/model/diffusion/animatediff.hpp src/model/diffusion/boogu.hpp src/model/diffusion/control.hpp src/model/diffusion/dit.hpp src/model/diffusion/ernie_image.hpp src/model/diffusion/flux.hpp src/model/diffusion/hidream_o1.hpp src/model/diffusion/hunyuan.hpp src/model/diffusion/ideogram4.hpp src/model/diffusion/krea2.hpp src/model/diffusion/lens.hpp src/model/diffusion/lingbot_video.hpp src/model/diffusion/ltxv.hpp src/model/diffusion/mage_flow.hpp src/model/diffusion/minimax_h3.hpp src/model/diffusion/minit2i.hpp src/model/diffusion/mmdit.hpp src/model/diffusion/model.hpp src/model/diffusion/pid.hpp src/model/diffusion/qwen_image.hpp src/model/diffusion/sefi_image.hpp src/model/diffusion/unet.hpp src/model/diffusion/wan.hpp src/model/diffusion/z_image.hpp src/model.h src/model_io/binary_io.h src/model_io/gguf_io.cpp src/model_io/gguf_io.h src/model_io/gguf_reader_ext.h src/model_io/kcpp_sdcpp_quantized_safetensors.hpp src/model_io/pickle_io.cpp src/model_io/pickle_io.h src/model_io/safetensors_io.cpp src/model_io/safetensors_io.h src/model_io/streaming_writer.h src/model_io/tensor_storage.h src/model_io/torch_legacy_io.cpp src/model_io/torch_legacy_io.h src/model_io/torch_zip_io.cpp src/model_io/torch_zip_io.h src/model_loader.cpp src/model_loader.h src/model_manager.cpp src/model_manager.h src/model/te/clip.hpp src/model/te/llm.hpp src/model/te/t5.hpp src/model/upscaler/esrgan.hpp src/model/upscaler/ltx_latent_upscaler.hpp src/model/vae/audio_vae.hpp src/model/vae/auto_encoder_kl.hpp src/model/vae/hunyuan_vae.hpp src/model/vae/ltx_audio_vae.hpp src/model/vae/ltx_vae.hpp src/model/vae/mage_vae.hpp src/model/vae/minimax_h3_audio_vae.hpp src/model/vae/minimax_h3_vae.hpp src/model/vae/tae.hpp src/model/vae/vae.hpp src/model/vae/wan_vae.hpp src/name_conversion.cpp src/name_conversion.h src/runtime/cache_dit.hpp src/runtime/condition_cache_utils.hpp src/runtime/denoiser.hpp src/runtime/easycache.hpp src/runtime/gits_noise.h src/runtime/guidance.cpp src/runtime/guidance.h src/runtime/imatrix.cpp src/runtime/imatrix.h src/runtime/latent-preview.h src/runtime/preprocessing.hpp src/runtime/sample-cache.cpp src/runtime/sample-cache.h src/runtime/spectrum.hpp src/runtime/ucache.hpp src/stable-diffusion.cpp src/tokenizers/bpe_tokenizer.cpp src/tokenizers/bpe_tokenizer.h src/tokenizers/clip_tokenizer.cpp src/tokenizers/clip_tokenizer.h src/tokenizers/gemma_tokenizer.cpp src/tokenizers/gemma_tokenizer.h src/tokenizers/gpt_oss_tokenizer.cpp src/tokenizers/gpt_oss_tokenizer.h src/tokenizers/mistral_tokenizer.cpp src/tokenizers/mistral_tokenizer.h src/tokenizers/qwen2_tokenizer.cpp src/tokenizers/qwen2_tokenizer.h src/tokenizers/t5_unigram_tokenizer.cpp src/tokenizers/t5_unigram_tokenizer.h src/tokenizers/tokenizer.cpp src/tokenizers/tokenizer.h src/tokenizers/tokenize_util.cpp src/tokenizers/tokenize_util.h src/tokenizers/vocab/vocab.h src/upscaler.cpp src/upscaler.h src/weight_manager.h SDCPP_MAIN_BASENAMES := examples/cli/image_metadata.cpp examples/cli/image_metadata.h examples/cli/main.cpp examples/cli/msf_gif.h examples/common/common.cpp examples/common/common.h examples/common/log.cpp examples/common/log.h examples/common/media_io.cpp examples/common/media_io.h examples/common/resource_owners.hpp src/tokenizers/vocab/clip_merges.hpp src/tokenizers/vocab/gemma2_merges.hpp src/tokenizers/vocab/gemma2_vocab.hpp src/tokenizers/vocab/gemma_merges.hpp src/tokenizers/vocab/gemma_vocab.hpp src/tokenizers/vocab/gpt_oss_merges.hpp src/tokenizers/vocab/gpt_oss_vocab.hpp src/tokenizers/vocab/mistral_merges.hpp src/tokenizers/vocab/mistral_vocab.hpp src/tokenizers/vocab/qwen_merges.hpp src/tokenizers/vocab/t5.hpp src/tokenizers/vocab/umt5.hpp src/tokenizers/vocab/vocab.cpp src/convert.cpp src/version.cpp diff --git a/otherarch/sdcpp/examples/cli/main.cpp b/otherarch/sdcpp/examples/cli/main.cpp index af62a9a93890..1cc7a7af4c38 100644 --- a/otherarch/sdcpp/examples/cli/main.cpp +++ b/otherarch/sdcpp/examples/cli/main.cpp @@ -754,6 +754,18 @@ int main(int argc, const char* argv[]) { return true; }; + auto load_audio = [&](const std::string& path, SDAudioOwner& audio) -> bool { + std::vector samples; + uint32_t sample_rate = 0; + uint32_t channels = 0; + if (!load_wav_from_file(path, samples, sample_rate, channels)) { + LOG_ERROR("load WAV audio from '%s' failed", path.c_str()); + return false; + } + audio.reset(std::move(samples), sample_rate, channels); + return true; + }; + if (gen_params.init_image_path.size() > 0) { if (!load_image_and_update_size(gen_params.init_image_path, gen_params.init_image)) { return 1; @@ -777,6 +789,37 @@ int main(int argc, const char* argv[]) { } } + if (!gen_params.ref_video_paths.empty()) { + gen_params.ref_videos.clear(); + gen_params.ref_videos.reserve(gen_params.ref_video_paths.size()); + for (const auto& path : gen_params.ref_video_paths) { + std::vector frames; + if (!load_images_from_dir(path, frames, 0, 0, 0, cli_params.verbose) || frames.empty()) { + LOG_ERROR("load reference video frames from '%s' failed", path.c_str()); + return 1; + } + gen_params.ref_videos.push_back(std::move(frames)); + } + + gen_params.ref_video_audios.clear(); + gen_params.ref_video_audios.resize(gen_params.ref_videos.size()); + for (size_t i = 0; i < gen_params.ref_video_audio_paths.size(); ++i) { + if (!load_audio(gen_params.ref_video_audio_paths[i], gen_params.ref_video_audios[i])) { + return 1; + } + } + } + + if (!gen_params.ref_audio_paths.empty()) { + gen_params.ref_audios.clear(); + gen_params.ref_audios.resize(gen_params.ref_audio_paths.size()); + for (size_t i = 0; i < gen_params.ref_audio_paths.size(); ++i) { + if (!load_audio(gen_params.ref_audio_paths[i], gen_params.ref_audios[i])) { + return 1; + } + } + } + if (gen_params.mask_image_path.size() > 0) { if (!load_sd_image_from_file(gen_params.mask_image.put(), gen_params.mask_image_path.c_str(), diff --git a/otherarch/sdcpp/examples/common/common.cpp b/otherarch/sdcpp/examples/common/common.cpp index 82558e7c24a1..28f18d9e6119 100644 --- a/otherarch/sdcpp/examples/common/common.cpp +++ b/otherarch/sdcpp/examples/common/common.cpp @@ -1008,7 +1008,7 @@ ArgOptions SDGenerationParams::get_options() { &hires_upscaler}, {"", "--extra-sample-args", - "extra sampler/scheduler/guidance args, key=value list. CFG supports guidance_schedule; APG supports apg_eta, apg_momentum, apg_norm_threshold, apg_norm_threshold_smoothing; SLG supports slg_uncond; lcm supports noise_clip_std, noise_scale_start, noise_scale_end; flux supports base_shift, max_shift; ltx2 supports max_shift, base_shift, stretch, terminal; euler_ge supports gamma;; logit_normal supports mu, std, logsnr_min, logsnr_max, resolution_aware", + "extra sampler/scheduler/guidance args, key=value list. CFG supports guidance_schedule; APG supports apg_eta, apg_momentum, apg_norm_threshold, apg_norm_threshold_smoothing; SLG supports slg_uncond; lcm supports noise_clip_std, noise_scale_start, noise_scale_end; flux supports base_shift, max_shift; ltx2 supports max_shift, base_shift, stretch, terminal; euler_ge supports gamma; beta scheduler supports alpha, beta; logit_normal supports mu, std, logsnr_min, logsnr_max, resolution_aware; lms supports lms_divisions", (int)',', &extra_sample_args}, {"", @@ -1404,6 +1404,30 @@ ArgOptions SDGenerationParams::get_options() { return 1; }; + auto on_ref_video_arg = [&](int argc, const char** argv, int index) { + if (++index >= argc) { + return -1; + } + ref_video_paths.push_back(argv[index]); + return 1; + }; + + auto on_ref_video_audio_arg = [&](int argc, const char** argv, int index) { + if (++index >= argc) { + return -1; + } + ref_video_audio_paths.push_back(argv[index]); + return 1; + }; + + auto on_ref_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; @@ -1538,12 +1562,12 @@ ArgOptions SDGenerationParams::get_options() { on_seed_arg}, {"", "--sampling-method", - "sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, dpm++2m_sde, dpm++2m_sde_bt, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep, res_2s, er_sde, euler_cfg_pp, euler_a_cfg_pp]" + "sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, dpm++2m_sde, dpm++2m_sde_bt, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep, res_2s, er_sde, euler_cfg_pp, euler_a_cfg_pp, lms]" "(default: euler for Flux/SD3/Wan, euler_a otherwise)", on_sample_method_arg}, {"", "--high-noise-sampling-method", - "(high noise) sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, dpm++2m_sde, dpm++2m_sde_bt, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep, res_2s, er_sde, euler_cfg_pp, euler_a_cfg_pp]" + "(high noise) sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, dpm++2m_sde, dpm++2m_sde_bt, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep, res_2s, er_sde, euler_cfg_pp, euler_a_cfg_pp, lms]" " default: euler for Flux/SD3/Wan, euler_a otherwise", on_high_noise_sample_method_arg}, {"", @@ -1568,8 +1592,20 @@ ArgOptions SDGenerationParams::get_options() { on_high_noise_skip_layers_arg}, {"-r", "--ref-image", - "reference image for Flux Kontext models (can be used multiple times)", + "reference image for Flux Kontext or MiniMax-H3 Ref2VA (can be used multiple times)", on_ref_image_arg}, + {"", + "--ref-video", + "MiniMax-H3 Ref2VA reference video frame directory at 24 fps (can be used multiple times)", + on_ref_video_arg}, + {"", + "--ref-video-audio", + "WAV soundtrack paired by index with --ref-video (can be used multiple times)", + on_ref_video_audio_arg}, + {"", + "--ref-audio", + "standalone WAV reference for MiniMax-H3 Ref2VA (can be used multiple times)", + on_ref_audio_arg}, {"", "--cache-mode", "caching method: 'easycache' (DiT), 'ucache' (UNET), 'dbcache'/'taylorseer'/'cache-dit' (DiT block-level), 'spectrum' (UNET/DiT Chebyshev+Taylor forecasting)", @@ -1901,6 +1937,7 @@ bool SDGenerationParams::from_json_str( load_if_exists("strength", strength); load_if_exists("control_strength", control_strength); + load_if_exists("ip_adapter_strength", ip_adapter_strength); load_if_exists("moe_boundary", moe_boundary); load_if_exists("vace_strength", vace_strength); @@ -2072,6 +2109,10 @@ bool SDGenerationParams::from_json_str( LOG_ERROR("invalid control_image"); return false; } + if (!parse_image_json_field(j, "ip_adapter_image", 3, width, height, ip_adapter_image)) { + LOG_ERROR("invalid ip_adapter_image"); + return false; + } return true; } @@ -2361,6 +2402,16 @@ bool SDGenerationParams::validate(SDMode mode) { return false; } + if (ref_video_audio_paths.size() > ref_video_paths.size()) { + LOG_ERROR("error: each --ref-video-audio needs a corresponding --ref-video"); + return false; + } + + if (mode != VID_GEN && (!ref_video_paths.empty() || !ref_video_audio_paths.empty() || !ref_audio_paths.empty())) { + LOG_ERROR("error: reference video and audio inputs require vid_gen mode"); + return false; + } + if (sample_params.shifted_timestep < 0 || sample_params.shifted_timestep > 1000) { LOG_ERROR("error: shifted_timestep must be in range [0, 1000]"); return false; @@ -2555,6 +2606,35 @@ sd_vid_gen_params_t SDGenerationParams::to_sd_vid_gen_params_t() { control_frame_views.push_back(frame.get()); } + ref_image_views.clear(); + ref_image_views.reserve(ref_images.size()); + for (auto& image : ref_images) { + ref_image_views.push_back(image.get()); + } + + ref_video_frame_views.clear(); + ref_video_frame_views.resize(ref_videos.size()); + ref_video_views.clear(); + ref_video_views.reserve(ref_videos.size()); + for (size_t i = 0; i < ref_videos.size(); ++i) { + auto& frame_views = ref_video_frame_views[i]; + frame_views.reserve(ref_videos[i].size()); + for (auto& frame : ref_videos[i]) { + frame_views.push_back(frame.get()); + } + sd_audio_t audio = i < ref_video_audios.size() ? ref_video_audios[i].get() : sd_audio_t{}; + ref_video_views.push_back({frame_views.empty() ? nullptr : frame_views.data(), + static_cast(frame_views.size()), + 24, + audio}); + } + + ref_audio_views.clear(); + ref_audio_views.reserve(ref_audios.size()); + for (auto& audio : ref_audios) { + ref_audio_views.push_back(audio.get()); + } + sample_params.guidance.slg.layers = skip_layers.empty() ? nullptr : skip_layers.data(); sample_params.guidance.slg.layer_count = skip_layers.size(); high_noise_sample_params.guidance.slg.layers = high_noise_skip_layers.empty() ? nullptr : high_noise_skip_layers.data(); @@ -2573,6 +2653,12 @@ sd_vid_gen_params_t SDGenerationParams::to_sd_vid_gen_params_t() { params.clip_skip = clip_skip; params.init_image = init_image.get(); params.end_image = end_image.get(); + params.ref_images = ref_image_views.empty() ? nullptr : ref_image_views.data(); + params.ref_images_count = static_cast(ref_image_views.size()); + params.ref_videos = ref_video_views.empty() ? nullptr : ref_video_views.data(); + params.ref_videos_count = static_cast(ref_video_views.size()); + params.ref_audios = ref_audio_views.empty() ? nullptr : ref_audio_views.data(); + params.ref_audios_count = static_cast(ref_audio_views.size()); params.control_frames = control_frame_views.empty() ? nullptr : control_frame_views.data(); params.control_frames_size = static_cast(control_frame_views.size()); params.width = get_resolved_width(); @@ -2652,6 +2738,9 @@ std::string SDGenerationParams::to_string() const { << " mask_image_path: \"" << mask_image_path << "\",\n" << " control_image_path: \"" << control_image_path << "\",\n" << " ref_image_paths: " << vec_str_to_string(ref_image_paths) << ",\n" + << " ref_video_paths: " << vec_str_to_string(ref_video_paths) << ",\n" + << " ref_video_audio_paths: " << vec_str_to_string(ref_video_audio_paths) << ",\n" + << " ref_audio_paths: " << vec_str_to_string(ref_audio_paths) << ",\n" << " control_video_path: \"" << control_video_path << "\",\n" << " auto_resize_ref_image: " << (auto_resize_ref_image ? "true" : "false") << ",\n" << " increase_ref_index: " << (increase_ref_index ? "true" : "false") << ",\n" @@ -2807,6 +2896,7 @@ std::string build_sdcpp_image_metadata_json(const SDContextParams& ctx_params, root["clip_skip"] = gen_params.clip_skip; root["strength"] = gen_params.strength; root["control_strength"] = gen_params.control_strength; + root["ip_adapter_strength"] = gen_params.ip_adapter_strength; root["auto_resize_ref_image"] = gen_params.auto_resize_ref_image; root["increase_ref_index"] = gen_params.increase_ref_index; if (mode == VID_GEN) { diff --git a/otherarch/sdcpp/examples/common/common.h b/otherarch/sdcpp/examples/common/common.h index ea90c8c1bdfb..34b4a013b10c 100644 --- a/otherarch/sdcpp/examples/common/common.h +++ b/otherarch/sdcpp/examples/common/common.h @@ -212,6 +212,9 @@ struct SDGenerationParams { std::string control_image_path; std::string ip_adapter_image_path; std::vector ref_image_paths; + std::vector ref_video_paths; + std::vector ref_video_audio_paths; + std::vector ref_audio_paths; std::string control_video_path; sd_sample_params_t sample_params; @@ -275,6 +278,9 @@ struct SDGenerationParams { SDImageOwner init_image; SDImageOwner end_image; std::vector ref_images; + std::vector> ref_videos; + std::vector ref_video_audios; + std::vector ref_audios; SDImageOwner mask_image; SDImageOwner control_image; SDImageOwner ip_adapter_image; @@ -283,6 +289,9 @@ struct SDGenerationParams { // Backing storage for sd_img_gen_params_t view fields. std::vector ref_image_views; + std::vector> ref_video_frame_views; + std::vector ref_video_views; + std::vector ref_audio_views; std::vector pm_id_image_views; std::vector control_frame_views; diff --git a/otherarch/sdcpp/examples/common/media_io.cpp b/otherarch/sdcpp/examples/common/media_io.cpp index f0fdf374e444..aadec6f0f368 100644 --- a/otherarch/sdcpp/examples/common/media_io.cpp +++ b/otherarch/sdcpp/examples/common/media_io.cpp @@ -1489,3 +1489,132 @@ sd_audio_t load_pcm_wav_from_file(const std::string& path) { audio.data = samples; return audio; } + +static uint16_t read_le16(const uint8_t* data) { + return static_cast(data[0]) | + (static_cast(data[1]) << 8); +} + +static uint32_t read_le32(const uint8_t* data) { + return static_cast(data[0]) | + (static_cast(data[1]) << 8) | + (static_cast(data[2]) << 16) | + (static_cast(data[3]) << 24); +} + +bool load_wav_from_file(const std::string& path, + std::vector& interleaved_samples, + uint32_t& sample_rate, + uint32_t& channels) { + interleaved_samples.clear(); + sample_rate = 0; + channels = 0; + + std::ifstream file(path, std::ios::binary); + uint8_t riff_header[12]; + if (!file.read(reinterpret_cast(riff_header), sizeof(riff_header)) || + std::memcmp(riff_header, "RIFF", 4) != 0 || + std::memcmp(riff_header + 8, "WAVE", 4) != 0) { + return false; + } + + uint16_t audio_format = 0; + uint16_t bits_per_sample = 0; + uint16_t block_align = 0; + std::streampos data_pos = std::streampos(-1); + uint32_t data_size = 0; + + while (file.good()) { + uint8_t chunk_header[8]; + if (!file.read(reinterpret_cast(chunk_header), sizeof(chunk_header))) { + break; + } + uint32_t chunk_size = read_le32(chunk_header + 4); + std::streampos chunk_data_pos = file.tellg(); + + if (std::memcmp(chunk_header, "fmt ", 4) == 0) { + if (chunk_size < 16) { + return false; + } + std::vector fmt(chunk_size); + if (!file.read(reinterpret_cast(fmt.data()), chunk_size)) { + return false; + } + audio_format = read_le16(fmt.data()); + channels = read_le16(fmt.data() + 2); + sample_rate = read_le32(fmt.data() + 4); + block_align = read_le16(fmt.data() + 12); + bits_per_sample = read_le16(fmt.data() + 14); + if (audio_format == 0xfffe && chunk_size >= 40) { + audio_format = read_le16(fmt.data() + 24); + } + } else if (std::memcmp(chunk_header, "data", 4) == 0) { + data_pos = chunk_data_pos; + data_size = chunk_size; + file.seekg(chunk_size, std::ios::cur); + } else { + file.seekg(chunk_size, std::ios::cur); + } + + if (!file.good()) { + break; + } + if ((chunk_size & 1) != 0) { + file.seekg(1, std::ios::cur); + } + } + + const uint32_t bytes_per_sample = (bits_per_sample + 7) / 8; + if (data_pos == std::streampos(-1) || data_size == 0 || channels == 0 || sample_rate == 0 || + block_align == 0 || bytes_per_sample == 0 || block_align < channels * bytes_per_sample || + (audio_format != 1 && audio_format != 3)) { + return false; + } + + const uint64_t frame_count = data_size / block_align; + if (frame_count == 0 || frame_count > SIZE_MAX / channels) { + return false; + } + std::vector pcm(data_size); + file.clear(); + file.seekg(data_pos); + if (!file.read(reinterpret_cast(pcm.data()), data_size)) { + return false; + } + + interleaved_samples.resize(static_cast(frame_count * channels)); + for (uint64_t frame = 0; frame < frame_count; ++frame) { + const uint8_t* frame_data = pcm.data() + frame * block_align; + for (uint32_t channel = 0; channel < channels; ++channel) { + const uint8_t* sample_data = frame_data + channel * bytes_per_sample; + float sample = 0.0f; + if (audio_format == 3 && bits_per_sample == 32) { + std::memcpy(&sample, sample_data, sizeof(sample)); + } else if (audio_format == 3 && bits_per_sample == 64) { + double value; + std::memcpy(&value, sample_data, sizeof(value)); + sample = static_cast(value); + } else if (audio_format == 1 && bits_per_sample == 8) { + sample = (static_cast(sample_data[0]) - 128) / 128.0f; + } else if (audio_format == 1 && bits_per_sample == 16) { + sample = static_cast(read_le16(sample_data)) / 32768.0f; + } else if (audio_format == 1 && bits_per_sample == 24) { + int32_t value = static_cast(sample_data[0]) | + (static_cast(sample_data[1]) << 8) | + (static_cast(sample_data[2]) << 16); + if ((value & 0x800000) != 0) { + value |= ~0xffffff; + } + sample = value / 8388608.0f; + } else if (audio_format == 1 && bits_per_sample == 32) { + int32_t value = static_cast(read_le32(sample_data)); + sample = value / 2147483648.0f; + } else { + interleaved_samples.clear(); + return false; + } + interleaved_samples[static_cast(frame * channels + channel)] = sample; + } + } + return true; +} diff --git a/otherarch/sdcpp/examples/common/media_io.h b/otherarch/sdcpp/examples/common/media_io.h index df2fd019b3f8..82a6ab29a88a 100644 --- a/otherarch/sdcpp/examples/common/media_io.h +++ b/otherarch/sdcpp/examples/common/media_io.h @@ -112,4 +112,9 @@ bool write_wav_to_file(const std::string& path, sd_audio_t load_pcm_wav_from_file(const std::string& path); +bool load_wav_from_file(const std::string& path, + std::vector& interleaved_samples, + uint32_t& sample_rate, + uint32_t& channels); + #endif // __MEDIA_IO_H__ diff --git a/otherarch/sdcpp/examples/common/resource_owners.hpp b/otherarch/sdcpp/examples/common/resource_owners.hpp index d7525a5faaf8..73f8bdd81d1c 100644 --- a/otherarch/sdcpp/examples/common/resource_owners.hpp +++ b/otherarch/sdcpp/examples/common/resource_owners.hpp @@ -141,6 +141,37 @@ class SDImageOwner { } }; +class SDAudioOwner { +private: + uint32_t sample_rate_ = 0; + uint32_t channels_ = 0; + std::vector samples_; + +public: + SDAudioOwner() = default; + + void reset(std::vector samples = {}, uint32_t sample_rate = 0, uint32_t channels = 0) { + samples_ = std::move(samples); + sample_rate_ = sample_rate; + channels_ = channels; + } + + bool empty() const { + return samples_.empty(); + } + + sd_audio_t get() { + return {sample_rate_, + channels_, + channels_ == 0 ? 0 : static_cast(samples_.size() / channels_), + samples_.empty() ? nullptr : samples_.data()}; + } + + const std::vector& samples() const { + return samples_; + } +}; + class SDImageVec { private: std::vector images_; diff --git a/otherarch/sdcpp/include/stable-diffusion.h b/otherarch/sdcpp/include/stable-diffusion.h index 60c99992f5f9..0a1162338fe8 100644 --- a/otherarch/sdcpp/include/stable-diffusion.h +++ b/otherarch/sdcpp/include/stable-diffusion.h @@ -56,6 +56,7 @@ enum sample_method_t { EULER_GE_SAMPLE_METHOD, DPMPP2M_SDE_SAMPLE_METHOD, DPMPP2M_SDE_BT_SAMPLE_METHOD, + LMS_SAMPLE_METHOD, SAMPLE_METHOD_COUNT }; @@ -246,6 +247,13 @@ typedef struct { uint8_t* data; } sd_image_t; +typedef struct { + sd_image_t* frames; + int frame_count; + int fps; + sd_audio_t audio; +} sd_ref_video_t; + typedef struct { int* layers; size_t layer_count; @@ -396,6 +404,12 @@ typedef struct { int clip_skip; sd_image_t init_image; sd_image_t end_image; + sd_image_t* ref_images; + int ref_images_count; + sd_ref_video_t* ref_videos; + int ref_videos_count; + sd_audio_t* ref_audios; + int ref_audios_count; sd_image_t* control_frames; int control_frames_size; int width; diff --git a/otherarch/sdcpp/sdtype_adapter.cpp b/otherarch/sdcpp/sdtype_adapter.cpp index 0542c03bbe1a..d2e68f65b08c 100644 --- a/otherarch/sdcpp/sdtype_adapter.cpp +++ b/otherarch/sdcpp/sdtype_adapter.cpp @@ -273,6 +273,11 @@ std::string load_gpt_oss_vocab_json() return load_embd_file(cache, "embd_res/gpt_oss_vocab_json.embd"); } +static bool is_video_model(kcpp_sd::model_info info) +{ + return info.is_wan || info.is_ltx || info.is_minimaxh3; +} + bool sdtype_load_model(const sd_load_model_inputs inputs) { sd_is_quiet = inputs.quiet; set_sd_quiet(sd_is_quiet); @@ -501,7 +506,7 @@ bool sdtype_load_model(const sd_load_model_inputs inputs) { auto info = get_model_info(sd_ctx); - if (info.is_wan || info.is_ltx) + if (is_video_model(info)) { printf("\nSetting to Video Generation Mode!\n"); is_vid_model = true; @@ -954,9 +959,9 @@ static sd_audio_t load_audio_from_b64(const std::string& b64audio) { return audio; } -bool supports_reference_images(kcpp_sd::model_info info) +static bool supports_reference_images(kcpp_sd::model_info info) { - bool supported = (info.is_wan || info.is_ltx || info.supports_ref_image || info.is_kontext || photomaker_enabled) && !info.is_zimage; + bool supported = (is_video_model(info) || info.supports_ref_image || info.is_kontext || photomaker_enabled) && !info.is_zimage; return supported; } @@ -1082,7 +1087,7 @@ sd_generation_outputs sdtype_generate(const sd_generation_inputs inputs) } } - if ((info.is_wan || info.is_ltx) && extra_image_data.size() == 0 && is_img2img) + if (is_video_model(info) && extra_image_data.size() == 0 && is_img2img) { extra_image_data.push_back(img2img_data); } @@ -1164,7 +1169,7 @@ sd_generation_outputs sdtype_generate(const sd_generation_inputs inputs) int desiredchannels = 3; if(supports_reference_images(info)||force_image_edit) { - if(info.is_wan || info.is_ltx) + if(is_video_model(info)) { uint8_t * loaded = load_image_from_b64(extra_image_data[i],nx2,ny2,img2imgW,img2imgH,3); if(loaded) diff --git a/otherarch/sdcpp/src/conditioning/conditioner.hpp b/otherarch/sdcpp/src/conditioning/conditioner.hpp index e84e86297605..3d7ff0397f35 100644 --- a/otherarch/sdcpp/src/conditioning/conditioner.hpp +++ b/otherarch/sdcpp/src/conditioning/conditioner.hpp @@ -2,8 +2,10 @@ #define __SD_CONDITIONING_CONDITIONER_HPP__ #include +#include #include #include +#include #include "core/tensor_ggml.hpp" #include "core/util.h" @@ -25,6 +27,8 @@ struct SDCondition { sd::Tensor c_vinput_mask; std::vector>> c_image_embeds; std::vector> c_ref_images; + std::vector> c_ref_audios; + std::vector c_reference_blocks; std::vector> extra_c_crossattns; @@ -55,6 +59,12 @@ struct SDCondition { } } + for (const auto& tensor : c_ref_audios) { + if (!tensor.empty()) { + return false; + } + } + for (const auto& tensor : extra_c_crossattns) { if (!tensor.empty()) { return false; @@ -65,6 +75,18 @@ struct SDCondition { } }; +enum class MiniMaxH3PresentationKind { + IMAGE, + VIDEO, + AUDIO, +}; + +struct MiniMaxH3PresentationItem { + MiniMaxH3PresentationKind kind = MiniMaxH3PresentationKind::IMAGE; + std::vector> frames; + std::vector timestamps; +}; + static inline sd::Tensor apply_token_weights(sd::Tensor hidden_states, const std::vector& weights) { if (hidden_states.empty()) { @@ -102,11 +124,12 @@ static inline sd::Tensor apply_token_weights(sd::Tensor hidden_sta struct ConditionerParams { std::string text; - int clip_skip = -1; - int width = -1; - int height = -1; - bool zero_out_masked = false; - const std::vector>* ref_images = nullptr; // for qwen image edit + int clip_skip = -1; + int width = -1; + int height = -1; + bool zero_out_masked = false; + const std::vector>* ref_images = nullptr; // for qwen image edit + const std::vector* minimax_h3_references = nullptr; RefImageParams ref_image_params; }; @@ -117,6 +140,7 @@ struct Conditioner { virtual SDCondition get_learned_condition(int n_threads, const ConditionerParams& conditioner_params) = 0; virtual void get_param_tensors(std::map& tensors) = 0; + virtual void get_param_tensor_ops(std::map& tensor_ops) {} virtual void set_max_graph_vram_bytes(size_t max_vram_bytes) {} virtual void set_stream_layers_enabled(bool enabled) {} virtual void set_runtime_backends(const std::vector& backends) {} @@ -1664,6 +1688,10 @@ struct AnimaConditioner : public Conditioner { llm->get_param_tensors(tensors, "text_encoders.llm"); } + void get_param_tensor_ops(std::map& tensor_ops) override { + llm->get_param_tensor_ops(tensor_ops); + } + void set_max_graph_vram_bytes(size_t max_vram_bytes) override { llm->set_max_graph_vram_bytes(max_vram_bytes); } @@ -1805,6 +1833,7 @@ struct LLMEmbedder : public Conditioner { sd_version_is_boogu_image(version) || sd_version_is_sefi_image(version) || sd_version_is_krea2(version) || + sd_version_is_minimax_h3(version) || sd_version_is_mage_flow(version)) { arch = LLM::LLMArch::QWEN3_VL; } else if (sd_version_is_z_image(version) || version == VERSION_OVIS_IMAGE || version == VERSION_FLUX2_KLEIN) { @@ -1847,6 +1876,10 @@ struct LLMEmbedder : public Conditioner { } } + void get_param_tensor_ops(std::map& tensor_ops) override { + llm->get_param_tensor_ops(tensor_ops); + } + void set_max_graph_vram_bytes(size_t max_vram_bytes) override { llm->set_max_graph_vram_bytes(max_vram_bytes); if (byt5) { @@ -1983,8 +2016,10 @@ struct LLMEmbedder : public Conditioner { const std::vector>>& image_embeds, const std::set& out_layers, int prompt_template_encode_start_idx, - bool spell_quotes = false, - int max_length = 100000000) { + bool spell_quotes = false, + int max_length = 100000000, + const LLM::DeepStackImageEmbeds& deepstack_image_embeds = {}, + const std::vector& image_grids = {}) { auto tokens_weights_mask = tokenize(prompt, prompt_attn_range, min_length, max_length, spell_quotes); auto& tokens = std::get<0>(tokens_weights_mask); auto& weights = std::get<1>(tokens_weights_mask); @@ -2017,7 +2052,9 @@ struct LLMEmbedder : public Conditioner { false, false, true, - true); + true, + deepstack_image_embeds, + image_grids); GGML_ASSERT(!hidden_states.empty()); hidden_states = apply_token_weights(std::move(hidden_states), weights); GGML_ASSERT(hidden_states.shape()[1] > prompt_template_encode_start_idx); @@ -2099,6 +2136,8 @@ struct LLMEmbedder : public Conditioner { std::vector extra_prompts; std::vector> extra_prompts_attn_range; std::vector>> image_embeds; + LLM::DeepStackImageEmbeds deepstack_image_embeds; + std::vector image_grids; int prompt_template_encode_start_idx = 34; int min_length = 0; // pad tokens int max_length = 100000000; @@ -2109,7 +2148,131 @@ struct LLMEmbedder : public Conditioner { int64_t t0 = ggml_time_ms(); RefImageResizeMode resize_mode = conditioner_params.ref_image_params.vlm_resize_mode; - if (sd_version_is_hunyuan_video(version)) { + if (sd_version_is_minimax_h3(version)) { + prompt_template_encode_start_idx = 0; + out_layers = {50}; + prompt_attn_range = {0, 0}; + + if (llm->enable_vision) { + const std::string placeholder = "<|image_pad|>"; + const int patch_size = llm->config.vision.patch_size; + const int factor = patch_size * llm->config.vision.spatial_merge_size; + + auto resize_for_vision = [&](const sd::Tensor& image) { + int height = static_cast(image.shape()[1]); + int width = static_cast(image.shape()[0]); + int h_bar = std::max(factor, static_cast(std::round(static_cast(height) / factor)) * factor); + int w_bar = std::max(factor, static_cast(std::round(static_cast(width) / factor)) * factor); + resize_image_dims(height, + width, + h_bar, + w_bar, + factor, + 3136, + 12845056, + RefImageResizeMode::AREA); + auto resized = sd::ops::interpolate( + image, + std::vector{w_bar, h_bar, image.shape()[2], image.shape()[3]}); + for (int64_t i = 0; i < resized.numel(); ++i) { + resized[i] = std::clamp(resized[i], 0.f, 1.f) * 2.f - 1.f; + } + return resized; + }; + + auto add_vision_outputs = [&](std::vector> image_outputs, + int grid_h, + int grid_w) { + GGML_ASSERT(image_outputs.size() == 4); + auto image_embed = std::move(image_outputs[0]); + prompt += "<|vision_start|>"; + int image_embed_idx = static_cast(tokenizer->encode(prompt, nullptr).size()); + image_embeds.emplace_back(image_embed_idx, image_embed); + if (deepstack_image_embeds.empty()) { + deepstack_image_embeds.resize(image_outputs.size() - 1); + } + for (size_t layer = 0; layer < deepstack_image_embeds.size(); ++layer) { + deepstack_image_embeds[layer].emplace_back(image_embed_idx, std::move(image_outputs[layer + 1])); + } + image_grids.push_back({image_embed_idx, + static_cast(image_embed.shape()[1]), + grid_h, + grid_w}); + for (int64_t i = 0; i < image_embed.shape()[1]; ++i) { + prompt += placeholder; + } + prompt += "<|vision_end|>"; + }; + + const auto* references = conditioner_params.minimax_h3_references; + if (references != nullptr && !references->empty()) { + int picture_index = 0; + int video_index = 0; + int audio_index = 0; + for (const auto& item : *references) { + if (item.kind == MiniMaxH3PresentationKind::AUDIO) { + prompt += "