diff --git a/.gitmodules b/.gitmodules index 9189d57..2b594d1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "third_party/ggml"] path = third_party/ggml - url = https://github.com/yiming-l21/ggml.git + url = https://github.com/Komorebi623/ggml.git branch = edge-cudnn-sdpa [submodule "third_party/onednn"] path = third_party/onednn diff --git a/README.md b/README.md index 694e4df..9527d9c 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ placement, and backend selection. - Shared **C API, CLI, HTTP server, and Python** interfaces across every family - **Fits large models into limited VRAM** - - **`--auto-fit`** — one flag auto-picks DiT **quantization** (`q8_0`→`q4_K`) *and* per-component placement to meet a hard VRAM budget + - **`--auto-fit`** — one flag auto-picks DiT **quantization** (`q8_0`→`q4_K`) and per-component placement; on single-device CUDA, pairing it with `--max-vram` also enforces an allocation ceiling - **Layered offload** — streams the diffusion transformer **one block at a time** (async double-buffered on CUDA), so 20 GB+ models run on a 24 GB or smaller card - **Per-component offload** (`--dit-offload` / `--text-encoder-offload` / `--vae-offload`) and **VAE tiling** - **`ed-convert`** — offline weight quantization to a **portable pre-quantized GGUF** (skips per-load CPU quantization), with per-tensor dtype rules and activation-calibrated **imatrix** @@ -57,7 +57,7 @@ placement, and backend selection. - **2026-08-14:** 🚀 Added **MiniMax-H3 FL2VA and Ref2VA video+audio generation** with image, video, embedded/paired audio, and mixed references; full or pruned BF16 DiTs, persistent Q8_0 conversion, Q4_K_M weights, and automatic VRAM fitting are supported ([usage and H200 results](docs/minimax-h3.md)). - **2026-08-05:** 🚀 Completed the **RTX 4090 (24 GB) benchmark** — full cross-system speed / VRAM / image-quality across text-to-image, editing, and video ([results](docs/performance-4090.md)). - **2026-07-30:** 🚀 Added **per-component offload** (`--dit-offload` / `--text-encoder-offload` / `--vae-offload`), unifying all offload paths on one semantics. -- **2026-07-29:** 🚀 Added **`--auto-fit`** — one flag picks DiT quantization *and* per-component placement to fit a hard VRAM budget. +- **2026-07-29:** 🚀 Added **`--auto-fit`** — one flag picks DiT quantization and per-component placement; `--auto-fit --max-vram ` enforces the requested single-device CUDA allocation ceiling. - **2026-07-27:** 🚀 Added **few-step distilled** auto-detection (Turbo/Lightning/schnell → 4–8 steps) and optional **SageAttention** for SD3/Wan. - **2026-07-23:** 🚀 Added **`ed-convert`** for offline weight quantization to portable pre-quantized GGUF (with activation-calibrated `--imatrix`). - **2026-07-11:** 🚀 **edge-dit.cpp v0.1.0-alpha** enters **public preview**. @@ -162,8 +162,8 @@ Generation latency is the primary cross-runtime performance metric. MiniMax-H3 has a separate 124-frame H200 benchmark because it generates video and audio rather than one image. In the current resident-component BF16 -comparison, Edge is faster than Diffusers in all four FL2VA and all four Ref2VA -generation paths while using less peak VRAM. FL2VA text-to-video takes +comparison, edge-dit.cpp is faster than Diffusers in all four FL2VA and all four +Ref2VA generation paths while using less peak VRAM. FL2VA text-to-video takes `51.396s` versus `53.986s`; Ref2VA image and mixed-reference generation reach `1.08x` and `1.06x` speedups. Full and pruned BF16 DiTs are supported directly, and either can be converted once to persistent Q8_0 GGUF. `--auto-fit` has also diff --git a/docs/assets/minimax-h3-ref2va-four-image-edge-comfyui-diffusers-demo.mp4 b/docs/assets/minimax-h3-ref2va-four-image-edge-comfyui-diffusers-demo.mp4 new file mode 100644 index 0000000..bde3bf4 Binary files /dev/null and b/docs/assets/minimax-h3-ref2va-four-image-edge-comfyui-diffusers-demo.mp4 differ diff --git a/docs/assets/minimax-h3-ref2va-h200-15s-metrics.json b/docs/assets/minimax-h3-ref2va-h200-15s-metrics.json new file mode 100644 index 0000000..875cac3 --- /dev/null +++ b/docs/assets/minimax-h3-ref2va-h200-15s-metrics.json @@ -0,0 +1,92 @@ +{ + "date": "2026-08-16", + "device": "NVIDIA H200", + "sampling": { + "frames": 362, + "fps": 24, + "steps": 20, + "cfg": 1.0, + "seed": 157368968253448, + "sampler": "res_multistep", + "scheduler": "simple" + }, + "tasks": { + "four_image_action": { + "width": 1280, + "height": 736, + "reference_images": 4, + "frameworks": { + "edge-dit.cpp": { + "end_to_end_seconds": 919.765, + "load_seconds": 17.773, + "conditioning_seconds": 2.641, + "dit_seconds": 839.890, + "video_vae_decode_seconds": 41.842, + "audio_vae_decode_seconds": 0.369, + "save_seconds": 5.519, + "peak_device_mib": 52667 + }, + "ComfyUI": { + "end_to_end_seconds": 1055.354, + "peak_device_mib": 49413, + "memory_sample_interval_seconds": 0.2, + "memory_sample_count": 3321, + "decoded_output_matches_original": true + }, + "Diffusers": { + "end_to_end_seconds": 2343.937, + "load_seconds": 32.260, + "conditioning_seconds": 14.924, + "dit_seconds": 2263.720, + "video_vae_decode_seconds": 20.679, + "audio_vae_decode_seconds": 0.145, + "mux_seconds": 4.699, + "pytorch_max_allocated_mib": 109960, + "pytorch_max_reserved_mib": 110866 + } + } + }, + "two_character_portrait": { + "width": 736, + "height": 1280, + "reference_images": 2, + "frameworks": { + "edge-dit.cpp": { + "end_to_end_seconds": 888.636, + "load_seconds": 17.784, + "conditioning_seconds": 1.168, + "dit_seconds": 811.867, + "video_vae_decode_seconds": 41.871, + "audio_vae_decode_seconds": 0.355, + "save_seconds": 3.737, + "peak_device_mib": 52809 + }, + "ComfyUI": { + "end_to_end_seconds": 1025.864, + "conditioning_node_seconds": 33.140, + "sampler_node_seconds": 965.504, + "video_vae_decode_seconds": 19.947, + "audio_vae_decode_seconds": 0.501, + "save_seconds": 5.927, + "peak_device_mib": 49159 + }, + "Diffusers": { + "end_to_end_seconds": 1625.178, + "load_seconds": 32.838, + "conditioning_seconds": 7.176, + "dit_seconds": 1553.626, + "video_vae_decode_seconds": 20.567, + "audio_vae_decode_seconds": 0.144, + "mux_seconds": 3.481, + "pytorch_max_allocated_mib": 99189, + "pytorch_max_reserved_mib": 108646 + } + } + } + }, + "reference_resize": { + "edge-dit.cpp": "match; no upscaling", + "ComfyUI": "match; no upscaling", + "Diffusers": "official MiniMaxH3ImageReference; 2048px short edge" + } +} diff --git a/docs/assets/minimax-h3-ref2va-two-character-edge-comfyui-diffusers-demo.mp4 b/docs/assets/minimax-h3-ref2va-two-character-edge-comfyui-diffusers-demo.mp4 new file mode 100644 index 0000000..25f35d8 Binary files /dev/null and b/docs/assets/minimax-h3-ref2va-two-character-edge-comfyui-diffusers-demo.mp4 differ diff --git a/docs/cli.md b/docs/cli.md index 24e6ee8..9fa497f 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -337,12 +337,21 @@ On-load weight type selection: --output flux-q4.png ``` +`--type` is a global loading policy: it applies to every eligible tensor in +every loaded component (DiT, text encoder, video VAE, and audio VAE). Biases, +norms, embeddings, protected tensors, and shapes incompatible with a block +quant remain at their stored type. + Supported `--type` values: ```text -f32 f16 bf16 q4_0 q4_1 q5_0 q5_1 q8_0 q2_k q3_k q4_k q5_k q6_k +preserve f32 f16 bf16 q4_0 q4_1 q5_0 q5_1 q8_0 q2_k q3_k q4_k q5_k q6_k ``` +`preserve` is the default and leaves every source tensor at its stored type. +The older value `auto` remains accepted as a compatibility alias for +`preserve`; it does not perform automatic quantization. + > **Qwen-Image models and FP16:** the Qwen-Image family (`qwen-image`, > `qwen-image-edit`, and their distilled/lightning variants) is not supported in > FP16 — its DiT activations exceed FP16's dynamic range and silently saturate, @@ -376,27 +385,36 @@ it off. The offload flags share one semantics — **weights kept on CPU and stag to the GPU per compute** (compute always runs on the GPU): `--offload-to-cpu` offloads the whole model, while `--dit-offload` / `--text-encoder-offload` / `--vae-offload` offload just that one component. `--auto-allocate` places each -component (DiT, text encoder, VAE) under a hard VRAM cap of `min(--max-vram, -free)`, keeping a component resident when it fits and offloading (staging) it -otherwise. +component (DiT, text encoder, VAE) against a planning budget of +`min(--max-vram, free)`, keeping a component resident when it fits and +offloading (staging) it otherwise. By itself this remains a placement-planning +budget. On single-device CUDA, `--auto-fit --max-vram ` additionally installs a guarded +allocation ceiling below the requested value, reserving 1 GiB for external +library workspaces. An allocation that cannot fit is rejected before the +process crosses the requested ceiling. These options are workload dependent. Validate output quality and latency for the exact model and resolution you plan to run. ### Budget-driven placement (`--auto-allocate`) and full auto (`--auto-fit`) -Two levels of automation size a run to a hard VRAM budget instead of tuning +Two levels of automation size a run to a VRAM planning budget instead of tuning `--type` and offload flags by hand: - `--auto-allocate` — you pick the quantization (`--type`); the runtime decides, per component (DiT / text encoder / VAE), what stays resident on the GPU and - what streams from host, so the peak stays under `--max-vram`. + what streams from host. `--max-vram` is not a universal process-level peak clamp. - `--auto-fit` — fully automatic. It **implies `--auto-allocate`** and, in addition, chooses the quantization itself: the DiT is driven down the ladder `q8_0 → q4_k` to the highest level that stays resident within the budget, the - text encoder is set to `q8_0` (near-lossless, halves its footprint), and - placement is decided as above. `--auto-fit` **ignores `--type`** (it owns the - quantization) and logs that it is doing so. + text encoder is lowered to at most `q8_0` (an already smaller quant stays unchanged), and + placement is decided as above. For the text encoder and DiT, this automatic + decision supersedes the global `--type` without ever increasing an already + lower-precision source. The VAEs are not replanned: they still follow + `--type`, where `preserve` means keeping each source tensor's stored type. + With an explicit `--max-vram` on + single-device CUDA, it also enables the hard allocation guard; an intrinsically too-large + graph fails safely instead of exceeding the budget. `--auto-fit` measures each component's real compute-buffer footprint at the requested resolution (`-W`/`-H`, and `--frames` for video) to size the resident @@ -416,7 +434,7 @@ the model's fixed 24 fps to the nearest legal `17k+5` frame count. Keep using mutually exclusive. ```bash -# MiniMax-H3 Q8_0 under a 40 GiB hard placement budget. +# MiniMax-H3 with automatic quantization/placement and a 40 GiB CUDA ceiling. ./build-cuda/bin/ed-cli --video \ --diffusion-model /path/to/minimax_h3_fl2va-diffusers-Q8_0.gguf \ --vae /path/to/minimax_h3_video_vae_fp16.safetensors \ diff --git a/docs/minimax-h3.md b/docs/minimax-h3.md index f910fa6..ab6d883 100644 --- a/docs/minimax-h3.md +++ b/docs/minimax-h3.md @@ -21,10 +21,24 @@ last frame. Ref2VA uses the following options: | Input | CLI option | Behavior | |---|---|---| | Image | `--ref-image ` | Repeatable; presented as `` | +| Image sizing | `--ref-image-size match|max` | `match` only downsizes to the output pixel area; `max` resizes to a 2048px short edge, including upscaling, to match official Diffusers geometry (default) | | Video | `--ref-video ` | Repeatable frame directory or `mp4`/`mov`/`mkv`/`webm`/`avi`; media files require `ffmpeg` | | Paired audio | `--ref-video-audio ` | The Nth WAV is paired with the Nth video and overrides embedded audio | | Additional audio | `--ref-audio ` | Repeatable; requires at least one image or video reference | +Reference-image resize policy changes both conditioning cost and every Ref2VA +Transformer step because the encoded reference latents are appended to the DiT +sequence: + +| Mode | Reference-image geometry | +|---|---| +| `max` (default) | Scales the short edge to 2048px, including upscaling | +| `match` | Never upscales; caps the image at the output pixel area | + +The selected mode and post-resize reference dimensions affect both conditioning +cost and DiT sequence length. Use `match` when smaller reference images should +not be enlarged. + When `--ref-video` points to a media file, the CLI decodes it at 24 fps and automatically extracts an embedded audio track. Explicit paired WAV files map positionally to videos. Additional audio is numbered after paired or embedded @@ -35,11 +49,11 @@ options, and audio-only Ref2VA requests are rejected. ### Downloadable weights -The complete, unpruned checkpoints are recommended for the best quality. Edge -also supports pruned DiT weights in BF16 safetensors format. Both full and -pruned BF16 DiTs can be converted to Q8_0 GGUF with `ed-convert`, and the -resulting Q8_0 DiTs can be loaded directly. Performance and quality results from -pruned and full DiTs are not directly comparable. +The complete, unpruned checkpoints are recommended for the best quality. +edge-dit.cpp also supports pruned DiT weights in BF16 safetensors format. Both +full and pruned BF16 DiTs can be converted to Q8_0 GGUF with `ed-convert`, and +the resulting Q8_0 DiTs can be loaded directly. Performance and quality results +from pruned and full DiTs are not directly comparable. | Precision | Component | File | Repository | |---|---|---|---| @@ -54,6 +68,12 @@ pruned and full DiTs are not directly comparable. | FP16 | Video VAE | `vae/minimax_h3_video_vae_fp16.safetensors` | [`Comfy-Org/MiniMax-H3`](https://huggingface.co/Comfy-Org/MiniMax-H3) | | FP32 | Audio VAE | `vae/minimax_h3_audio_vae_fp32.safetensors` | [`Comfy-Org/MiniMax-H3`](https://huggingface.co/Comfy-Org/MiniMax-H3) | +BF16 and FP16 both use 16 bits per weight, so converting the FP16 video VAE to +BF16 would not reduce its weight memory. With `--type preserve`, `--auto-fit` +therefore preserves the supplied VAE precision and focuses automatic +quantization on Qwen3-VL and the DiT. An explicit `--type` still applies to +eligible VAE tensors. + The official [`MiniMaxAI/MiniMax-H3`](https://huggingface.co/MiniMaxAI/MiniMax-H3) Diffusers shard indexes are also accepted for BF16 transformer loading. Merged Comfy-Org files are usually more convenient for standalone component commands. @@ -76,10 +96,11 @@ hf download Comfy-Org/MiniMax-H3 \ ### Persistent Q8_0 GGUF -Q8_0 benchmark files are offline conversions of the full BF16 DiTs and Qwen3-VL, -not Comfy-Org INT8 ConvRot weights. A pruned BF16 DiT can be converted with the -same command when lower storage and memory usage are preferred. Convert once -with `ed-convert` instead of quantizing during every model load: +Create persistent Q8_0 GGUF files from the BF16 DiTs and Qwen3-VL. The INT8 +ConvRot safetensors use a different representation and are not interchangeable +with GGUF Q8_0. A pruned BF16 DiT can be converted with the same command when +lower storage and memory usage are preferred. Convert once with `ed-convert` +instead of quantizing during every model load: ```bash ed-convert --model models/minimax-h3/diffusion_models/minimax_h3_fl2va_bf16.safetensors \ @@ -129,6 +150,7 @@ point them to the converted GGUF files. ed-cli --video --diffusion-model "$DIT" --llm "$LLM" \ --vae "$VIDEO_VAE" --audio-vae "$AUDIO_VAE" \ --video-duration 5 -W 864 -H 480 --steps 20 --cfg-scale 1 \ + --sampler res_multistep --scheduler simple \ --prompt "A cinematic sunset over layered mountain ridges with quiet natural ambience." \ --video-format mp4 --output t2va.mp4 @@ -136,6 +158,7 @@ ed-cli --video --diffusion-model "$DIT" --llm "$LLM" \ ed-cli --video --diffusion-model "$DIT" --llm "$LLM" \ --vae "$VIDEO_VAE" --audio-vae "$AUDIO_VAE" --image first.png \ --video-duration 5 -W 864 -H 480 --steps 20 --cfg-scale 1 \ + --sampler res_multistep --scheduler simple \ --prompt "Starting from , preserve the scene and add subtle natural motion." \ --video-format mp4 --output i2va.mp4 @@ -147,19 +170,25 @@ ed-cli --video --diffusion-model "$DIT" --llm "$LLM" \ Use the Ref2VA DiT for all commands in this section. +For the recommended MiniMax-H3 sampling path, use `res_multistep` with the +`simple` sigma schedule. The default remains `euler` with `discrete` for +compatibility with existing edge-dit.cpp commands. + ```bash # Image reference ed-cli --video --diffusion-model "$DIT" --llm "$LLM" \ --vae "$VIDEO_VAE" --audio-vae "$AUDIO_VAE" \ --ref-image reference.png --video-duration 5 -W 864 -H 480 --steps 20 \ - --cfg-scale 1 --prompt "Use as the strict visual reference." \ + --cfg-scale 1 --sampler res_multistep --scheduler simple \ + --prompt "Use as the strict visual reference." \ --video-format mp4 --output ref-image.mp4 # MP4 reference; embedded audio is paired automatically when present ed-cli --video --diffusion-model "$DIT" --llm "$LLM" \ --vae "$VIDEO_VAE" --audio-vae "$AUDIO_VAE" \ --ref-video reference.mp4 --video-duration 5 -W 864 -H 480 --steps 20 \ - --cfg-scale 1 --prompt "Preserve