diff --git a/assets/wan/Wan2.2_A14B_vace_r2v.mp4 b/assets/wan/Wan2.2_A14B_vace_r2v.mp4
new file mode 100644
index 00000000..caf19c21
Binary files /dev/null and b/assets/wan/Wan2.2_A14B_vace_r2v.mp4 differ
diff --git a/docs/wan.md b/docs/wan.md
index 13c26a20..1b38bcbc 100644
--- a/docs/wan.md
+++ b/docs/wan.md
@@ -40,6 +40,9 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
- 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
+ - Wan2.2 VACE-Fun A14B
+ - safetensors: https://huggingface.co/alibaba-pai/Wan2.2-VACE-Fun-A14B
+ - gguf: https://huggingface.co/QuantStack/Wan2.2-VACE-Fun-A14B-GGUF/tree/main
- 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
@@ -256,3 +259,25 @@ ffmpeg -i ..\..\ComfyUI\input\post+depth.mp4 -qscale:v 1 -vf fps=8 post+depth\fr
```
+
+### Wan2.2 VACE-Fun A14B
+
+VACE-Fun runs as a MoE pair: `--diffusion-model` takes the low-noise expert and
+`--high-noise-diffusion-model` the high-noise one. Reference-to-video uses `-i`
+for the reference image, same as Wan2.1 VACE.
+
+#### R2V
+
+```
+.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-VACE-Fun-A14B-low-noise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-VACE-Fun-A14B-high-noise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 3.5 --sampling-method euler --steps 10 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 8 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa -i ..\assets\cat_with_sd_cpp_42.png --video-frames 33 --offload-to-cpu
+```
+
+
+
+#### T2V
+
+Same command without `-i` (VACE context is synthesized from an empty control
+video, like Wan2.1 VACE t2v).
+
+> On GPUs with ~12 GB VRAM, VACE also needs `--vae-tiling` — the control-video
+> encode can exceed the budget otherwise.
diff --git a/src/model_io/gguf_io.cpp b/src/model_io/gguf_io.cpp
index 081fcf97..3e574487 100644
--- a/src/model_io/gguf_io.cpp
+++ b/src/model_io/gguf_io.cpp
@@ -47,10 +47,14 @@ bool read_gguf_file(const std::string& file_path,
gguf_context* ctx_gguf_ = nullptr;
ggml_context* ctx_meta_ = nullptr;
- ctx_gguf_ = gguf_init_from_file(file_path.c_str(), {true, &ctx_meta_});
+ GGUFReader gguf_reader;
+ bool probe_ok = gguf_reader.load(file_path);
+
+ if (!probe_ok || !gguf_reader.has_tensors_beyond_ggml_limits()) {
+ ctx_gguf_ = gguf_init_from_file(file_path.c_str(), {true, &ctx_meta_});
+ }
if (!ctx_gguf_) {
- GGUFReader gguf_reader;
- if (!gguf_reader.load(file_path)) {
+ if (!probe_ok && !gguf_reader.load(file_path)) {
set_error(error, "failed to open '" + file_path + "' with GGUFReader");
return false;
}
diff --git a/src/model_io/gguf_reader_ext.h b/src/model_io/gguf_reader_ext.h
index 7b372e4f..b55d49f1 100644
--- a/src/model_io/gguf_reader_ext.h
+++ b/src/model_io/gguf_reader_ext.h
@@ -35,23 +35,72 @@ enum class GGUFMetadataType : uint32_t {
class GGUFReader {
private:
std::vector tensors_;
+ bool has_wide_tensors_ = false;
+ uint64_t remaining_bytes_ = 0;
size_t data_offset_;
size_t alignment_ = 32; // default alignment is 32
template
bool safe_read(std::ifstream& fin, T& value) {
- fin.read(reinterpret_cast(&value), sizeof(T));
- return fin.good();
+ return safe_read(fin, reinterpret_cast(&value), sizeof(T));
}
bool safe_read(std::ifstream& fin, char* buffer, size_t size) {
+ if (size > remaining_bytes_)
+ return false;
fin.read(buffer, size);
- return fin.good();
+ if (!fin.good())
+ return false;
+ remaining_bytes_ -= size;
+ return true;
}
- bool safe_seek(std::ifstream& fin, std::streamoff offset, std::ios::seekdir dir) {
- fin.seekg(offset, dir);
- return fin.good();
+ bool safe_skip(std::ifstream& fin, uint64_t count, uint64_t element_size = 1) {
+ if (count > remaining_bytes_ / element_size)
+ return false;
+ uint64_t size = count * element_size;
+ fin.seekg(static_cast(size), std::ios::cur);
+ if (!fin.good())
+ return false;
+ remaining_bytes_ -= size;
+ return true;
+ }
+
+ bool skip_metadata_values(std::ifstream& fin, GGUFMetadataType type, uint64_t count) {
+ switch (type) {
+ case GGUFMetadataType::UINT8:
+ case GGUFMetadataType::INT8:
+ case GGUFMetadataType::BOOL:
+ return safe_skip(fin, count);
+
+ case GGUFMetadataType::UINT16:
+ case GGUFMetadataType::INT16:
+ return safe_skip(fin, count, 2);
+
+ case GGUFMetadataType::UINT32:
+ case GGUFMetadataType::INT32:
+ case GGUFMetadataType::FLOAT32:
+ return safe_skip(fin, count, 4);
+
+ case GGUFMetadataType::UINT64:
+ case GGUFMetadataType::INT64:
+ case GGUFMetadataType::FLOAT64:
+ return safe_skip(fin, count, 8);
+
+ case GGUFMetadataType::STRING:
+ if (count > remaining_bytes_ / sizeof(uint64_t))
+ return false;
+ for (uint64_t i = 0; i < count; i++) {
+ uint64_t len = 0;
+ if (!safe_read(fin, len) || !safe_skip(fin, len))
+ return false;
+ }
+ return true;
+
+ default:
+ LOG_ERROR("Unknown metadata type=%u", static_cast(type));
+ return false;
+ }
}
bool read_metadata(std::ifstream& fin) {
@@ -84,52 +133,12 @@ private:
return true;
}
- switch (static_cast(type)) {
- case GGUFMetadataType::UINT8:
- case GGUFMetadataType::INT8:
- case GGUFMetadataType::BOOL:
- return safe_seek(fin, 1, std::ios::cur);
-
- case GGUFMetadataType::UINT16:
- case GGUFMetadataType::INT16:
- return safe_seek(fin, 2, std::ios::cur);
-
- case GGUFMetadataType::UINT32:
- case GGUFMetadataType::INT32:
- case GGUFMetadataType::FLOAT32:
- return safe_seek(fin, 4, std::ios::cur);
-
- case GGUFMetadataType::UINT64:
- case GGUFMetadataType::INT64:
- case GGUFMetadataType::FLOAT64:
- return safe_seek(fin, 8, std::ios::cur);
-
- case GGUFMetadataType::STRING: {
- uint64_t len = 0;
- if (!safe_read(fin, len))
- return false;
- return safe_seek(fin, len, std::ios::cur);
- }
-
- case GGUFMetadataType::ARRAY: {
- uint32_t elem_type = 0;
- uint64_t len = 0;
- if (!safe_read(fin, elem_type))
- return false;
- if (!safe_read(fin, len))
- return false;
-
- for (uint64_t i = 0; i < len; i++) {
- if (!read_metadata(fin))
- return false;
- }
- return true;
- }
-
- default:
- LOG_ERROR("Unknown metadata type=%u", type);
+ uint64_t count = 1;
+ if (type == static_cast(GGUFMetadataType::ARRAY)) {
+ if (!safe_read(fin, type) || !safe_read(fin, count))
return false;
}
+ return skip_metadata_values(fin, static_cast(type), count);
}
GGUFTensorInfo read_tensor_info(std::ifstream& fin) {
@@ -154,6 +163,7 @@ private:
}
if (n_dims > GGML_MAX_DIMS) {
+ has_wide_tensors_ = true;
for (uint32_t i = GGML_MAX_DIMS; i < n_dims; i++) {
info.shape[GGML_MAX_DIMS - 1] *= info.shape[i]; // stack to last dim;
}
@@ -174,12 +184,20 @@ private:
public:
bool load(const std::string& file_path) {
- std::ifstream fin(file_path, std::ios::binary);
+ std::ifstream fin(file_path, std::ios::binary | std::ios::ate);
if (!fin) {
LOG_ERROR("failed to open '%s'", file_path.c_str());
return false;
}
+ std::streamoff file_size = fin.tellg();
+ if (file_size < 0)
+ return false;
+ remaining_bytes_ = static_cast(file_size);
+ fin.seekg(0, std::ios::beg);
+ if (!fin.good())
+ return false;
+
// --- Header ---
char magic[4];
if (!safe_read(fin, magic, 4) || strncmp(magic, "GGUF", 4) != 0) {
@@ -228,6 +246,8 @@ public:
}
const std::vector& tensors() const { return tensors_; }
+
+ bool has_tensors_beyond_ggml_limits() const { return has_wide_tensors_; }
size_t data_offset() const { return data_offset_; }
};