Compare commits

...
Author SHA1 Message Date
leejet d763dae3b8 feat: add per-module automatic flash attention selection (#2132) 2026-10-13 22:58:45 +08:00
losewayy 60423ab5f6 fix: reclaim before accepting wrapped Vulkan iGPU memory estimates (#2124) 2026-10-13 21:10:05 +08:00
Robert Oswald 7e3e20a50b ci: add ROCm Dockerfile (#2094) 2026-10-13 21:09:13 +08:00
leejet d637cfb935 feat: add Qwen Image 2.1 Turbo sigma preset (#2130) 2026-10-12 23:09:42 +08:00
Gabriele Lozupone 792bcd344c feat: add MoSR upscaler support (#2128) 2026-10-12 22:38:38 +08:00
leejet 2988060a43 feat: add TAEQI2.1 support for Qwen Image 2.1 (#2123) 2026-10-11 00:01:18 +08:00
Daniele 1b0ba10893 feat: run conditional and unconditional CFG in one batched UNet forward (#2085) 2026-10-10 23:35:58 +08:00
losewayy b7a74e68d7 fix: demote resident params to disk residency when memory reclamation fails (#2119) 2026-10-10 23:15:16 +08:00
Mario Limonciello 05845fde41 ci: fix ROCm ccache build caching and update to ROCm 10.1.0 (#1890) 2026-10-10 22:49:28 +08:00
leejet 0e1d733b7a docs: restrict README UI listings to open-source projects 2026-10-10 22:40:06 +08:00
losewayy ce95c09ce1 fix: halve VAE decode tile size on OOM retry instead of clamping to 256px (#2120) 2026-10-10 22:33:37 +08:00
leejet f89d9b13d7 feat: add Iris-3B text-to-image support (#2117) 2026-10-10 01:39:49 +08:00
leejet f2f168fa70 fix: preserve singleton output dimension when merging LoRA (#2116) 2026-10-10 01:12:03 +08:00
leejet 7867f6da4a fix: bump RPC protocol patch version for rope apply op (#2115) 2026-10-10 00:21:25 +08:00
leejet 228c707fde perf: use ggml rope apply op on supported backends (#2113) 2026-10-09 04:32:36 +08:00
Daniel K. Gralak e16d26a96a fix: align SD3 encoder token chunks before conditioning (#2111) 2026-10-08 23:52:03 +08:00
mikemikimike a4a966990d fix: avoid scanning cwd during server startup (#2086) 2026-10-08 23:40:43 +08:00
leejet a1ded76da5 chore: resolve MSVC warnings in ggml extensions 2026-10-06 20:10:36 +08:00
leejet 8cc5ad8f1c sync: update frontend 2026-10-06 20:04:34 +08:00
leejet 4a892293ac docs: document video reference image support and constraints 2026-10-06 20:03:52 +08:00
leejet fd43b542a6 refactor: escape newlines in prompt logs and clarify source separators (#2106) 2026-10-06 19:51:12 +08:00
losewayy 0963357214 fix: keep MiniMax-H3 VAE weights resident across temporal chunks (#2103) 2026-10-06 19:40:48 +08:00
stduhpfandleejet 1da55ebde8 feat: enable sdapi latent2RGB previews (#2096)
Co-authored-by: leejet <leejet714@gmail.com>
2026-10-06 19:28:30 +08:00
leejet fb8ee71f55 refactor: unify circular and non-circular tiling (#2105) 2026-10-06 19:20:44 +08:00
stduhpfandleejet 96f67fe631 feat: Server: Add support for previews via sdcpp API (#2093)
Co-authored-by: leejet <leejet714@gmail.com>
2026-10-06 19:20:02 +08:00
leejet 203a882a28 refactor: shorten log levels and move source locations to the end (#2104) 2026-10-06 18:26:49 +08:00
mcxu 019bcfc0ae fix: correct non-circular tile placement and blending (#2088) 2026-10-06 18:23:41 +08:00
Wagner Bruna c150a6bafe fix: correct step count adjustment for custom sigmas list (#2084) 2026-10-06 17:55:00 +08:00
Wagner Brunaandleejet f4b5326da7 feat: support for merging LoRA weights on model conversion (#2079)
Co-authored-by: leejet <leejet714@gmail.com>
2026-10-06 17:50:16 +08:00
Aleksei Pakhomov bbdcad48dd feat: add Z-Image L2P support (#2075) 2026-10-06 17:23:21 +08:00
losewayyandleejet b6947b8bdc fix: avoid f16 overflow in Z-Image quantized matmuls on CUDA (#2074)
Co-authored-by: leejet <leejet714@gmail.com>
2026-10-06 17:19:56 +08:00
Daniele 9db0db6570 perf: avoid redundant permute+cont in non-interleaved RoPE (#2102) 2026-10-06 16:54:55 +08:00
Harkirat Gill d4bfcd5d78 ci: bundle HIP runtime libraries with Windows ROCm release (#2090) 2026-10-06 16:42:02 +08:00
leejet 3f8527a46c feat: enable hip INT8 tensorwise matmul and convrot (#2071) 2026-09-27 23:24:07 +08:00
leejet 42ab1c12f1 fix: keep scaled INT8 convrot matmuls on Vulkan (#2070) 2026-09-27 20:43:06 +08:00
leejet 27f7c430c0 fix: update ggml to fix INT8 convrot backend fallback (#2069) 2026-09-27 20:42:31 +08:00
leejet ede32a621e fix: load INT8 convrot LLM embeddings correctly (#2067) 2026-09-27 19:46:21 +08:00
leejet 47e83d7136 sync: update ggml 2026-09-27 18:40:09 +08:00
leejet f8890b9db3 feat: add Ming-Image Design support (#2063) 2026-09-27 17:24:13 +08:00
akleine 61a83b85cc docs: update hugging face links for MageFlow diffusion and VAE models (#2065) 2026-09-27 17:22:03 +08:00
yi111 9947eebec8 fix: handle filesystem errors during lora and upscaler cache scan (#2064) 2026-09-27 17:21:19 +08:00
losewayyandleejet 168f7b88b9 fix: improve GGUF metadata parsing and reader selection (#2062)
Co-authored-by: leejet <leejet714@gmail.com>
2026-09-27 16:24:07 +08:00
leejet 2f886889e6 refactor: align PixArt weights with upstream layout (#2061) 2026-09-26 02:03:05 +08:00
leejet 19bbbca1c7 refactor: define VAE tile dimensions in image pixels (#2059) 2026-09-25 18:20:32 +08:00
losewayy 39ada0863b feat: add PixArt model family support (#2047) 2026-09-25 18:18:55 +08:00
leejet 4c3cf7543d fix: load safetensors index shards without recursion (#2058) 2026-09-25 16:40:20 +08:00
leejet 510bccf330 fix: map Qwen Image 2.1 LoRAs to fused MLP weights (#2057) 2026-09-25 16:30:34 +08:00
yi111 5e7b29131e fix: keep the server frontend install out of a parent pnpm workspace (#2056) 2026-09-25 16:11:21 +08:00
leejet 0a9340c599 fix: scale Qwen Image 2.1 VAE convolutions (#2054) 2026-09-25 15:52:35 +08:00
leejet b167b942f7 fix: align Qwen Image 2.1 flow schedule with official defaults (#2048) 2026-09-25 02:43:10 +08:00
leejet 1a2330de68 fix: reserve 128 MiB headroom when selecting monolithic execution (#2046) 2026-09-25 01:53:11 +08:00
leejet 740c7ae193 feat: add configurable Qwen cache types and early cache scheduling (#2045) 2026-09-25 01:51:01 +08:00
Nick Beerbower 4dfe8f5d45 feat: add a stand-alone upscale endpoint to the server (#2026) 2026-09-25 01:13:23 +08:00
stduhpf caa111adf3 feat: optimize cfg special cases with guidance schdeule (#2033) 2026-09-25 01:10:22 +08:00
leejet 88411ef1e0 refactor: centralize circular RoPE and extend image model support (#2039) 2026-09-24 01:45:14 +08:00
Dong Wang 500ef5fa7c fix: map mmapped weights through Metal buffers instead of CPU buffers (#2037) 2026-09-24 00:53:49 +08:00
leejet 2a4ebba818 ci: automatically close PRs from organization-owned forks 2026-09-24 00:40:42 +08:00
Dong Wang 70c1dbc01e perf: run one-frame Wan VAE convolutions as 2D convolutions (#2038) 2026-09-24 00:23:31 +08:00
assouan 36746936c0 fix: add graph cuts for MiniMax-H3 text conditioning (#1900) 2026-09-24 00:03:29 +08:00
leejet 2dc7f5408a feat: add Qwen Image 2.1 prefix KV cache (#2035) 2026-09-23 23:08:21 +08:00
leejet e6281b6318 feat: add configurable conditioning cache for all models (#2034) 2026-09-23 22:25:42 +08:00
stduhpf 241518b35d feat: add latent2rgba preview for Qwen-Image 2.1 (#2032) 2026-09-23 22:16:55 +08:00
leejet c92d73c408 fix: preserve alpha when upscaling RGBA images with ESRGAN (#2029) 2026-09-23 02:23:10 +08:00
leejet 28b454bda1 feat: add configurable image input preprocessing (#2028) 2026-09-23 02:12:49 +08:00
LED-M 2bb72947cb perf: cache MiniMax H3 text conditioning (#1966) 2026-09-22 22:19:39 +08:00
leejet ac45422a05 fix: honor reference image resize settings in OpenAI edits (#2025) 2026-09-22 22:02:21 +08:00
Weiqi Gao e112ab5a50 fix: add alpha channel input for Qwen Image 2.1 and relative docs (#2021) 2026-09-22 21:45:06 +08:00
mikemikimike e01206574b fix: preserve reference image dimensions in server requests (#2007) 2026-09-22 21:34:29 +08:00
leejet 6dcb5bbd42 fix: handle GPU memory reports and LLM encoding failures (#2020) 2026-09-22 00:28:52 +08:00
leejet 97d932b8f8 fix: restrict VAE tiling retries to allocation failures (#2019) 2026-09-21 23:40:05 +08:00
Aziz Muminov 78557f88d9 fix: honor reference image resize opt-out in server requests (#2011) 2026-09-21 23:39:24 +08:00
leejet 2726dd35c2 fix: update ggml to prevent permute metadata truncation (#2018) 2026-09-21 22:19:27 +08:00
leejet 74988b290e fix: reject video models in image generation (#2017) 2026-09-21 21:48:42 +08:00
leejet c678dfe704 docs: update CONTRIBUTING.md 2026-09-21 01:30:25 +08:00
leejet b56c68617d fix: avoid narrowing conversion in SigVQ patch embedding and format code 2026-09-21 01:27:31 +08:00
leejet 187b2561ea feat: add native CUDA SageAttention support (#2005) 2026-09-21 01:16:59 +08:00
fszontaghandleejet 15f335daa5 feat: add LLaDA-Image support (#1968)
Co-authored-by: leejet <leejet714@gmail.com>
2026-09-21 01:14:20 +08:00
Wagner Bruna b8248a869c fix: avoid passing ggml logs as format strings (#2002) 2026-09-21 00:50:52 +08:00
Wagner Bruna 008ca5b492 feat: restore legacy fp8 handling when building with upstream ggml (#2001) 2026-09-21 00:48:30 +08:00
leejet 137f7409bb feat: add Qwen Image 2.1 support (#1994) 2026-09-20 22:51:21 +08:00
leejet 1330cebae8 feat: support building with upstream ggml (#1999) 2026-09-19 22:25:01 +08:00
leejet 17860c0e45 perf: parallelize host tensor elementwise and broadcast ops (#1998) 2026-09-19 21:46:16 +08:00
leejet 275ab58e01 perf: reduce CPU overhead in graph execution and sampling (#1997) 2026-09-19 18:32:58 +08:00
Fabrice Aneche d32b4e893b fix: prevent clip_preprocess center crop from exceeding the resized image (#1995) 2026-09-19 18:08:34 +08:00
leejet 9982c9caae fix: propagate CUDA driver dependency to shared library consumers 2026-09-19 18:04:59 +08:00
leejet 3e037a81e4 perf: accelerate VAE direct 3D convolutions (#1996) 2026-09-19 17:50:01 +08:00
leejet 2ea8aff7ef perf: update ggml for faster direct convolutions (#1993) 2026-09-19 00:32:24 +08:00
leejet adcac69650 perf: pad small attention heads to 64 for MMA Flash Attention (#1992) 2026-09-18 23:59:58 +08:00
188 changed files with 12270 additions and 2064 deletions
+28 -6
View File
@@ -451,8 +451,8 @@ jobs:
runs-on: windows-2022
env:
ROCM_VERSION: "7.14.0"
GPU_TARGETS: "gfx1010;gfx1011;gfx1012;gfx1030;gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036;gfx1100;gfx1101;gfx1102;gfx1103;gfx1150;gfx1151;gfx1152;gfx1153;gfx1200;gfx1201"
ROCM_VERSION: "10.1.0"
gpu_targets: "gfx1010;gfx1011;gfx1012;gfx1030;gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036;gfx1100;gfx1101;gfx1102;gfx1103;gfx1150;gfx1151;gfx1152;gfx1153;gfx1200;gfx1201"
steps:
- uses: actions/checkout@v3
@@ -498,7 +498,7 @@ jobs:
write-host "Installing ROCm wheels for multi-arch support"
# Install ROCm wheels for multi-arch support (this may take several minutes)
python -m pip install --index-url https://repo.amd.com/rocm/whl-multi-arch/ "rocm[libraries,devel]==${{env.ROCM_VERSION}}"
python -m pip install --index-url https://stable.repo.amd.com/rocm/whl-next/ "rocm[libraries,devel]==${{env.ROCM_VERSION}}"
# Pre-expand the devel tree so it is included in the cache
write-host "Initializing ROCm devel tree"
@@ -560,6 +560,18 @@ jobs:
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
uses: pr-mpt/actions-commit-hash@v2
- name: Bundle HIP runtime libraries
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
run: |
$ErrorActionPreference = "Stop"
$binPath = (rocm-sdk path --bin).Trim()
if (-not $binPath) { throw "rocm-sdk path --bin returned empty" }
foreach ($dll in @("amdhip64_7.dll", "rocm_kpack.dll", "amd_comgr.dll")) {
$f = Get-ChildItem -Path $binPath -Filter $dll -ErrorAction SilentlyContinue
if (-not $f) { throw "no match for $dll in $binPath" }
Copy-Item $f.FullName -Destination .\build\bin\ -Force
}
- name: Pack artifacts
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
run: |
@@ -582,9 +594,9 @@ jobs:
strategy:
matrix:
include:
- ROCM_VERSION: "7.14.0"
- ROCM_VERSION: "10.1.0"
gpu_targets: "gfx900;gfx906;gfx908;gfx90a;gfx90c;gfx942;gfx950;gfx1010;gfx1011;gfx1012;gfx1030;gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036;gfx1100;gfx1101;gfx1102;gfx1103;gfx1150;gfx1151;gfx1152;gfx1153;gfx1200;gfx1201"
build: x64
build: 'x64'
steps:
- name: Clone
@@ -598,6 +610,16 @@ jobs:
with:
key: ubuntu-rocm-cmake-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
evict-old-files: 1d
max-size: "2G"
- name: Tune ccache for reinstalled ROCm toolchain
run: |
# ROCm is pip-installed fresh each run, so the clang binary's mtime
# changes every time. With the default compiler_check=mtime that
# invalidates the cache; hash compiler contents instead so warm
# builds hit.
ccache --set-config=compiler_check=content
ccache --set-config=sloppiness=time_macros,include_file_mtime,include_file_ctime
- name: Dependencies
id: depends
@@ -634,7 +656,7 @@ jobs:
# libraries = HIP runtime and CMake configs needed for linking
# devel = compilers, headers, static libs
python -m pip install --upgrade pip
python -m pip install --index-url https://repo.amd.com/rocm/whl-multi-arch/ "rocm[libraries,devel]==${{matrix.ROCM_VERSION}}"
python -m pip install --index-url https://stable.repo.amd.com/rocm/whl-next/ "rocm[libraries,devel]==${{matrix.ROCM_VERSION}}"
# Get ROCm installation paths using the rocm-sdk CLI tool
ROCM_PATH=$(rocm-sdk path --root)
@@ -0,0 +1,61 @@
name: Close PRs from organization forks
on:
pull_request_target:
types: [opened, reopened]
permissions:
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: false
jobs:
close-organization-fork-pr:
if: >-
github.event.pull_request.head.repo.owner.type == 'Organization' &&
github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Explain the contribution policy and close the PR
uses: actions/github-script@v9
with:
script: |
const { data: pr } = await github.rest.pulls.get({
...context.repo,
pull_number: context.issue.number,
});
const headRepo = pr.head.repo;
if (pr.state !== 'open' || !headRepo ||
headRepo.id === pr.base.repo.id || headRepo.owner.type !== 'Organization') {
return;
}
const marker = '<!-- organization-fork-policy -->';
const comments = await github.paginate(github.rest.issues.listComments, {
...context.repo,
issue_number: pr.number,
per_page: 100,
});
const alreadyExplained = comments.some(comment =>
comment.user?.login === 'github-actions[bot]' && comment.body?.includes(marker));
if (!alreadyExplained) {
await github.rest.issues.createComment({
...context.repo,
issue_number: pr.number,
body: [
marker,
'This repository requires contributions from forks to use a personal fork with **Allow edits from maintainers** enabled.',
'GitHub does not support this option for organization-owned forks, so this PR is being closed automatically.',
'Please open a new PR from a fork in your personal GitHub account and enable **Allow edits from maintainers** so maintainers can help update the branch.',
'See [the GitHub documentation](https://docs.github.com/en/pull-requests/how-tos/work-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork).',
].join('\n\n'),
});
}
await github.rest.pulls.update({
...context.repo,
pull_number: pr.number,
state: 'closed',
});
+21 -12
View File
@@ -95,6 +95,8 @@ option(SD_MUSA "sd: musa backend" OFF)
option(SD_BUILD_SHARED_LIBS "sd: build shared libs" OFF)
option(SD_BUILD_SHARED_GGML_LIB "sd: build ggml as a separate shared lib" OFF)
option(SD_USE_SYSTEM_GGML "sd: use system-installed GGML library" OFF)
option(SD_USE_UPSTREAM_GGML "sd: build with upstream GGML instead of the patched GGML extensions" OFF)
set(SD_GGML_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ggml" CACHE PATH "sd: ggml source directory (also supplies private headers for system ggml)")
#option(SD_BUILD_SERVER "sd: build server example" ON)
set(CMAKE_C_STANDARD 11)
@@ -223,6 +225,8 @@ file(GLOB SD_LIB_SOURCES CONFIGURE_DEPENDS
"src/core/*.h"
"src/core/*.cpp"
"src/core/*.hpp"
"src/core/probe/*.h"
"src/core/probe/*.cpp"
"src/extensions/*.h"
"src/extensions/*.cpp"
"src/extensions/*.hpp"
@@ -325,24 +329,29 @@ if (NOT SD_USE_SYSTEM_GGML)
endif()
# deps
# Only add ggml if it hasn't been added yet
if (NOT TARGET ggml)
if (SD_USE_SYSTEM_GGML)
find_package(ggml REQUIRED)
if (NOT ggml_FOUND)
message(FATAL_ERROR "System-installed GGML library not found.")
endif()
add_library(ggml ALIAS ggml::ggml)
else()
add_subdirectory(ggml)
endif()
endif()
include(cmake/ggml.cmake)
add_subdirectory(thirdparty)
target_sources(${SD_LIB} PRIVATE $<TARGET_OBJECTS:zip>)
target_link_libraries(${SD_LIB} PUBLIC ggml)
find_package(Threads REQUIRED)
target_link_libraries(${SD_LIB} PRIVATE Threads::Threads)
target_link_libraries(${SD_LIB} PRIVATE onig sd-utf8proc)
if (SD_CUDA)
find_package(CUDAToolkit REQUIRED)
# Keep the driver stub on downstream link lines when no driver is installed.
target_link_libraries(${SD_LIB} PUBLIC CUDA::cuda_driver)
set_property(SOURCE src/core/ggml_extend_backend.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_USE_CUDA)
endif()
if (SD_HIPBLAS)
find_package(hip REQUIRED CONFIG HINTS "$ENV{ROCM_PATH}" /opt/rocm)
target_link_libraries(${SD_LIB} PRIVATE hip::host)
set_property(SOURCE src/core/ggml_extend_backend.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_USE_HIP)
if (GGML_HIP_MMQ_MFMA)
set_property(SOURCE src/core/ggml_extend_backend.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_HIP_MFMA)
endif()
endif()
target_include_directories(${SD_LIB} PUBLIC . src include)
target_include_directories(${SD_LIB} PRIVATE src/core)
target_include_directories(${SD_LIB} PUBLIC . thirdparty)
+11 -2
View File
@@ -12,8 +12,14 @@ If you want to update a third-party dependency, please open an issue first inste
## Pull Requests
When contributing from a fork, use a fork under your personal GitHub account and enable **Allow edits from maintainers**. This lets maintainers make follow-up fixes directly on the PR branch.
PRs from organization-owned forks are automatically closed when opened or reopened because GitHub does not support this maintainer-edit option for those forks. Submit the changes from a personal fork instead. See [GitHub's documentation](https://docs.github.com/en/pull-requests/how-tos/work-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork).
Keep each PR focused on one clear change. Large or overly complex PRs are harder to review and may not be merged.
Do not include test code or test scripts in commits or PRs. Keep them local and report verification results in the PR description.
Follow Conventional Commit-style subjects seen in history: `feat:`, `fix:`, `refactor:`, `ci:`, `docs:`, `chore:`. Keep subjects imperative and scoped.
PRs should include:
@@ -23,6 +29,10 @@ PRs should include:
- Linked issue/PR context when applicable.
- Screenshots or sample outputs for UI/visual behavior changes.
## README UI Listings
The [UIs list in the README](README.md#uis) only includes open-source projects that use `stable-diffusion.cpp` as a backend. Do not submit PRs to add closed-source UIs.
## Code Style
Format code according to the repository style before submitting changes.
@@ -35,12 +45,11 @@ Naming conventions:
- In `PascalCase` names, preserve common abbreviations in uppercase, for example `SD`, `API`, `HTTP`, `JSON`, `RGB`, `VAE`, `TAE`, `LoRA`, and `WebP`.
- Use `snake_case` for functions, methods, variables, and file names unless an existing API requires a different style.
- Use a trailing underscore for private data member names, for example `hidden_size_` or `tokenizer_`.
- Use `.h` for C and C++ header files. Do not introduce new `.hpp` headers.
- Use `.hpp` for model headers under `src/model/`, including new model headers. Do not rename these headers to `.h`. Use `.h` for other C and C++ header files.
- Use macro-based header include guards instead of `#pragma once`.
- Format header include guards as `__SD_{PATH}__`, where `{PATH}` is the header path in uppercase snake case without the file extension. For example, `src/sample.h` should use `__SD_SAMPLE_H__`.
- Do not introduce anonymous namespaces in new or modified code; prefer `static` file-local functions/variables or an explicit named namespace when scoping is needed.
- In `class`/`struct` definitions, place data members before member functions unless an existing type already clearly follows a different pattern.
- Keep `test_*.cpp` / `test_*.py` naming for tests.
Some older code in the project may not fully follow the current conventions. Please do not submit PRs that only rewrite existing code to match style rules.
+8 -1
View File
@@ -15,6 +15,7 @@ API and command-line option may change frequently.***
## 🔥Important News
* **2026/09/20** 🚀 stable-diffusion.cpp adds **Day-0 support for Qwen-Image-2.1**
* **2026/08/20** 🚀 stable-diffusion.cpp now supports **LTX-2.5**
* **2026/08/04** 🚀 stable-diffusion.cpp adds **Day-1 support for MiniMax-H3**
* **2026/06/25** 🚀 stable-diffusion.cpp now supports **Krea2**
@@ -47,9 +48,11 @@ API and command-line option may change frequently.***
- [Chroma](./docs/chroma.md)
- [Chroma1-Radiance](./docs/chroma_radiance.md)
- [Qwen Image](./docs/qwen_image.md)
- [Qwen Image 2.1](./docs/qwen_image_2.1.md)
- [PiD](./docs/pid.md)
- [LongCat Image](./docs/longcat_image.md)
- [Z-Image](./docs/z_image.md)
- [Z-Image L2P](./docs/z_image_l2p.md)
- [MiniT2I](./docs/minit2i.md)
- [SenseNova U1.5](./docs/sensenova_u1.md)
- [Ovis-Image](./docs/ovis_image.md)
@@ -61,12 +64,17 @@ API and command-line option may change frequently.***
- [SeFi-Image](./docs/sefi_image.md)
- [HiDream-O1-Image](./docs/hidream_o1_image.md)
- [Ideogram4](./docs/ideogram4.md)
- [LLaDA-Image](./docs/llada_image.md)
- [Ming-Image Design](./docs/ming_image.md)
- [PixArt](./docs/pixart.md)
- [Iris-3B](./docs/iris.md)
- [Image Edit Models](./docs/edit.md)
- [FLUX.1-Kontext-dev](./docs/kontext.md)
- [Qwen Image Edit series](./docs/qwen_image_edit.md)
- [LongCat Image Edit](./docs/longcat_image.md)
- [Boogu Image Edit](./docs/boogu_image.md)
- [Mage-Flow-Edit](./docs/mage_flow.md#image-editing)
- [LLaDA-Image Edit](./docs/llada_image.md#image-editing)
- Video Models
- [Wan2.1/Wan2.2](./docs/wan.md)
- [MiniMax-H3](./docs/minimax_h3.md)
@@ -175,7 +183,6 @@ These projects wrap `stable-diffusion.cpp` for easier use in other languages/fra
These projects use `stable-diffusion.cpp` as a backend for their image generation.
- [GIMP Plugins](https://github.com/themanyone/gimp-plugins)
- [Jellybox](https://jellybox.com)
- [Stable Diffusion GUI](https://github.com/fszontagh/sd.cpp.gui.wx)
- [Stable Diffusion CLI-GUI](https://github.com/piallai/stable-diffusion.cpp)
- [Local Diffusion](https://github.com/rmatif/Local-Diffusion)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 KiB

Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
if(NOT TARGET ggml AND NOT TARGET ggml::ggml)
if(SD_USE_SYSTEM_GGML)
find_package(ggml REQUIRED)
else()
add_subdirectory("${SD_GGML_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/ggml")
endif()
endif()
if(NOT TARGET ggml)
add_library(ggml ALIAS ggml::ggml)
endif()
get_target_property(sd_ggml_imported ggml IMPORTED)
if(sd_ggml_imported)
set(sd_ggml_private_include "${SD_GGML_SOURCE_DIR}/src")
else()
get_target_property(sd_ggml_private_include ggml SOURCE_DIR)
endif()
if(NOT EXISTS "${sd_ggml_private_include}/ggml-impl.h")
message(FATAL_ERROR "Set SD_GGML_SOURCE_DIR to the source tree matching the selected ggml library (ggml-impl.h is required).")
endif()
target_include_directories(${SD_LIB} PRIVATE "${sd_ggml_private_include}")
set_property(TARGET ${SD_LIB} PROPERTY SD_GGML_PRIVATE_INCLUDE_DIR "${sd_ggml_private_include}")
if(SD_USE_UPSTREAM_GGML)
target_compile_definitions(${SD_LIB} PUBLIC SD_USE_UPSTREAM_GGML)
message(WARNING "Using upstream GGML: INT8 tensorwise/convrot is disabled and FP8 weights are converted to F16 at load time. Some operators may be unsupported and performance may be lower than with patched GGML.")
endif()
+9 -1
View File
@@ -10,6 +10,10 @@ set(SD_BIN_DIR "@PACKAGE_SD_BIN_INSTALL_DIR@")
include(CMakeFindDependencyMacro)
find_dependency(ggml REQUIRED HINTS "${SD_LIB_DIR}/cmake")
find_dependency(Threads REQUIRED)
if(@SD_CUDA@)
find_dependency(CUDAToolkit REQUIRED)
endif()
if(NOT TARGET stable-diffusion)
find_library(stable-diffusion_LIBRARY stable-diffusion
@@ -22,12 +26,16 @@ if(NOT TARGET stable-diffusion)
set_target_properties(stable-diffusion
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${SD_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "ggml::ggml"
INTERFACE_LINK_LIBRARIES "ggml::ggml;Threads::Threads"
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${stable-diffusion_LIBRARY}"
INTERFACE_COMPILE_FEATURES "c_std_11;cxx_std_17"
POSITION_INDEPENDENT_CODE ON)
if(@SD_CUDA@)
set_property(TARGET stable-diffusion APPEND PROPERTY INTERFACE_LINK_LIBRARIES CUDA::cuda_driver)
endif()
if(SD_SHARED_LIB)
target_compile_definitions(stable-diffusion
INTERFACE SD_BUILD_SHARED_LIB)
+1 -1
View File
@@ -7,5 +7,5 @@ Name: stable-diffusion
Description: Diffusion model(SD,Flux,Wan,Qwen Image,Z-Image,...) inference in pure C/C++
Version: @SDCPP_BUILD_VERSION@
Libs: -L${libdir} -lstable-diffusion
Libs.private: -lggml -lggml-base
Libs.private: -lggml -lggml-base @CMAKE_THREAD_LIBS_INIT@
Cflags: -I${includedir}
+2
View File
@@ -40,4 +40,6 @@ RUN printf '#!/bin/sh\nexec /sd.cpp/bin/sd-cli "$@"\n' > /sd-cli && \
printf '#!/bin/sh\nexec /sd.cpp/bin/sd-server "$@"\n' > /sd-server && \
chmod +x /sd-cli /sd-server
WORKDIR /sd.cpp
ENTRYPOINT [ "/sd-cli" ]
+2
View File
@@ -57,4 +57,6 @@ RUN printf '#!/bin/sh\nexec /sd.cpp/bin/sd-cli "$@"\n' > /sd-cli && \
printf '#!/bin/sh\nexec /sd.cpp/bin/sd-server "$@"\n' > /sd-server && \
chmod +x /sd-cli /sd-server
WORKDIR /sd.cpp
ENTRYPOINT [ "/sd-cli" ]
+2
View File
@@ -42,4 +42,6 @@ RUN printf '#!/bin/sh\nexec /sd.cpp/bin/sd-cli "$@"\n' > /sd-cli && \
printf '#!/bin/sh\nexec /sd.cpp/bin/sd-server "$@"\n' > /sd-server && \
chmod +x /sd-cli /sd-server
WORKDIR /sd.cpp
ENTRYPOINT [ "/sd-cli" ]
+50
View File
@@ -0,0 +1,50 @@
FROM rocm/dev-ubuntu-24.04 AS build
# sd-server embeds the web UI at build time, so the build image needs Node/pnpm.
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential git cmake ninja-build ccache ca-certificates curl gnupg && \
mkdir -p /etc/apt/keyrings && \
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key -o /tmp/nodesource-repo.gpg.key && \
gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg /tmp/nodesource-repo.gpg.key && \
rm /tmp/nodesource-repo.gpg.key && \
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" > /etc/apt/sources.list.d/nodesource.list && \
apt-get update && apt-get install -y --no-install-recommends nodejs && \
npm install -g pnpm@10.15.1 && \
apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /sd.cpp
# ROCm's clang is not on PATH in Docker RUN shells (profile scripts are not loaded).
ENV PATH="/opt/rocm/llvm/bin:$PATH"
COPY . .
ARG AMDGPU_TARGETS=""
RUN cmake . -B ./build -G Ninja \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DSD_HIPBLAS=ON \
-DGGML_NATIVE=OFF \
-DAMDGPU_TARGETS="${AMDGPU_TARGETS}" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
RUN cmake --build ./build --config Release --parallel
FROM rocm/dev-ubuntu-24.04 AS runtime
# ROCm clang links against its own OpenMP runtime in /opt/rocm/llvm/lib,
# and the ROCm runtime libs (hipblas, rocblas, amdhip64) live in /opt/rocm/lib.
ENV LD_LIBRARY_PATH="/opt/rocm/llvm/lib:/opt/rocm/lib:$LD_LIBRARY_PATH"
RUN apt-get update && \
apt-get install --yes --no-install-recommends libgomp1 && \
apt-get clean && rm -rf /var/lib/apt/lists/*
COPY --from=build /sd.cpp/build/bin /sd.cpp/bin
RUN printf '#!/bin/sh\nexec /sd.cpp/bin/sd-cli "$@"\n' > /sd-cli && \
printf '#!/bin/sh\nexec /sd.cpp/bin/sd-server "$@"\n' > /sd-server && \
chmod +x /sd-cli /sd-server
ENTRYPOINT [ "/sd-cli" ]
+2
View File
@@ -29,4 +29,6 @@ FROM intel/oneapi-basekit:${SYCL_VERSION}-devel-ubuntu24.04 AS runtime
COPY --from=build /sd.cpp/build/bin/sd-cli /sd-cli
COPY --from=build /sd.cpp/build/bin/sd-server /sd-server
WORKDIR /sd.cpp
ENTRYPOINT [ "/sd-cli" ]
+2
View File
@@ -41,4 +41,6 @@ RUN printf '#!/bin/sh\nexec /sd.cpp/bin/sd-cli "$@"\n' > /sd-cli && \
printf '#!/bin/sh\nexec /sd.cpp/bin/sd-server "$@"\n' > /sd-server && \
chmod +x /sd-cli /sd-server
WORKDIR /sd.cpp
ENTRYPOINT [ "/sd-cli" ]
+1 -1
View File
@@ -15,7 +15,7 @@
## Examples
```sh
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\anima-preview.safetensors --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_3_06b_base.safetensors -p "a lovely cat holding a sign says 'anima.cpp'" --cfg-scale 6.0 --sampling-method euler -v --offload-to-cpu --diffusion-fa
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\anima-preview.safetensors --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_3_06b_base.safetensors -p "a lovely cat holding a sign says 'anima.cpp'" --cfg-scale 6.0 --sampling-method euler -v --offload-to-cpu
```
<img alt="anima image example" src="../assets/anima/example.png" />
+3 -3
View File
@@ -51,7 +51,7 @@ SD 1.5 defaults (euler_a, low CFG) produces noise-like output.
.\bin\Release\sd-cli.exe -M vid_gen \
--model ..\models\checkpoints\realisticVisionV60B1.safetensors \
--motion-module ..\models\animatediff\mm_sd15_v3.safetensors \
--offload-to-cpu --diffusion-fa \
--offload-to-cpu \
-p "a red apple on a wooden table" \
--cfg-scale 8.0 --sampling-method euler --scheduler discrete \
-H 512 -W 512 --video-frames 8 --fps 8 --steps 20 -s 42 \
@@ -66,7 +66,7 @@ use layer streaming:
.\bin\Release\sd-cli.exe -M vid_gen \
--model ..\models\checkpoints\realisticVisionV60B1.safetensors \
--motion-module ..\models\animatediff\mm_sd15_v3.safetensors \
--offload-to-cpu --diffusion-fa \
--offload-to-cpu \
-p "photo of coastline, rocks, storm weather, wind, waves, lightning" \
--cfg-scale 8.0 --sampling-method euler --scheduler discrete \
-H 384 -W 384 --video-frames 16 --fps 8 --steps 20 -s 42 \
@@ -79,7 +79,7 @@ Low-VRAM streaming (verified with a 2 GiB cap on RTX 3060):
.\bin\Release\sd-cli.exe -M vid_gen \
--model ..\models\checkpoints\realisticVisionV60B1.safetensors \
--motion-module ..\models\animatediff\mm_sd15_v3.safetensors \
--max-vram 2.0 --diffusion-fa \
--max-vram 2.0 \
-p "photo of coastline, rocks, storm weather, wind, waves, lightning" \
--cfg-scale 8.0 --sampling-method euler --scheduler discrete \
-H 384 -W 384 --video-frames 8 --fps 8 --steps 20 -s 42 \
+81 -2
View File
@@ -156,8 +156,14 @@ the runner's graph-cut capacity checks.
Runtime capacity checks also leave 512 MiB of currently free device memory for
backend scratch buffers and pipelines, including with explicit backend assignments.
They cap stale free-memory reports by the device's total memory minus tracked
resident allocations and reject reports that exceed the device's total memory.
They cap free-memory reports by the device's total memory minus tracked
resident allocations. Vulkan reports exceeding total memory are rejected because
its heap-budget subtraction can underflow. Other backends use the cap instead of
treating such reports as zero free memory. Failed checks log the reported free and
total memory alongside tracked weight and runtime allocations.
With `--mmap`, device-backed mappings count toward these budgets at their full
mapped-file size, once per device buffer even when multiple parameter blocks
share it. Mappings retained in the loader cache continue to count.
Components are considered in `diffusion`, `te`, `vae` order so that repeatedly
used diffusion weights have priority. Each component's weights use the first
@@ -209,6 +215,79 @@ If a VAE decode fails, decoding retries with spatial tiling even when `--auto-fi
is off; supported video decoders try temporal tiling first and can then add
spatial tiling. Spatial retries use half-size tiles along each latent dimension.
## Flash attention (`--flash-attn on|off|auto`)
`--flash-attn` defaults to `auto`. Selection happens while creating the model
context, before inference, and is cached once per compute backend in that context.
Parameter-storage backends are not benchmarked.
The option also accepts module assignments, using the same module aliases as
`--backend`:
```shell
sd-cli -m model.safetensors -p "a cat" --flash-attn auto,te=off,vae=on
sd-cli -m model.safetensors -p "a cat" --flash-attn off,diffusion=auto
```
Supported modules are `diffusion`, `te`, `clip_vision`, `vae`, `controlnet`, and
`audio_encoder`. Modules without an assignment use the default (`auto` if no
default is supplied). A bare mode or `all=`, `default=`, or `*=` sets the default;
module-specific settings take precedence regardless of their position. Within
one assignment string, the last setting for the same module or default wins.
Repeating `--flash-attn` replaces the entire previous string.
Only modules set to `auto` need backend probing. An explicit `on` or `off` for
one module does not change another module's decision, even when they share a
backend. Modules without configurable attention are rejected instead of silently
ignoring their settings.
- `on` requests flash attention for all models. Unsupported attention operations
still fall back to ordinary attention.
- `off` disables flash attention, including the flash-attention fallback from
SageAttention. SageAttention itself remains controlled by `--sage-attn`.
- `auto` enables CPU and supported CUDA MMA configurations without timing a
benchmark. CUDA uses both the device's capability and the compiled GGML
architectures. HIP also skips timing on known CDNA MFMA architectures
(`gfx908`, `gfx90a`, `gfx940`/`gfx941`/`gfx942`, `gfx950`) and RDNA 3/3.5/4
WMMA architectures (`gfx11xx`/`gfx12xx`). The CDNA shortcut requires
`GGML_HIP_MMQ_MFMA` to be enabled at build time. All shortcuts still check
support for the FA probe graph. Vulkan devices whose description identifies
NVIDIA RTX hardware (GeForce RTX, TITAN RTX, Quadro RTX and professional RTX)
also skip timing. This is a device-name heuristic and needs no CUDA runtime.
Unsupported graphs are disabled; other configurations, including older or
unknown HIP architectures and other Vulkan devices, are benchmarked.
The probe compares ordinary and flash attention through the same attention
builder used by models, including layout conversion and F16 conversion. It uses
two small self-attention graphs (512 tokens with head size 64, and 1024 tokens
with head size 128, both with four heads), checks their outputs, warms up each
path, then alternates five synchronized measurements. Flash attention is enabled
when the sum of its median times is at least 5% lower. These representative
shapes are a startup heuristic, not a guarantee for every model or resolution.
Allocation/execution failures and inconclusive probes select `off`. Timing stops
starting new measurement pairs after one second of measured computation;
backend initialization, warmup and an in-flight pair can take longer.
Different modules can select different results. A multi-device model enables
flash attention only if all of its compute devices select `on`, preserving the
model's existing single boolean setting. This conservative rule also covers
row-split configurations, which can fall back to layer split during initialization.
`--fa` and `--diffusion-fa` are deprecated and emit a warning when used.
Use `--flash-attn on` and `--flash-attn off,diffusion=on`, respectively.
Both aliases write the same mode string as `--flash-attn`; the last occurrence
of any of these options wins. Library callers
can set `sd_ctx_params_t::flash_attn_mode` to the same string, including
`"on"`, `"off"`, `"auto"`, or `"auto,te=off,vae=on"`.
A null or empty string uses automatic selection unless a
legacy `flash_attn` or `diffusion_flash_attn` boolean is set. The context copies
the string; no new enum is required.
```shell
sd-cli -m model.safetensors -p "a cat" --flash-attn auto
sd-cli -m model.safetensors -p "a cat" --flash-attn off
```
## Modules
| Module | Purpose | Accepted names |
+46
View File
@@ -0,0 +1,46 @@
# Adding backend performance probes
`src/core/probe/backend_probe.h` provides the shared measurement machinery. Operator
policy stays separate: `src/core/probe/flash_attn.cpp`, for example, defines attention
shapes and its CPU/CUDA/HIP/Vulkan shortcuts, then calls `sd_run_backend_probe` for unknown
configurations. A future convolution probe can use the same runner without
changing the attention implementation.
For each representative shape, create a `BackendProbeCase`:
- `build(graph, candidate)` creates inputs and either the reference or candidate
graph in `graph.ctx`, registers input tensors in `graph.inputs`, and assigns
`graph.output`. Leave the output null if the requested implementation is
unavailable; do not silently benchmark its fallback as the candidate.
- By default, inputs receive deterministic F32 data, with the same seed for both
variants. Supply `initialize` for F16 convolution weights, integer inputs, or
operator-specific values. It runs after allocation, outside timed execution.
Both variants must receive equivalent logical inputs.
- Default output validation requires matching shapes and contiguous F32 outputs,
checks finiteness, and applies absolute and relative tolerances. Supply
`compare` for other output types or validation rules.
Graphs must be safe to execute repeatedly without modifying their inputs.
`BackendProbeGraph` owns its GGML context and buffer, so all return paths release
probe resources. Constructors allocate metadata only; `supported()` can also be
used before a known-architecture shortcut, without allocating device tensors.
`BackendProbeOptions` controls graph metadata capacity, warmup count, measurement
count, measured-time budget, minimum free device memory, comparison tolerances,
and minimum improvement. Warmup and allocation are excluded from timing. The
runner alternates the two variants, synchronizes the backend around execution,
and compares sums of per-case medians. The time budget is checked before each
measurement pair; it cannot interrupt an in-flight backend operation.
Only use `candidate_faster` when `completed` is true. On incomplete probes,
`error` reports why comparison failed and the timing totals may be partial.
The caller handles logging, known architecture rules, fallback policy, and
context-lifetime caching. Keep caches separate by operation (or key a shared
cache by both operation and backend); an attention result must not select a
convolution implementation. If shapes or settings vary within one context,
include them in the corresponding cache key as well.
No convolution policy or CLI option is added by this infrastructure.
Module assignment parsing remains in `src/core/ggml_extend_backend.cpp`, next
to backend assignment parsing. The probe directory does not parse CLI/API options.
+2 -2
View File
@@ -17,7 +17,7 @@ Boogu Image uses a Boogu diffusion transformer, the FLUX VAE, and Qwen3-VL as th
### Boogu Image Base
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\boogu_image_base_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --vae ..\models\vae\ae.sft -p "a lovely cat" --diffusion-fa -v --offload-to-cpu
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\boogu_image_base_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --vae ..\models\vae\ae.sft -p "a lovely cat" -v --offload-to-cpu
```
<img width="256" alt="Boogu Image Base example" src="../assets/boogu/example.png" />
@@ -25,7 +25,7 @@ Boogu Image uses a Boogu diffusion transformer, the FLUX VAE, and Qwen3-VL as th
### Boogu Image Edit
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\boogu_image_edit_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --llm_vision ..\models\text_encoders\mmproj-Qwen3VL-8B-Instruct-F16.gguf --vae ..\models\vae\ae.sft --diffusion-fa -v --offload-to-cpu -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'boogu.cpp'"
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\boogu_image_edit_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --llm_vision ..\models\text_encoders\mmproj-Qwen3VL-8B-Instruct-F16.gguf --vae ..\models\vae\ae.sft -v --offload-to-cpu -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'boogu.cpp'"
```
<img width="256" alt="Boogu Image Edit example" src="../assets/boogu/edit_example.png" />
+38
View File
@@ -16,6 +16,40 @@ git submodule init
git submodule update
```
## Selecting a GGML source tree
By default, sd.cpp builds the patched GGML submodule in `ggml/`. To build with
an upstream GGML checkout instead, enable `SD_USE_UPSTREAM_GGML` and set
`SD_GGML_SOURCE_DIR`:
```shell
cmake -S . -B build-upstream -DSD_USE_UPSTREAM_GGML=ON -DSD_GGML_SOURCE_DIR=../ggml-upstream
cmake --build build-upstream --config Release
```
The selected source tree supplies both the library and its private headers.
Backend options such as `-DSD_CUDA=ON` apply to the selected tree as usual.
`SD_USE_UPSTREAM_GGML` defaults to `OFF`, which enables the patched GGML
extensions. Set it to `ON` when using upstream GGML; it selects the compatibility
mode and does not download or replace the GGML source tree. Upstream mode keeps
the original FP8 safetensors handling: FP8 tensors are converted to F16 at load
time (one byte per element in the file, two in RAM and VRAM). INT8
tensorwise/convrot is disabled and its model files are rejected with an explicit
error. FP8 GGUF files, FP8 weight type requests and tensor type rules are also
rejected; no automatic conversion is performed.
Upstream GGML may lack some operators and performance optimizations provided by
the patched version. A warning is emitted during CMake configuration and when
creating an inference context. Ordinary floating-point and shared GGML
quantization types remain available, subject to backend operator support.
`SD_USE_SYSTEM_GGML=ON` instead links an installed GGML CMake package, located
with `ggml_DIR` or `CMAKE_PREFIX_PATH`. In that mode, `SD_GGML_SOURCE_DIR` must
point to the matching source tree for private headers. The installed library
must use the same ABI settings as sd.cpp, including `GGML_MAX_NAME`.
Set `SD_USE_UPSTREAM_GGML=ON` as well if the installed package is upstream GGML.
## WebP and WebM Support in Examples
The example applications (`examples/cli` and `examples/server`) use `libwebp` to support WebP image I/O, and `examples/cli` can also use `libwebm` for `.webm` video output. Both are enabled by default. WebM output currently reuses `libwebp` to encode each frame as VP8 before muxing with `libwebm`.
@@ -56,6 +90,10 @@ cmake --build . --config Release
## Build with CUDA
Native SageAttention is included when using CUDA with patched GGML
(`SD_USE_UPSTREAM_GGML=OFF`).
See [SageAttention](sage_attention.md) for GPU requirements and `--sage-attn` usage.
This provides GPU acceleration using NVIDIA GPU. Make sure to have the CUDA toolkit installed. You can download it from your Linux distro's package manager (e.g. `apt install nvidia-cuda-toolkit`) or from here: [CUDA Toolkit](https://developer.nvidia.com/cuda-downloads). Recommended to have at least 4 GB of VRAM.
```shell
+10
View File
@@ -2,6 +2,16 @@
Caching methods accelerate diffusion inference by reusing intermediate computations when changes between steps are small.
### Conditioning Cache
Conditioning results are cached per model context using an LRU cache. The default
capacity is **0 (disabled) for `sd-cli`** and **4 entries for `sd-server` and the C
API**. Set `--conditioning-cache-size N` to change the limit; `0` disables caching.
For example, `sd-cli -m model.safetensors -p "a cat" --conditioning-cache-size 4`
enables the cache in the CLI. The C API option is
`sd_ctx_params_t::conditioning_cache_size`, initialized by `sd_ctx_params_init()`.
This cache is independent of the diffusion-step `--cache-mode` options below.
### Cache Modes
| Mode | Target | Description |
+32 -1
View File
@@ -24,12 +24,25 @@ docker build -f docker/Dockerfile -t sd .
## Building variants using Docker
Vulkan:
### Vulkan:
```shell
docker build -f docker/Dockerfile.vulkan -t sd .
```
### ROCm (AMD GPU):
Defining one or multiple targets with `--build-arg AMDGPU_TARGETS` is strongly recommended.
Building the image will otherwise likely fail or build for a target that does not fit your
hardware.
```shell
# Build for one target
docker build -f docker/Dockerfile.rocm --build-arg "AMDGPU_TARGETS=gfx1100" -t sd .
# Build for multiple targets
docker build -f docker/Dockerfile.rocm --build-arg "AMDGPU_TARGETS=gfx1030;gfx1100" -t sd .
```
## Run locally built image's CLI
```shell
@@ -37,3 +50,21 @@ docker run --rm -v /path/to/models:/models -v /path/to/output/:/output sd [args.
# For example
# docker run --rm -v ./models:/models -v ./build:/output sd -m /models/sd-v1-4.ckpt -p "a lovely cat" -v -o /output/output.png
```
### ROCm (AMD GPU)
The user that executes this command needs to be in the `video` group and have read and write
access to `/dev/dri` and `/dev/kfd`.
`--device` and `--group-add` allow the container access to the GPU.
```shell
docker run --rm --device=/dev/dri --device=/dev/kfd --group-add video \
-v /path/to/models:/models -v /path/to/output/:/output \
sd [args...]
# For example
# docker run --rm --device=/dev/dri --device=/dev/kfd --group-add video \
# -v ./models:/models -v ./build:/output \
# sd -m /models/sd-v1-4.ckpt -v -o /output/output.png \
# -p "a lovely cat"
```
+4
View File
@@ -17,6 +17,7 @@ Depending on the architecture, different models handle reference images differen
| [**Boogu Image Edit**](./boogu_image.md) | `z_image_omni` |
| **Krea2 (Community Edit LoRAs)** | `krea2_ostris_edit` |
| [**Mage-Flow-Edit**](./mage_flow.md#image-editing) | `mage_flow` |
| [**LLaDA-Image**](./llada_image.md#image-editing) | `llada_image` |
| **Anima (Community Edit LoRAs)** | `cosmos_reference` |
Stable-diffusion.spp also supports basic Unet-based editing models like instruct-pix2pix or CosXL-Edit. This document is not about those.
@@ -25,6 +26,9 @@ Stable-diffusion.spp also supports basic Unet-based editing models like instruct
## Configuring Reference Modes (`--ref-image-args`)
For a one-time input transform before reference presets and model processing,
including cropping, padding, and resizing algorithms, see [Image preprocessing](./image_preprocessing.md).
Different DiT-based editing models require different configurations to process reference images correctly (e.g., whether to use a Vision Language Model (VLM) encoder or pass VAE-encoded images directly to the DiT).
To simplify this, we provide **Presets**. By default, the system automatically selects the best preset based on the model architecture. However, you can override this using the `--ref-image-args` argument.
+2 -2
View File
@@ -21,7 +21,7 @@ You can run ERNIE-Image with stable-diffusion.cpp on GPUs with 4GB of VRAM — o
### ERNIE-Image-Turbo
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\ernie-image-turbo.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\ministral-3-3b.safetensors -p "a lovely cat" --cfg-scale 1.0 --steps 8 -v --offload-to-cpu --diffusion-fa
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\ernie-image-turbo.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\ministral-3-3b.safetensors -p "a lovely cat" --cfg-scale 1.0 --steps 8 -v --offload-to-cpu
```
<img width="256" alt="ERNIE-Image Turbo example" src="../assets/ernie_image/turbo_example.png" />
@@ -29,7 +29,7 @@ You can run ERNIE-Image with stable-diffusion.cpp on GPUs with 4GB of VRAM — o
### ERNIE-Image
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\ernie-image-UD-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\ministral-3-3b.safetensors -p "a lovely cat" --cfg-scale 5.0 -v --offload-to-cpu --diffusion-fa
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\ernie-image-UD-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\ministral-3-3b.safetensors -p "a lovely cat" --cfg-scale 5.0 -v --offload-to-cpu
```
<img width="256" alt="ERNIE-Image example" src="../assets/ernie_image/example.png" />
+4
View File
@@ -2,6 +2,10 @@
You can use ESRGAN—such as the model [RealESRGAN_x4plus_anime_6B.pth](https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth)—to upscale the generated images and improve their overall resolution and clarity.
MoSR models with the PixelShuffle upsampler, such as [4xNomos2_hq_mosr.pth](https://github.com/Phhofm/models/releases/download/4xNomos2_hq_mosr/4xNomos2_hq_mosr.pth), are also supported. The architecture is detected from the weights, so they are used the same way as ESRGAN models.
RGBA images, including Qwen Image 2.1 output, keep their alpha channel during model upscaling and hires fix. The upscaler processes the RGB channels; the alpha channel is resized with bilinear interpolation and recombined with the upscaled image.
- Specify the model path using the `--upscale-model PATH` parameter. example:
```bash
+7 -7
View File
@@ -16,7 +16,7 @@
### Examples
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux2-dev-Q4_K_S.gguf --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\Mistral-Small-3.2-24B-Instruct-2506-Q4_K_M.gguf -r .\kontext_input.png -p "change 'flux.cpp' to 'flux2-dev.cpp'" --cfg-scale 1.0 --sampling-method euler -v --diffusion-fa --offload-to-cpu
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux2-dev-Q4_K_S.gguf --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\Mistral-Small-3.2-24B-Instruct-2506-Q4_K_M.gguf -r .\kontext_input.png -p "change 'flux.cpp' to 'flux2-dev.cpp'" --cfg-scale 1.0 --sampling-method euler -v --offload-to-cpu
```
<img alt="flux2 example" src="../assets/flux2/example.png" />
@@ -42,19 +42,19 @@
### Examples
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-4b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "a lovely cat" --cfg-scale 1.0 --steps 4 -v --offload-to-cpu --diffusion-fa
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-4b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "a lovely cat" --cfg-scale 1.0 --steps 4 -v --offload-to-cpu
```
<img alt="flux2-klein-4b" src="../assets/flux2/flux2-klein-4b.png" />
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-4b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -r .\kontext_input.png -p "change 'flux.cpp' to 'klein.cpp'" --cfg-scale 1.0 --sampling-method euler -v --diffusion-fa --offload-to-cpu --steps 4
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-4b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -r .\kontext_input.png -p "change 'flux.cpp' to 'klein.cpp'" --cfg-scale 1.0 --sampling-method euler -v --offload-to-cpu --steps 4
```
<img alt="flux2-klein-4b-edit" src="../assets/flux2/flux2-klein-4b-edit.png" />
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-base-4b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "a lovely cat" --cfg-scale 4.0 --steps 20 -v --offload-to-cpu --diffusion-fa
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-base-4b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "a lovely cat" --cfg-scale 4.0 --steps 20 -v --offload-to-cpu
```
<img alt="flux2-klein-base-4b" src="../assets/flux2/flux2-klein-base-4b.png" />
@@ -78,19 +78,19 @@
### Examples
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-9b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_8b.safetensors -p "a lovely cat" --cfg-scale 1.0 --steps 4 -v --offload-to-cpu --diffusion-fa
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-9b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_8b.safetensors -p "a lovely cat" --cfg-scale 1.0 --steps 4 -v --offload-to-cpu
```
<img alt="flux2-klein-9b" src="../assets/flux2/flux2-klein-9b.png" />
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-9b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_8b.safetensors -r .\kontext_input.png -p "change 'flux.cpp' to 'klein.cpp'" --cfg-scale 1.0 --sampling-method euler -v --diffusion-fa --offload-to-cpu --steps 4
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-9b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_8b.safetensors -r .\kontext_input.png -p "change 'flux.cpp' to 'klein.cpp'" --cfg-scale 1.0 --sampling-method euler -v --offload-to-cpu --steps 4
```
<img alt="flux2-klein-9b-edit" src="../assets/flux2/flux2-klein-9b-edit.png" />
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-base-9b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_8b.safetensors -p "a lovely cat" --cfg-scale 4.0 --steps 20 -v --offload-to-cpu --diffusion-fa
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-base-9b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_8b.safetensors -p "a lovely cat" --cfg-scale 4.0 --steps 20 -v --offload-to-cpu
```
<img alt="flux2-klein-base-9b" src="../assets/flux2/flux2-klein-base-9b.png" />
+1 -1
View File
@@ -18,7 +18,7 @@ and ByT5 Small GlyphXL for glyph-aware text conditioning.
## Text-to-video example
```shell
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\hunyuanvideo1.5_720p_t2v_fp16.safetensors --vae ..\models\vae\hunyuanvideo15_vae_fp16.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --t5xxl ..\models\text_encoders\byt5_small_glyphxl_fp16.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -W 1280 -H 720 --offload-to-cpu --diffusion-fa --video-frames 33 --vae-tiling
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\hunyuanvideo1.5_720p_t2v_fp16.safetensors --vae ..\models\vae\hunyuanvideo15_vae_fp16.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --t5xxl ..\models\text_encoders\byt5_small_glyphxl_fp16.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -W 1280 -H 720 --offload-to-cpu --video-frames 33 --vae-tiling
```
<video src=../assets/hunyuan_video/hy1.5_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
+4 -1
View File
@@ -18,6 +18,9 @@ at one byte per element in RAM and VRAM. Backends that cannot multiply FP8
weights directly cast only the active layer to a temporary BF16 tensor during
execution; the loader does not expand the entire checkpoint to BF16.
With `SD_USE_UPSTREAM_GGML=ON`, FP8 tensors are converted to F16 at load time
instead (two bytes per element in RAM and VRAM).
Use `ideogram4_fp8.safetensors` and `ideogram4_uncond_fp8.safetensors` directly
with `--diffusion-model` and `--uncond-diffusion-model`, respectively.
@@ -46,7 +49,7 @@ If you want lower VRAM usage, you can change the quantization from q8_0 to a low
## Examples
```sh
.\bin\Release\sd-cli.exe --diffusion-model ideogram4-Q8_0.gguf --uncond-diffusion-model ideogram4_uncond-Q8_0.gguf --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors -p '{"high_level_description":"A square 1024 x 1024 luxury fashion magazine cover featuring exactly one short chubby fluffy cat as the main model. The cat sits on a soft ivory studio floor, facing the viewer with a stylish calm expression, wearing tiny black sunglasses, a red silk scarf, and a small gold collar charm. In front of the cat on the floor is a wide horizontal luxury nameplate that clearly reads ideogram4.cpp. The whole design feels premium, fashionable, clean, and editorial.","style_description":{"aesthetics":"luxury fashion magazine cover, high-end pet couture campaign, minimalist editorial design, elegant studio photography, soft paper texture, refined typography, fashionable and polished","lighting":"Soft diffused studio lighting, gentle spotlight on the cat, subtle floor shadow, warm ivory highlights, clean separation between subject and background","photo":"high-resolution fashion editorial photography look, front-facing cat portrait, crisp fur details, glossy sunglasses, clear readable nameplate text, shallow depth of field","medium":"mixed media fashion photography and premium editorial graphic design","color_palette":["#F4EFE7","#111111","#D8B56D","#B73A3A","#FFFFFF","#8A7A6A"]},"compositional_deconstruction":{"canvas":"Square 1024 x 1024 canvas with a normal upright orientation. Do not rotate the poster or any text. Use a clean fashion magazine cover layout.","background":"Warm ivory studio backdrop with subtle paper grain, a soft spotlight gradient, faint floor shadow, and a few minimal gold editorial lines. The background is spacious, premium, and uncluttered.","layout":"Top center has a small elegant headline. Center area features one cat as the main fashion model. Lower foreground has a wide horizontal luxury nameplate placed on the floor in front of the cat. Bottom center has a small footer. All text is horizontal, upright, and readable left to right.","elements":[{"type":"text","desc":"Top center headline reading LOOK WHAT I FOUND in a refined high-fashion serif font. The headline is horizontal, centered, elegant, and secondary to the nameplate text."},{"type":"obj","desc":"Exactly one short chubby fluffy cat sitting in the center like a luxury fashion model. The cat has a large round head, compact body, short legs, soft detailed fur, expressive eyes, and a calm confident pose. The cat is cute and rounded, not tall, not stretched, not duplicated."},{"type":"obj","desc":"Tiny glossy black sunglasses worn naturally by the cat, slightly oversized but still showing the cat face clearly. The sunglasses add a chic fashion-editorial attitude."},{"type":"obj","desc":"A red silk scarf tied neatly around the cat neck, with soft folds and a couture feeling. The scarf must not cover the cat face or the nameplate."},{"type":"obj","desc":"A small gold collar charm or fashion accessory under the scarf, subtle and premium, adding a luxury campaign detail."},{"type":"obj","desc":"In the lower foreground, place a wide horizontal luxury nameplate on the floor in front of the cat. The nameplate is low, flat, landscape-oriented, much wider than tall, like a fashion show seat card or premium display plaque. It is centered, front-facing, level, and fully visible. It must not become vertical, tall, standing, rotated, or side-facing."},{"type":"text","desc":"Print the exact text ideogram4.cpp only on the wide horizontal nameplate. Use clean bold black lettering, perfectly spelled, lowercase, with the number 4 and .cpp extension. The text must fit completely inside the nameplate, stay horizontal, and be readable from left to right."},{"type":"obj","desc":"Add sparse premium editorial accents around the edges: thin gold lines, small code brackets, tiny cursor marks, subtle dots, and minimal geometric details. No extra cats, no stickers, no animal faces, no busy decorations."},{"type":"text","desc":"Bottom center footer reading tiny paws, big compile energy in a small refined monospace or editorial font. The footer is horizontal, centered, understated, and much smaller than the nameplate text."}]}}' --diffusion-fa -v --offload-to-cpu -H 1024 -W 1024
.\bin\Release\sd-cli.exe --diffusion-model ideogram4-Q8_0.gguf --uncond-diffusion-model ideogram4_uncond-Q8_0.gguf --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors -p '{"high_level_description":"A square 1024 x 1024 luxury fashion magazine cover featuring exactly one short chubby fluffy cat as the main model. The cat sits on a soft ivory studio floor, facing the viewer with a stylish calm expression, wearing tiny black sunglasses, a red silk scarf, and a small gold collar charm. In front of the cat on the floor is a wide horizontal luxury nameplate that clearly reads ideogram4.cpp. The whole design feels premium, fashionable, clean, and editorial.","style_description":{"aesthetics":"luxury fashion magazine cover, high-end pet couture campaign, minimalist editorial design, elegant studio photography, soft paper texture, refined typography, fashionable and polished","lighting":"Soft diffused studio lighting, gentle spotlight on the cat, subtle floor shadow, warm ivory highlights, clean separation between subject and background","photo":"high-resolution fashion editorial photography look, front-facing cat portrait, crisp fur details, glossy sunglasses, clear readable nameplate text, shallow depth of field","medium":"mixed media fashion photography and premium editorial graphic design","color_palette":["#F4EFE7","#111111","#D8B56D","#B73A3A","#FFFFFF","#8A7A6A"]},"compositional_deconstruction":{"canvas":"Square 1024 x 1024 canvas with a normal upright orientation. Do not rotate the poster or any text. Use a clean fashion magazine cover layout.","background":"Warm ivory studio backdrop with subtle paper grain, a soft spotlight gradient, faint floor shadow, and a few minimal gold editorial lines. The background is spacious, premium, and uncluttered.","layout":"Top center has a small elegant headline. Center area features one cat as the main fashion model. Lower foreground has a wide horizontal luxury nameplate placed on the floor in front of the cat. Bottom center has a small footer. All text is horizontal, upright, and readable left to right.","elements":[{"type":"text","desc":"Top center headline reading LOOK WHAT I FOUND in a refined high-fashion serif font. The headline is horizontal, centered, elegant, and secondary to the nameplate text."},{"type":"obj","desc":"Exactly one short chubby fluffy cat sitting in the center like a luxury fashion model. The cat has a large round head, compact body, short legs, soft detailed fur, expressive eyes, and a calm confident pose. The cat is cute and rounded, not tall, not stretched, not duplicated."},{"type":"obj","desc":"Tiny glossy black sunglasses worn naturally by the cat, slightly oversized but still showing the cat face clearly. The sunglasses add a chic fashion-editorial attitude."},{"type":"obj","desc":"A red silk scarf tied neatly around the cat neck, with soft folds and a couture feeling. The scarf must not cover the cat face or the nameplate."},{"type":"obj","desc":"A small gold collar charm or fashion accessory under the scarf, subtle and premium, adding a luxury campaign detail."},{"type":"obj","desc":"In the lower foreground, place a wide horizontal luxury nameplate on the floor in front of the cat. The nameplate is low, flat, landscape-oriented, much wider than tall, like a fashion show seat card or premium display plaque. It is centered, front-facing, level, and fully visible. It must not become vertical, tall, standing, rotated, or side-facing."},{"type":"text","desc":"Print the exact text ideogram4.cpp only on the wide horizontal nameplate. Use clean bold black lettering, perfectly spelled, lowercase, with the number 4 and .cpp extension. The text must fit completely inside the nameplate, stay horizontal, and be readable from left to right."},{"type":"obj","desc":"Add sparse premium editorial accents around the edges: thin gold lines, small code brackets, tiny cursor marks, subtle dots, and minimal geometric details. No extra cats, no stickers, no animal faces, no busy decorations."},{"type":"text","desc":"Bottom center footer reading tiny paws, big compile energy in a small refined monospace or editorial font. The footer is horizontal, centered, understated, and much smaller than the nameplate text."}]}}' -v --offload-to-cpu -H 1024 -W 1024
```
<img alt="ideogram4 image example" src="../assets/ideogram4/example.png" />
+173
View File
@@ -0,0 +1,173 @@
# Image preprocessing
Use `--image-preprocess` to transform each image input once, before generation:
```sh
sd-cli ... \
--image-preprocess "target=init,mode=crop-resize,filter=lanczos,antialias=true" \
--image-preprocess "target=mask,filter=nearest-exact" \
--image-preprocess "target=ref,index=0,mode=fit-pad,width=768,height=768,filter=bicubic"
```
CLI and server image loaders decode at the original resolution. The generation
entry point merges input defaults with user rules and prepares one transformed
image per input. The original pipeline then consumes those images, including
its mandatory canvas adaptation, reference resizing, and encoder preprocessing.
```text
native-resolution image
-> input defaults + user overrides
-> one input transform
-> original generation pipeline and model-specific processing
```
These rules do not override internal VAE, CLIP/VLM, ControlNet, or pixel-patch preprocessing.
`--ref-image-args` retains its existing meaning and runs after this input transform.
## Inputs and defaults
| `target` | Input | Default geometry | Indexed? |
| --- | --- | --- | --- |
| `init` | img2img image or video first frame | Center crop to the generation aspect ratio, then resize | No |
| `end` | Video last frame | Center crop, then resize | No |
| `mask` | Inpainting mask | Inherit init geometry; otherwise center crop, then resize | No |
| `control` | Control image | Center crop, then resize | No |
| `ref` | Reference images | Preserve source dimensions | Yes |
| `ip-adapter` | IP-Adapter image | Preserve source dimensions | No |
| `id` | PhotoMaker identity images | Preserve source dimensions | Yes |
| `control-frame` | Control video frames | Center crop, then resize | Yes |
Canvas defaults use the aligned generation dimensions. Reference, IP-Adapter,
and identity inputs use their original dimensions unless overridden. Default
resampling is nearest for images and nearest-exact for masks.
These defaults are shared by CLI, server, and C API. Moving geometry out of
the loaders replaces the previous CLI/server BOX/sRGB resizing, so default
pixels are not guaranteed to match earlier builds.
Reference video and audio preprocessing are outside these image rules.
Preprocessing options apply to `img_gen` and `vid_gen`, not standalone upscale
or ADetailer mode. ADetailer clears the user's rules for its internal crops.
## Rules
Rules are comma-separated `key=value` lists. Repeat the CLI option or separate
rules with semicolons. Every rule requires a `target` and at least one option.
Rule syntax and input compatibility are checked when image/video generation
starts. Unknown keys, invalid values, duplicate keys in a rule, missing images,
and out-of-range indices cause generation to fail with an error log.
Omit `index` to configure every image of that type; otherwise use a zero-based
index. CLI directory inputs follow filename order. Indexed rules override
type-wide rules field by field, regardless of order. At equal specificity,
the last value for a field wins. `auto` selects the input preset.
| `mode` | Input transform |
| --- | --- |
| `auto` | Use the input's default geometry |
| `none` | Keep source dimensions without resizing, cropping, or padding |
| `stretch` | Resize to the target dimensions |
| `crop` | Crop a target-sized rectangle without resizing; fail if the source is too small |
| `crop-resize` | Crop to the target aspect ratio, then resize |
| `fit-pad` | Fit the entire image inside the target dimensions, preserving aspect ratio, then pad |
`width` and `height` must be specified together as positive integers. They
override the input transform's dimensions, not the generation or encoder size.
For a native-size preset, specifying dimensions without a mode selects stretch.
`mode=none` with explicit dimensions different from the source is contradictory
and is rejected.
`anchor=center|top|bottom|left|right` selects crop/padding placement.
`pad_color=#RRGGBB` or `#RRGGBBAA` selects padding, defaulting to opaque black.
A grayscale mask uses the first color component.
`filter=auto|nearest|nearest-exact|bilinear|bicubic|lanczos` selects resampling.
`antialias=auto|true|false` enables antialiasing automatically for filtered
downscaling; explicit true requires bilinear, bicubic, or Lanczos.
Filtered RGBA resizing uses premultiplied alpha.
`canny=true|false` enables edge detection for any supported image target,
defaulting to `false`. It runs once after geometry, before the original
generation pipeline, including with `mode=none`. Grayscale, grayscale-alpha,
RGB, and RGBA inputs are supported; alpha is preserved.
Each input has its own Canny setting. Indexed rules can enable or disable it
for individual references, identity images, or video control frames.
```sh
--image-preprocess "target=init,mode=fit-pad,canny=true"
--image-preprocess "target=ref,index=0,mode=none,canny=true"
--image-preprocess "target=control-frame,index=2,canny=true"
```
Init and mask sources must have the same dimensions. The mask inherits the
init crop, resize, and padding coordinates, while retaining its own filter,
padding value, and Canny setting. Conflicting mask geometry is rejected. An
omitted mask remains absent until the original pipeline creates its default mask.
## Downstream behavior
`mode=none` only skips the input geometry transform. For example:
```sh
--image-preprocess "target=init,mode=none" \
--image-preprocess "target=ref,mode=none"
```
The init image is still adapted to the generation canvas by the original
pipeline. Reference images still follow `--ref-image-args` and model-specific
resizing. CLIP retains its fixed input dimensions and normalization. HiDream-O1
retains its original pixel-reference and visual preprocessing.
Existing sharing between consumers is preserved: for example, Wan img2video
uses the same adapted first frame for VAE conditioning and CLIP. High-resolution
passes reuse the prepared images and apply their original size adaptation;
they do not apply the user's crop a second time.
To disable reference resizing before VAE encoding, use
`--ref-image-args "resize_before_vae=false"` or the server field
`"ref_image_args": "resize_before_vae=false"`. This is separate from
`target=ref,mode=none`, which only skips input geometry. Model constraints
still apply.
## Server requests
Native image/video requests and SDAPI accept `image_preprocess` as a string or
an array of rule strings:
```json
{
"image_preprocess": [
"target=init,mode=fit-pad,filter=bicubic",
"target=mask,filter=nearest-exact",
"target=ref,index=0,mode=none"
]
}
```
OpenAI-compatible requests accept it through
`<sd_cpp_extra_args>{...}</sd_cpp_extra_args>` in the prompt.
Request rules replace server-default rules. Generation metadata records the
user rules; image encodings and channel conventions are unchanged.
## C API
Set `image_preprocess` on the existing image/video generation parameters.
The `generate_image()` and `generate_video()` signatures are unchanged:
```c
sd_img_gen_params_t params;
sd_img_gen_params_init(&params);
/* Set prompt, original-resolution input images, and generation options. */
params.image_preprocess.rules = "target=init,mode=crop-resize,filter=lanczos;"
"target=mask,filter=nearest-exact";
bool ok = generate_image(ctx, &params, &images, &count);
```
Both generation parameter initializers set `image_preprocess.rules` to `NULL`,
selecting input presets. Rule strings are borrowed for the synchronous call.
The library owns temporary transformed pixels; caller images and arrays are
not modified. Add `canny=true` to the desired target's rule in
`image_preprocess.rules` to enable Canny.
The parameter structs have grown; applications and bindings must be rebuilt.
+8 -4
View File
@@ -2,6 +2,9 @@
sd.cpp can load and execute ComfyUI `int8_tensorwise` safetensors with `convrot` metadata directly. The stored INT8 weights are not converted to another weight type at load time.
This requires the INT8 tensorwise/convrot extensions in the patched GGML.
Builds with `SD_USE_UPSTREAM_GGML=ON` reject these files during loading.
## Checkpoint format
Each quantized linear module contains the following tensors:
@@ -81,22 +84,23 @@ The floating-point output is reconstructed as
Y[r, o] ~= A[r, o] * s_x[r] * s_w[o] + b[o]
```
The packed runtime activation tensor contains the I8 activation rows and their floating-point row scales. Linear layers that share the same input and convrot group size reuse this packed tensor, avoiding repeated rotation and activation quantization within the graph.
The packed runtime activation tensor contains the I8 activation rows and their floating-point row scales. Linear layers with an input scale of `1` that share the same input and convrot group size reuse this packed tensor, avoiding repeated rotation and activation quantization within the graph. For other input scales, activations are scaled before explicit convrot quantization, and the linear output is unscaled before adding bias.
## Backend support
- CPU provides the portable regular Hadamard, activation quantization, INT8 matrix multiplication, and scale restoration implementations.
- NVIDIA CUDA devices with compute capability 7.5 or newer use the native accelerated path. For H256, CUDA fuses the rotation, row-wise maximum reduction, and activation quantization. It uses cuBLAS for I8 x I8 to I32 GEMM and a CUDA kernel for scale restoration and bias addition.
- Vulkan and other GPU backends do not currently have dedicated INT8 convrot kernels. They use the backend scheduler to fall back to CPU, which is expected to be substantially slower than the CUDA path.
- AMD HIP devices in the CDNA, RDNA3 (including RDNA3.5), and RDNA4 families use the same INT8/H256 kernels with hipBLAS for I8 x I8 to I32 GEMM. Other AMD architectures fall back to CPU for INT8 matrix multiplication.
- Vulkan provides native H256 activation quantization and INT8 matrix multiplication when the build and device support accelerated packed INT8 dot products. Unsupported configurations and GPU backends without these kernels use the backend scheduler to fall back to CPU.
LoRA adapters are applied at runtime without modifying the INT8 weights. The INT8 convrot path computes the base linear output, while LoRA, LoHa, LoKr, and raw weight-difference adapters compute their output corrections from the original, unrotated activation and add them to the base output. `--lora-apply-mode auto` selects this path for models containing INT8 tensorwise weights. If `immediately` is requested, sd.cpp falls back to runtime application because merging an adapter would require dequantizing and rotating its weight update, then recalculating the per-row scales and requantizing the result.
The dedicated CUDA convrot activation path currently requires a group size of `256`; other supported group sizes use CPU execution.
The dedicated CUDA and HIP convrot activation paths currently require a group size of `256`; other supported group sizes use CPU execution.
## Example
ComfyUI INT8 convrot safetensors can be passed to `--diffusion-model` without conversion:
```powershell
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\krea2_turbo_int8_convrot.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\wan_2.1_vae.safetensors -p "a lovely cat holding a sign says 'krea2.cpp'" --steps 8 --cfg-scale 1 --diffusion-fa -v --offload-to-cpu
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\krea2_turbo_int8_convrot.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\wan_2.1_vae.safetensors -p "a lovely cat holding a sign says 'krea2.cpp'" --steps 8 --cfg-scale 1 -v --offload-to-cpu
```
+2 -2
View File
@@ -48,7 +48,7 @@ sd-cli -m ..\models\sd_v1.5.safetensors --clip_vision ..\models\clip_vision_h.sa
## Example (SDXL)
```
sd-cli -m ..\models\sdxl.safetensors --clip_vision ..\models\clip_vision_h.safetensors --ip-adapter ..\models\ip-adapter_sdxl_vit-h.safetensors --ip-adapter-image ..\assets\reference.png --ip-adapter-strength 0.8 -p "a woman, best quality" -n "lowres, bad anatomy" --cfg-scale 6 --steps 25 --sampling-method dpm++2m --scheduler karras -W 1024 -H 1024 --diffusion-fa --vae-tiling
sd-cli -m ..\models\sdxl.safetensors --clip_vision ..\models\clip_vision_h.safetensors --ip-adapter ..\models\ip-adapter_sdxl_vit-h.safetensors --ip-adapter-image ..\assets\reference.png --ip-adapter-strength 0.8 -p "a woman, best quality" -n "lowres, bad anatomy" --cfg-scale 6 --steps 25 --sampling-method dpm++2m --scheduler karras -W 1024 -H 1024 --vae-tiling
```
The SDXL VAE decode at 1024x1024 is memory heavy; add `--vae-tiling` (and
@@ -83,5 +83,5 @@ Add the usual Control Net options to keep the reference appearance while
controlling the pose:
```
sd-cli -m ..\models\sdxl.safetensors --clip_vision ..\models\clip_vision_h.safetensors --ip-adapter ..\models\ip-adapter_sdxl_vit-h.safetensors --ip-adapter-image ..\assets\character.png --ip-adapter-strength 0.9 --control-net ..\models\OpenPoseXL2.safetensors --control-image ..\assets\pose.png --control-strength 0.8 -p "a character, side view" --cfg-scale 6 --steps 25 -W 1024 -H 1024 --diffusion-fa --vae-tiling
sd-cli -m ..\models\sdxl.safetensors --clip_vision ..\models\clip_vision_h.safetensors --ip-adapter ..\models\ip-adapter_sdxl_vit-h.safetensors --ip-adapter-image ..\assets\character.png --ip-adapter-strength 0.9 --control-net ..\models\OpenPoseXL2.safetensors --control-image ..\assets\pose.png --control-strength 0.8 -p "a character, side view" --cfg-scale 6 --steps 25 -W 1024 -H 1024 --vae-tiling
```
+24
View File
@@ -0,0 +1,24 @@
# How to Use
Iris-3B generates images directly in pixel space and uses Qwen3-VL-4B-Instruct as the text encoder. No VAE is required.
## Download weights
- Download Iris-3B
- safetensors: https://huggingface.co/speridlabs/iris-3b/tree/main (`model.safetensors` in the root directory)
- Download Qwen3-VL-4B-Instruct
- safetensors: https://huggingface.co/Comfy-Org/Krea-2/tree/main/text_encoders
- gguf: https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct-GGUF/tree/main
## Examples
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\iris-3b.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf -p "a lovely cat" --cfg-scale 3 -H 1024 -W 1024
```
<img width="256" alt="iris-3b example" src="../assets/iris/example.png" />
## Notes
- Width and height must be multiples of 16. Do not pass `--vae`.
- Captions are limited to 300 tokens including the assistant-turn suffix. Positive and negative prompts use the same template.
+1 -1
View File
@@ -21,7 +21,7 @@ Krea2 uses a Krea2 diffusion transformer, the Wan2.1 VAE, and Qwen3-VL 4B as the
### Krea2
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Krea-2-Raw-Q8_0.gguf --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\wan_2.1_vae.safetensors -p "a lovely cat holding a sign says 'krea2.cpp'" --diffusion-fa -v --offload-to-cpu
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Krea-2-Raw-Q8_0.gguf --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\wan_2.1_vae.safetensors -p "a lovely cat holding a sign says 'krea2.cpp'" -v --offload-to-cpu
```
<img width="256" alt="Krea2 Raw example" src="../assets/krea2/example.png" />
+2 -2
View File
@@ -22,7 +22,7 @@ Lens and Lens Turbo require an external GPT-OSS `tokenizer.json` matching the te
### Lens
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\lens_bf16.safetensors --llm "..\models\text_encoders\gpt-oss-20b-UD-Q8_K_XL.gguf" --tokenizer ..\models\tokenizers\tokenizer_gpt_oss.json --vae ..\models\vae\flux2_ae.safetensors --cfg-scale 5.0 -p "A crystal dragon soaring through an aurora borealis sky, its entire body made of transparent faceted crystal refracting the green and purple aurora light into rainbow spectra, ice particles trailing from its wings, high fantasy digital art" --diffusion-fa -v
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\lens_bf16.safetensors --llm "..\models\text_encoders\gpt-oss-20b-UD-Q8_K_XL.gguf" --tokenizer ..\models\tokenizers\tokenizer_gpt_oss.json --vae ..\models\vae\flux2_ae.safetensors --cfg-scale 5.0 -p "A crystal dragon soaring through an aurora borealis sky, its entire body made of transparent faceted crystal refracting the green and purple aurora light into rainbow spectra, ice particles trailing from its wings, high fantasy digital art" -v
```
<img width="256" alt="Lens example" src="../assets/lens/example.png" />
@@ -30,7 +30,7 @@ Lens and Lens Turbo require an external GPT-OSS `tokenizer.json` matching the te
### Lens Turbo
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\lens_turbo_bf16.safetensors --llm "..\models\text_encoders\gpt-oss-20b-UD-Q8_K_XL.gguf" --tokenizer ..\models\tokenizers\tokenizer_gpt_oss.json --vae ..\models\vae\flux2_ae.safetensors --cfg-scale 1.0 -p "A crystal dragon soaring through an aurora borealis sky, its entire body made of transparent faceted crystal refracting the green and purple aurora light into rainbow spectra, ice particles trailing from its wings, high fantasy digital art" --diffusion-fa -v --steps 4
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\lens_turbo_bf16.safetensors --llm "..\models\text_encoders\gpt-oss-20b-UD-Q8_K_XL.gguf" --tokenizer ..\models\tokenizers\tokenizer_gpt_oss.json --vae ..\models\vae\flux2_ae.safetensors --cfg-scale 1.0 -p "A crystal dragon soaring through an aurora borealis sky, its entire body made of transparent faceted crystal refracting the green and purple aurora light into rainbow spectra, ice particles trailing from its wings, high fantasy digital art" -v --steps 4
```
<img width="256" alt="Lens Turbo example" src="../assets/lens/turbo_example.png" />
+1 -1
View File
@@ -28,5 +28,5 @@ Lingbot Video uses a Lingbot diffusion transformer, the Wan2.1 VAE, and Qwen3-VL
### LingBot-Video T2V
```sh
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\lingbot-video-dens-1.3b.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\wan_2.1_vae.safetensors -p '{"caption":{"comprehensive_description":"A lovely cat sits comfortably on a soft cushion near a sunlit window, looking calm, gentle, and adorable. The cat has soft fluffy fur, bright expressive eyes, small rounded ears, delicate whiskers, and a relaxed posture. Warm daylight falls across the cat from one side, creating soft highlights on the fur and a cozy glow around the scene. The background is softly blurred, showing hints of a peaceful indoor home environment with warm tones and gentle natural light. The overall atmosphere is cute, tender, serene, and photorealistic, emphasizing the cat''s charming appearance, soft texture, and affectionate presence.","camera_info":{"color":"Warm","frame_size":"Close Up","shot_type_angle":"Eye level","lens_size":"Medium Lens","composition":"Centered balanced","lighting":"Soft light","lighting_type":"Daylight"},"world_knowledge":[],"prominent_elements":[{"name":"lovely cat","description":"A cute and gentle domestic cat with soft fluffy fur, expressive eyes, and a calm relaxed presence.","location":"center of the frame","relative_size":"large","shape_and_color":"Small animal body with rounded face, triangular ears, bright eyes, and soft fur in warm natural tones","texture":"soft, fluffy, silky","appearance_details":"The cat has clean well-groomed fur, delicate whiskers, small ears, a cute nose, and bright attentive eyes. Its expression appears calm, affectionate, and slightly curious.","relationship":"Acts as the main subject and emotional focal point of the scene.","orientation":"facing the camera","pose":"sitting comfortably with a relaxed posture","expression":"gentle, adorable, calm, slightly curious","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"cat eyes","description":"Bright expressive eyes that give the cat a sweet and affectionate appearance.","location":"upper center of the cat face","relative_size":"small","shape_and_color":"Round almond-like eyes with glossy reflections","texture":"clear, glossy, reflective","appearance_details":"The eyes catch the soft daylight, creating small natural highlights that make the cat look vivid and alive.","relationship":"Enhance the emotional charm and cuteness of the cat.","orientation":"looking toward the camera","pose":"","expression":"soft and attentive","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"soft cushion","description":"A comfortable cushion or blanket where the cat is resting.","location":"bottom portion of the frame","relative_size":"medium","shape_and_color":"Soft rounded fabric surface in light warm neutral tones","texture":"plush, fabric, cozy","appearance_details":"The cushion gently supports the cat and adds a comfortable home-like feeling to the scene.","relationship":"Provides a cozy resting place for the cat.","orientation":"horizontal","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"sunlit indoor background","description":"A softly blurred indoor background with warm daylight and peaceful home atmosphere.","location":"behind the cat, filling the upper and side areas of the frame","relative_size":"large","shape_and_color":"Soft abstract shapes in warm beige, cream, and pale golden tones","texture":"soft, blurry, bokeh-like","appearance_details":"The background is intentionally out of focus, keeping attention on the cat while creating a cozy and serene mood.","relationship":"Provides a warm and gentle environment that supports the cute domestic scene.","orientation":"upright","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":"","is_cluster":true,"number_of_objects":"numerous"}]}}' -n '{"universal_negative":{"visual_quality":["low quality","worst quality","blurry","pixelated","jpeg artifacts","low resolution","unstable color","color flicker","underexposed","overexposed","invisible subject","subject hidden in darkness"],"artistic_style":["painting","illustration","drawing","cartoon","3d render","cgi","sketch","digital art"],"composition_and_content":["text","watermark","signature","logo","subtitles","pillarboxed","side bars","portrait image in landscape frame"],"temporal_and_motion_stability":["flickering","jittery","motion blur","temporal inconsistency","warping","morphing","incoherent motion","unnatural movement","static object with sudden jump","frame-to-frame inconsistency"],"material_and_structure":["plastic-like glass","unrealistic texture","deformed bottle","liquid freezing improperly","distorted reflections"]}}' --diffusion-fa --offload-to-cpu --cfg-scale 3 --video-frames 33 -v
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\lingbot-video-dens-1.3b.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\wan_2.1_vae.safetensors -p '{"caption":{"comprehensive_description":"A lovely cat sits comfortably on a soft cushion near a sunlit window, looking calm, gentle, and adorable. The cat has soft fluffy fur, bright expressive eyes, small rounded ears, delicate whiskers, and a relaxed posture. Warm daylight falls across the cat from one side, creating soft highlights on the fur and a cozy glow around the scene. The background is softly blurred, showing hints of a peaceful indoor home environment with warm tones and gentle natural light. The overall atmosphere is cute, tender, serene, and photorealistic, emphasizing the cat''s charming appearance, soft texture, and affectionate presence.","camera_info":{"color":"Warm","frame_size":"Close Up","shot_type_angle":"Eye level","lens_size":"Medium Lens","composition":"Centered balanced","lighting":"Soft light","lighting_type":"Daylight"},"world_knowledge":[],"prominent_elements":[{"name":"lovely cat","description":"A cute and gentle domestic cat with soft fluffy fur, expressive eyes, and a calm relaxed presence.","location":"center of the frame","relative_size":"large","shape_and_color":"Small animal body with rounded face, triangular ears, bright eyes, and soft fur in warm natural tones","texture":"soft, fluffy, silky","appearance_details":"The cat has clean well-groomed fur, delicate whiskers, small ears, a cute nose, and bright attentive eyes. Its expression appears calm, affectionate, and slightly curious.","relationship":"Acts as the main subject and emotional focal point of the scene.","orientation":"facing the camera","pose":"sitting comfortably with a relaxed posture","expression":"gentle, adorable, calm, slightly curious","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"cat eyes","description":"Bright expressive eyes that give the cat a sweet and affectionate appearance.","location":"upper center of the cat face","relative_size":"small","shape_and_color":"Round almond-like eyes with glossy reflections","texture":"clear, glossy, reflective","appearance_details":"The eyes catch the soft daylight, creating small natural highlights that make the cat look vivid and alive.","relationship":"Enhance the emotional charm and cuteness of the cat.","orientation":"looking toward the camera","pose":"","expression":"soft and attentive","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"soft cushion","description":"A comfortable cushion or blanket where the cat is resting.","location":"bottom portion of the frame","relative_size":"medium","shape_and_color":"Soft rounded fabric surface in light warm neutral tones","texture":"plush, fabric, cozy","appearance_details":"The cushion gently supports the cat and adds a comfortable home-like feeling to the scene.","relationship":"Provides a cozy resting place for the cat.","orientation":"horizontal","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"sunlit indoor background","description":"A softly blurred indoor background with warm daylight and peaceful home atmosphere.","location":"behind the cat, filling the upper and side areas of the frame","relative_size":"large","shape_and_color":"Soft abstract shapes in warm beige, cream, and pale golden tones","texture":"soft, blurry, bokeh-like","appearance_details":"The background is intentionally out of focus, keeping attention on the cat while creating a cozy and serene mood.","relationship":"Provides a warm and gentle environment that supports the cute domestic scene.","orientation":"upright","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":"","is_cluster":true,"number_of_objects":"numerous"}]}}' -n '{"universal_negative":{"visual_quality":["low quality","worst quality","blurry","pixelated","jpeg artifacts","low resolution","unstable color","color flicker","underexposed","overexposed","invisible subject","subject hidden in darkness"],"artistic_style":["painting","illustration","drawing","cartoon","3d render","cgi","sketch","digital art"],"composition_and_content":["text","watermark","signature","logo","subtitles","pillarboxed","side bars","portrait image in landscape frame"],"temporal_and_motion_stability":["flickering","jittery","motion blur","temporal inconsistency","warping","morphing","incoherent motion","unnatural movement","static object with sudden jump","frame-to-frame inconsistency"],"material_and_structure":["plastic-like glass","unrealistic texture","deformed bottle","liquid freezing improperly","distorted reflections"]}}' --offload-to-cpu --cfg-scale 3 --video-frames 33 -v
```
+155
View File
@@ -0,0 +1,155 @@
# How to Use
LLaDA-Image is a 6B text-to-image and instruction-guided editing model. The denoiser is a
Lumina2/Z-Image-style NextDiT conditioned by a LLaDA2-MoE diffusion-LLM text encoder, and it
reuses the Flux.2 VAE. Two checkpoints are published: a 50-step base model and
LLaDA-Image-Turbo, a 4-step distilled model.
## Download weights
Four components are required: a transformer, a text encoder, a VAE, and a connectors file
holding the QueryFormer, the text projection and, for editing, the SigVQ image encoder.
The two published checkpoints are **not** interchangeable. LLaDA-Image-Turbo and LLaDA-Image
ship different transformers, text encoders, QueryFormers and text projections; only the VAE,
the SigVQ encoder and the tokenizer are shared. Mixing the two produces degraded output rather
than a clean error, so keep each checkpoint's files together.
Both need an external LLaDA2 `tokenizer.json`, which is not embedded in sd.cpp and is the same
file for either checkpoint. Take `tokenizer/tokenizer.json` from either repository and pass it
with `--tokenizer`. See [JSON tokenizers](tokenizers.md) for CLI and C API usage.
### LLaDA-Image-Turbo (4 steps)
Converted transformer, text encoder and pre-merged connectors are at
https://huggingface.co/fszontagh/LLaDA-Image-Turbo-GGUF:
- `llada-image-turbo-f16.gguf`
- `llada-image-turbo-text_encoder-q8_0.gguf`
- `llada-image-turbo-connectors.safetensors` for text to image, or
`llada-image-turbo-connectors-edit.safetensors`, which also carries the SigVQ encoder that
editing needs.
Other quantizations of the transformer and the text encoder are in the same repository.
The VAE comes from the original repository,
https://huggingface.co/inclusionAI/LLaDA-Image-Turbo: `vae/diffusion_pytorch_model.safetensors`,
referred to below as `llada_vae.safetensors`.
### LLaDA-Image (50 steps)
Converted transformer, text encoder and pre-merged connectors are at
https://huggingface.co/fszontagh/LLaDA-Image-GGUF:
- `llada-image-f16.gguf`
- `llada-image-text_encoder-q8_0.gguf`
- `llada-image-connectors.safetensors` for text to image, or
`llada-image-connectors-edit.safetensors`, which also carries the SigVQ encoder that editing
needs.
Other quantizations of the transformer and the text encoder are in the same repository.
The VAE comes from the original repository,
https://huggingface.co/inclusionAI/LLaDA-Image, and is the same file as the Turbo one.
### Converting the weights yourself
The transformer has to go in through `--diffusion-model` so that its tensor names keep the
prefix the loader expects, while the text encoder goes in through `-m`:
```bash
./bin/sd-cli -M convert --diffusion-model transformer/diffusion_pytorch_model.safetensors.index.json \
-o llada-image-f16.gguf --type f16
./bin/sd-cli -M convert -m text_encoder/model.safetensors.index.json \
-o llada-image-text_encoder-q8_0.gguf --type q8_0
```
### Building the connector file yourself
`--embeddings-connectors` takes one file, so the QueryFormer, the text projection and
(for editing) the SigVQ encoder have to be combined into a single Safetensors file, each
tensor name prefixed with its component name. Leaving `sigvq` out skips loading the 2.6 GB
encoder:
```python
from safetensors.torch import load_file, save_file
merged = {}
for prefix, path in [
("queryformer", "queryformer/diffusion_pytorch_model.safetensors"),
("text_projection", "text_projection/diffusion_pytorch_model.safetensors"),
("sigvq", "sigvq/diffusion_pytorch_model.safetensors"),
]:
for name, tensor in load_file(path).items():
merged[f"{prefix}.{name}"] = tensor
save_file(merged, "llada_connectors.safetensors")
```
## Examples
### Text to image
```bash
./bin/sd-cli \
--diffusion-model /path/to/llada-image-turbo-f16.gguf \
--llm /path/to/llada-image-turbo-text_encoder-q8_0.gguf \
--tokenizer /path/to/tokenizer.json \
--vae /path/to/llada_vae.safetensors \
--embeddings-connectors /path/to/llada-image-turbo-connectors.safetensors \
--prompt "a lovely cat holding a sign says 'llada.cpp'" \
--width 1024 \
--height 1024 \
--steps 4 \
--cfg-scale 1.0 \
--seed 42 \
--output output.png
```
<img width="256" alt="LLaDA-Image example" src="../assets/llada_image/example.png" />
### Image editing
```bash
./bin/sd-cli \
--diffusion-model /path/to/llada-image-turbo-f16.gguf \
--llm /path/to/llada-image-turbo-text_encoder-q8_0.gguf \
--tokenizer /path/to/tokenizer.json \
--vae /path/to/llada_vae.safetensors \
--embeddings-connectors /path/to/llada-image-turbo-connectors-edit.safetensors \
--ref-image /path/to/input.png \
--prompt "change the sign text to 'sd.cpp'" \
--width 1024 \
--height 1024 \
--steps 4 \
--cfg-scale 1.0 \
--output output.png
```
<img width="256" alt="LLaDA-Image edit example" src="../assets/llada_image/edit_example.png" />
See [edit.md](./edit.md) for the shared reference-image options. LLaDA-Image uses the
`llada_image` preset by default, resizing the reference image to the output width and height
before VAE encoding. SigVQ uses bilinear resizing to half the output resolution and inputs
normalized to `[-1, 1]`. CFG keeps the source latent in both branches and uses SigVQ features
only in the positive branch. Editing requires connectors that include the SigVQ weights.
## Notes
- Use 4 steps and `--cfg-scale 1.0` for LLaDA-Image-Turbo; the guidance is distilled away, so
a higher CFG degrades output and doubles the text encoder cost. The 50-step base model uses
`--steps 50 --cfg-scale 5`.
- Width and height are rounded up to a multiple of 16. For editing the reference pipeline
requires them to be divisible by 32.
- Edit the 50-step base model at 1024x1024. At 512x512 it returns the reference image almost
unchanged instead of applying the instruction; LLaDA-Image-Turbo edits correctly at both.
- Editing runs the reference and the target in one sequence, so it needs roughly twice the
tokens of text to image at the same size. On 12 GB, editing at 1024x1024 needs
flash attention; without it the diffusion graph does not fit.
- The weights total about 16 GB, but segmented execution streams them, so a much smaller
budget works. At 512x512, `--max-vram 6` costs almost nothing over unconstrained execution,
and `--max-vram 3` still produces byte-identical output at roughly 2.5x the time.
- `--scheduler` defaults to `llada_image`, which reproduces the reference Kumaraswamy sigma
grid. `--extra-sample-args uniform=1` selects the uniform grid instead.
- Prompt templating is handled automatically; pass a plain description.
- VQ-conditioned generation (`generation_mode="vq"`, where the text encoder decodes image
tokens before diffusion) is not implemented.
+1 -1
View File
@@ -24,7 +24,7 @@ LongCat uses quoted text for character-level text rendering. Put target text ins
### LongCat Image
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\LongCat-Image-Q4_K_M.gguf --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\Qwen2.5-VL-7B-Instruct-Q8_0.gguf -p "a lovely cat holding a sign says 'longcat.cpp'" --cfg-scale 5.0 --sampling-method euler --flow-shift 3 -v --offload-to-cpu --diffusion-fa
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\LongCat-Image-Q4_K_M.gguf --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\Qwen2.5-VL-7B-Instruct-Q8_0.gguf -p "a lovely cat holding a sign says 'longcat.cpp'" --cfg-scale 5.0 --sampling-method euler --flow-shift 3 -v --offload-to-cpu
```
<img alt="longcat example" src="../assets/longcat/example.png" />
+7 -7
View File
@@ -55,7 +55,7 @@ To run the text encoder quantized, convert it once with sd-cli:
### LTX-2.3 dev T2V
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "worst quality, low quality, blurry, distorted, artifacts" -W 1280 -H 720 --diffusion-fa --offload-to-cpu --video-frames 33 --fps 24 -o t2v.webm
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "worst quality, low quality, blurry, distorted, artifacts" -W 1280 -H 720 --offload-to-cpu --video-frames 33 --fps 24 -o t2v.webm
```
<video
@@ -67,7 +67,7 @@ To run the text encoder quantized, convert it once with sd-cli:
### LTX-2.3 dev I2V
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -W 1280 -H 720 --diffusion-fa --offload-to-cpu --video-frames 33 -i ..\assets\ernie_image\turbo_example.png -o i2v.webm
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -W 1280 -H 720 --offload-to-cpu --video-frames 33 -i ..\assets\ernie_image\turbo_example.png -o i2v.webm
```
<video
@@ -79,7 +79,7 @@ To run the text encoder quantized, convert it once with sd-cli:
### LTX-2.3 dev FLF2V
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors -p "glass flower blossom" --cfg-scale 6.0 --sampling-method euler -v -W 1280 -H 720 --diffusion-fa --offload-to-cpu --video-frames 33 --init-img ..\..\ComfyUI\input\start_image.png --end-img ..\..\ComfyUI\input\end_image.png -o flf2v.webm
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors -p "glass flower blossom" --cfg-scale 6.0 --sampling-method euler -v -W 1280 -H 720 --offload-to-cpu --video-frames 33 --init-img ..\..\ComfyUI\input\start_image.png --end-img ..\..\ComfyUI\input\end_image.png -o flf2v.webm
```
<video
@@ -95,7 +95,7 @@ LTX spatial latent upscale runs a model-backed x2 latent upsampler between the l
Put `ltx-2.3-spatial-upscaler-x2-1.1.safetensors` under the directory passed to `--hires-upscalers-dir`, then use the model name without path or extension in `--hires-upscaler`.
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors --hires-upscalers-dir ..\models\latent_upscale_models --hires-upscaler ltx-2.3-spatial-upscaler-x2-1.1 --hires --hires-steps 4 -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -W 640 -H 360 --diffusion-fa --offload-to-cpu --video-frames 33 -i ..\assets\ernie_image\turbo_example.png -o hires_i2v.webm
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors --hires-upscalers-dir ..\models\latent_upscale_models --hires-upscaler ltx-2.3-spatial-upscaler-x2-1.1 --hires --hires-steps 4 -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -W 640 -H 360 --offload-to-cpu --video-frames 33 -i ..\assets\ernie_image\turbo_example.png -o hires_i2v.webm
```
By default, the hires refine pass uses the main sampler and scheduler, then trims the second-pass sigma schedule by `--hires-denoising-strength` (`0.7` by default). To reproduce a ComfyUI-style explicit refine schedule, pass custom hires sigmas:
@@ -113,13 +113,13 @@ By default, the hires refine pass uses the main sampler and scheduler, then trim
### LTX-2.5 dev T2V
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf -p "A wide aerial shot of a red vintage convertible driving along a coastal cliff road at sunset, waves crashing below" --cfg-scale 3.0 --sampling-method euler -v -n "worst quality, low quality, blurry, distorted, artifacts" -W 1280 -H 720 --diffusion-fa --offload-to-cpu --video-frames 121 --fps 24 -o t2v.webm
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf -p "A wide aerial shot of a red vintage convertible driving along a coastal cliff road at sunset, waves crashing below" --cfg-scale 3.0 --sampling-method euler -v -n "worst quality, low quality, blurry, distorted, artifacts" -W 1280 -H 720 --offload-to-cpu --video-frames 121 --fps 24 -o t2v.webm
```
### LTX-2.5 dev I2V
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf -p "a lovely cat blinking slowly, gentle camera push in" --cfg-scale 3.0 --sampling-method euler -v -W 1280 -H 720 --diffusion-fa --offload-to-cpu --video-frames 121 -i ..\assets\ernie_image\turbo_example.png -o i2v.webm
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf -p "a lovely cat blinking slowly, gentle camera push in" --cfg-scale 3.0 --sampling-method euler -v -W 1280 -H 720 --offload-to-cpu --video-frames 121 -i ..\assets\ernie_image\turbo_example.png -o i2v.webm
```
### LTX-2.5 spatial latent upscale
@@ -129,7 +129,7 @@ Works exactly like the LTX-2.3 upscaler described below; put
pass its name without path or extension to `--hires-upscaler`.
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf --hires-upscalers-dir ..\models\latent_upscale_models --hires-upscaler ltx-2.5-latent-spatial-upscaler-x2-bf16-1.0 --hires --hires-steps 6 -p "a lovely cat" --cfg-scale 3.0 --sampling-method euler -v -W 640 -H 360 --diffusion-fa --offload-to-cpu --video-frames 121 -o hires_t2v.webm
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf --hires-upscalers-dir ..\models\latent_upscale_models --hires-upscaler ltx-2.5-latent-spatial-upscaler-x2-bf16-1.0 --hires --hires-steps 6 -p "a lovely cat" --cfg-scale 3.0 --sampling-method euler -v -W 640 -H 360 --offload-to-cpu --video-frames 121 -o hires_t2v.webm
```
## Not implemented
+4 -16
View File
@@ -4,20 +4,8 @@
## Download weights
- Download Mage-Flow
- safetensors: https://huggingface.co/microsoft/Mage-Flow/tree/main/transformer
- Download Mage-Flow-Base
- safetensors: https://huggingface.co/microsoft/Mage-Flow-Base/tree/main/transformer
- Download Mage-Flow-Turbo
- safetensors: https://huggingface.co/microsoft/Mage-Flow-Turbo/tree/main/transformer
- Download Mage-Flow-Edit
- safetensors: https://huggingface.co/microsoft/Mage-Flow-Edit/tree/main/transformer
- Download Mage-Flow-Edit-Turbo
- safetensors: https://huggingface.co/microsoft/Mage-Flow-Edit-Turbo/tree/main/transformer
- Download Mage-Flow-Edit-Base
- safetensors: https://huggingface.co/microsoft/Mage-Flow-Edit-Base/tree/main/transformer
- Download Mage-Flow vae
- safetensors: https://huggingface.co/microsoft/Mage-Flow/tree/main/vae
- Download Mage-Flow diffusion from https://huggingface.co/Comfy-Org/Mage-Flow/tree/main/diffusion_models
- Download Mage-Flow vae from https://huggingface.co/Comfy-Org/Mage-Flow/tree/main/vae
- Download Qwen3-VL 4B
- safetensors: https://huggingface.co/Comfy-Org/Krea-2/tree/main/text_encoders
- gguf: https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct-GGUF/tree/main
@@ -27,7 +15,7 @@
Use 30 steps for Base models and 4 steps with `--cfg-scale 1` for Turbo models. Image dimensions must be multiples of 16; the official checkpoints are trained for native resolutions from 512 to 2048 pixels.
```bash
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Mage-Flow-Turbo.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\mage_vae.safetensors -p "a lovely cat holding a sign says 'mage.cpp'" --cfg-scale 1.0 --steps 4 --diffusion-fa -v --offload-to-cpu
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Mage-Flow-Turbo.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\mage_vae.safetensors -p "a lovely cat holding a sign says 'mage.cpp'" --cfg-scale 1.0 --steps 4 -v --offload-to-cpu
```
<img width="256" alt="Mage-Flow example" src="../assets/mage_flow/example.png" />
@@ -39,7 +27,7 @@ Mage-Flow-Edit accepts one or more reference images. The default `mage_flow` ref
For the Turbo edit checkpoint, use 4 steps and `--cfg-scale 1`.
```bash
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Mage-Flow-Edit.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --llm_vision ..\models\text_encoders\Qwen3-VL-4B-Instruct-mmproj-BF16.gguf --vae ..\models\vae\mage_vae.safetensors -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'mage.cpp'" --cfg-scale 4.0 --sampling-method euler -v --diffusion-fa --offload-to-cpu
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Mage-Flow-Edit.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --llm_vision ..\models\text_encoders\Qwen3-VL-4B-Instruct-mmproj-BF16.gguf --vae ..\models\vae\mage_vae.safetensors -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'mage.cpp'" --cfg-scale 4.0 --sampling-method euler -v --offload-to-cpu
```
<img width="256" alt="Mage-Flow-Edit example" src="../assets/mage_flow/edit_example.png" />
+24
View File
@@ -0,0 +1,24 @@
# Ming-Image
[Ming-Image](https://github.com/inclusionAI/Ming-Image) 0.1 Design uses a 6B diffusion transformer (DiT), Ling-mini-2.0 for text conditioning, and the Ming-Image VAE. Text-to-image generation with RGBA output is supported.
## Download weights
- Download Ming-Image 0.1 Design DiT
- safetensors: https://huggingface.co/Comfy-Org/Ming-Image/tree/main/diffusion_models
- Download Ling-mini-2.0 BF16
- safetensors: https://huggingface.co/Comfy-Org/Ming-Image/tree/main/text_encoders
- Download Ming-Image VAE
- safetensors: https://huggingface.co/Comfy-Org/Ming-Image/tree/main/vae
- Download Ling tokenizer
- tokenizer.json: https://huggingface.co/inclusionAI/Ming-Image-0.1-Design/blob/main/mllm/tokenizer.json
The example below uses `ming_image_0.1_design_bf16.safetensors` for the DiT. You can also use `ming_image_0.1_design_int8_convrot.safetensors` with [INT8 convrot support](int8_convrot.md). Use the BF16 text encoder.
## Text-to-image
Pass the Ling `tokenizer.json` with `--tokenizer` and use the matching Ming-Image VAE. Image dimensions must be multiples of 16. Save the output as PNG to preserve the alpha channel.
```bash
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\ming_image_0.1_design_bf16.safetensors --llm ..\models\text_encoders\ming_image_0.1_ling_mini_2.0_bf16.safetensors --vae ..\models\vae\ming_image_vae_bf16.safetensors --tokenizer ..\models\text_encoders\tokenizer.json -p "A cheerful orange cat sticker, transparent background" --width 1024 --height 1024 --steps 12 --cfg-scale 1 --sampling-method euler -v --offload-to-cpu -o ming_image.png
```
+3 -3
View File
@@ -38,7 +38,7 @@ are detected from their weights.
## Text-to-audio-video
```sh
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\minimax_h3_fl2va-Q4_K_M.gguf --vae ..\models\vae\minimax_h3_video_vae_fp16.safetensors --audio-vae ..\models\vae\minimax_h3_audio_vae_fp32.safetensors --llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf -p "A cute American Shorthair silver tabby kitten surfs on a tropical ocean wave, riding a white surfboard with the clear text 'sd.cpp' on it. Cinematic tracking shot, realistic water, bright sunlight, smooth motion, and consistent character appearance. Add upbeat tropical surf-rock background music with cheerful drums and guitar, synchronized with the kitten’s energetic surfing." --cfg-scale 1.0 -v -W 864 -H 480 --diffusion-fa --offload-to-cpu --rng cpu --fps 24 --video-frames 56
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\minimax_h3_fl2va-Q4_K_M.gguf --vae ..\models\vae\minimax_h3_video_vae_fp16.safetensors --audio-vae ..\models\vae\minimax_h3_audio_vae_fp32.safetensors --llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf -p "A cute American Shorthair silver tabby kitten surfs on a tropical ocean wave, riding a white surfboard with the clear text 'sd.cpp' on it. Cinematic tracking shot, realistic water, bright sunlight, smooth motion, and consistent character appearance. Add upbeat tropical surf-rock background music with cheerful drums and guitar, synchronized with the kitten’s energetic surfing." --cfg-scale 1.0 -v -W 864 -H 480 --offload-to-cpu --rng cpu --fps 24 --video-frames 56
```
<video src=../assets/minimax-h3/t2av.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -51,7 +51,7 @@ decoded audio track.
Add `--init-img` for I2VA, or both `--init-img` and `--end-img` for FL2VA:
```sh
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\minimax_h3_fl2va-Q4_K_M.gguf --vae ..\models\vae\minimax_h3_video_vae_fp16.safetensors --audio-vae ..\models\vae\minimax_h3_audio_vae_fp32.safetensors --llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf -p "a lovely cat" -i ..\assets\ernie_image\turbo_example.png --cfg-scale 1.0 -v -W 864 -H 480 --diffusion-fa --offload-to-cpu --rng cpu --fps 24 --video-frames 56
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\minimax_h3_fl2va-Q4_K_M.gguf --vae ..\models\vae\minimax_h3_video_vae_fp16.safetensors --audio-vae ..\models\vae\minimax_h3_audio_vae_fp32.safetensors --llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf -p "a lovely cat" -i ..\assets\ernie_image\turbo_example.png --cfg-scale 1.0 -v -W 864 -H 480 --offload-to-cpu --rng cpu --fps 24 --video-frames 56
```
<video src=../assets/minimax-h3/i2av.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -62,7 +62,7 @@ Ref2VA accepts any combination of reference images, reference videos, paired
video soundtracks, and standalone audio references:
```sh
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\minimax_h3_ref2va_pruned-Q4_K_M.gguf --vae ..\models\vae\minimax_h3_video_vae_fp16.safetensors --audio-vae ..\models\vae\minimax_h3_audio_vae_fp32.safetensors --llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf -p "Use the cat from <Picture 1> as the main character. Keep the cat’s appearance, fur color, facial features, and identity consistent with the reference image. Create a 2-second cinematic video: start with an extreme close-up shot of the cat’s face, focusing on its cute expression and detailed fur texture. The camera slowly rotates around the cat’s head, creating a dynamic reveal. Then smoothly pull back and zoom out to reveal the full scene: the cat is standing confidently on a surfboard, riding ocean waves. Water splashes around the board, sea breeze gently moves the cat’s fur, and the cat maintains a cute and fearless expression while surfing. Smooth camera movement, cinematic orbit shot, seamless zoom-out transition, low-angle wide shot, realistic ocean environment, golden sunlight, dynamic waves, high-quality realistic style, natural motion, no distortion, keep the cat’s identity unchanged." -r ..\assets\ernie_image\turbo_example.png --cfg-scale 1.0 -v -W 864 -H 480 --diffusion-fa --offload-to-cpu --rng cpu --fps 24 --video-frames 56
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\minimax_h3_ref2va_pruned-Q4_K_M.gguf --vae ..\models\vae\minimax_h3_video_vae_fp16.safetensors --audio-vae ..\models\vae\minimax_h3_audio_vae_fp32.safetensors --llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf -p "Use the cat from <Picture 1> as the main character. Keep the cat’s appearance, fur color, facial features, and identity consistent with the reference image. Create a 2-second cinematic video: start with an extreme close-up shot of the cat’s face, focusing on its cute expression and detailed fur texture. The camera slowly rotates around the cat’s head, creating a dynamic reveal. Then smoothly pull back and zoom out to reveal the full scene: the cat is standing confidently on a surfboard, riding ocean waves. Water splashes around the board, sea breeze gently moves the cat’s fur, and the cat maintains a cute and fearless expression while surfing. Smooth camera movement, cinematic orbit shot, seamless zoom-out transition, low-angle wide shot, realistic ocean environment, golden sunlight, dynamic waves, high-quality realistic style, natural motion, no distortion, keep the cat’s identity unchanged." -r ..\assets\ernie_image\turbo_example.png --cfg-scale 1.0 -v -W 864 -H 480 --offload-to-cpu --rng cpu --fps 24 --video-frames 56
```
<video src=../assets/minimax-h3/r2av.mp4 controls="controls" muted="muted" type="video/mp4"></video>
-1
View File
@@ -43,6 +43,5 @@ MiniT2I uses a MiniT2I diffusion transformer and `google/flan-t5-large` as the t
--height 512 \
--seed 42 \
--sampling-method euler \
--diffusion-fa \
--output minit2i_cuda.png
```
+1 -1
View File
@@ -13,7 +13,7 @@
## Examples
```
.\bin\Release\sd-cli.exe --diffusion-model ovis_image-Q4_0.gguf --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\ovis_2.5.safetensors -p "a lovely cat" --cfg-scale 5.0 -v --offload-to-cpu --diffusion-fa
.\bin\Release\sd-cli.exe --diffusion-model ovis_image-Q4_0.gguf --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\ovis_2.5.safetensors -p "a lovely cat" --cfg-scale 5.0 -v --offload-to-cpu
```
<img alt="ovis image example" src="../assets/ovis_image/example.png" />
+65 -1
View File
@@ -8,7 +8,7 @@ eg.:
For most backends, it slows things down, but for cuda it generally speeds it up too.
At the moment, it is only supported for some models and some backends (like cpu, cuda/rocm, metal).
Run by adding `--diffusion-fa` to the arguments and watch for:
Watch for:
```
[INFO ] stable-diffusion.cpp:312 - Using flash attention in the diffusion model
```
@@ -21,6 +21,64 @@ CPU fallback. It excludes weights and cache buffers. Within a runner lifecycle,
the summary is printed only on the first graph or when backend capacities or the
segment count change.
## Run conditional and unconditional CFG in one batched UNet forward.
For UNet models, the conditional and unconditional guidance branches are
concatenated into a single batch of two and run through one UNet forward per
step instead of two separate forwards. This is enabled by default whenever the
run qualifies for it.
Use `--batched-cfg off` to force separate conditional and unconditional
forwards.
## Use VAE tiling to reduce encode and decode memory usage.
`--vae-tiling` enables spatial tiling for both VAE encoding and decoding. The
default tile size is 256x256 **image pixels**, independent of the VAE scale factor:
```shell
--vae-tiling --vae-tile-size 256x256 --vae-tile-overlap 0.5
```
`--vae-tile-size` accepts one size or `WIDTHxHEIGHT`. A zero dimension uses the
256-pixel default. Sizes are rounded down to a multiple of the VAE scale factor
and capped at the current input dimensions. Explicit sizes below four latent
pixels per axis (or the full axis when it is smaller) are rejected. Encoding and
decoding use the same spatial sizes, without an additional encoding multiplier.
Inputs that fit within a tile are processed as one tile.
For a 512x512 image with the default 50% overlap, both encoding and decoding use
3x3 tiles. A 256-pixel tile corresponds to 32 latent pixels for an 8x VAE, 16 for
a 16x VAE, and 8 for a 32x VAE. Smaller tiles reduce each graph's memory demand,
but overlapping work can increase processing time and tiling can affect image
quality, especially during encoding. Use larger tiles when more context is needed.
`--vae-relative-tile-size` overrides the absolute size on each axis with a positive
value. Values up to and including 1 specify a fraction of the current input size;
values greater than 1 specify a target number of tiles per axis, accounting for
overlap. For example, `0.5x0.5` uses half the width and height in both encode and
decode. The target overlap is clamped to 0 through 0.5 and the actual overlap is
adjusted to fit the image. Size and overlap options require `--vae-tiling`.
**Migration:** `--vae-tile-size` and the C/JSON fields `tile_size_w` and
`tile_size_h` now use image pixels instead of latent units. The C/JSON fields
`tile_size_x/y` have been renamed to `tile_size_w/h`, and `rel_size_x/y` to
`rel_size_w/h`. The command-line option names are unchanged. For example, an old
decode tile size of 32 corresponds to 256 pixels for an 8x VAE or 512 pixels for a
16x VAE. Encoding no longer enlarges explicit or relative tile sizes.
The main VAE decode path retries allocation failures with smaller tiles, even
without `--vae-tiling`. Supported video VAEs first try temporal tiling; spatial
retries use at most 256-pixel tiles initially and then halve the effective tile
dimensions down to the minimum size. Each spatial retry must reduce the effective
tile size. These runtime adjustments do not change the caller's parameters.
Execution failures are not retried, and encoding has no automatic OOM retry.
`--temporal-tiling` remains independent of spatial tiling. MiniMax H3 always uses
spatial tiling (256x256 pixels and 25% overlap by default) and its own temporal
windows. With `--vae-tiling`, its overlap follows `--vae-tile-overlap`; explicit
spatial sizes are honored.
## Offload weights to the CPU to save VRAM without reducing generation speed.
Using `--offload-to-cpu` allows you to offload weights to the CPU, saving VRAM without reducing generation speed.
@@ -63,6 +121,12 @@ See [backend selection](./backend.md) for full syntax.
When a graph has cut markers and its missing weights plus incremental compute workspace exceed the available device headroom, it runs its fixed segment list in order. A reusable monolithic compute buffer is not counted as a new allocation. An explicit `--max-vram` budget deducts already-resident managed weights and compute/cache buffers registered by every runner sharing the device, so later graph runs remain segmented when the full graph exceeds the budget. The current segment's weights are pinned during compute, and the next parameter-bearing segment is prefetched when the device supports asynchronous transfer. No opt-in streaming flag is required.
When choosing between monolithic and segmented execution, the runner requires
an additional 128 MiB of headroom in both available device memory and any explicit
managed budget. This planning headroom absorbs small allocation estimate changes;
subsequent capacity checks can consume it while still preserving the 512 MiB device
scratch reserve and respecting the managed budget.
- `--max-vram <GiB>` optionally lowers the live-memory limit. A positive value is a managed per-device budget, `0` uses the device's current free memory without an explicit budget, and a negative value snapshots free memory at startup while reserving that many GiB (`--max-vram -1` reserves about 1 GiB). Driver contexts and unrelated external allocations remain outside the managed budget.
- `--disable-prefetch` disables asynchronous next-segment prefetch while retaining synchronous loading, eviction, and segmented execution.
- `--disable-segmented-compute` forces monolithic graph execution for diagnostics or compatibility, even when the automatic memory check would select segments.
+1 -1
View File
@@ -27,7 +27,7 @@ PiD and PiD 1.5 require an external Gemma 2 `tokenizer.json` matching the text e
## Examples
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\pid_flux1_512_to_2048_4step_bf16.safetensors --llm "..\models\text_encoders\gemma_2_2b_it_elm_bf16.safetensors" --tokenizer ..\models\tokenizers\tokenizer_gemma2.json --vae ..\models\vae\ae.sft --vae-format flux --cfg-scale 1.0 -p "a lovely cat" -r ..\assets\ernie_image\turbo_example.png --diffusion-fa -v --steps 4 -H 2048 -W 2048 --rng cpu
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\pid_flux1_512_to_2048_4step_bf16.safetensors --llm "..\models\text_encoders\gemma_2_2b_it_elm_bf16.safetensors" --tokenizer ..\models\tokenizers\tokenizer_gemma2.json --vae ..\models\vae\ae.sft --vae-format flux --cfg-scale 1.0 -p "a lovely cat" -r ..\assets\ernie_image\turbo_example.png -v --steps 4 -H 2048 -W 2048 --rng cpu
```
Before:
+46
View File
@@ -0,0 +1,46 @@
# How to Use
You can run PixArt-α / PixArt-Σ with stable-diffusion.cpp.
PixArt is a DiT-based text-to-image model family conditioned by a T5-XXL text
encoder and a 4-channel VAE: SDXL-style for PixArt-Σ and SD1.x-style for PixArt-α.
## Download weights
- Download the transformer (diffusion model)
- PixArt-Σ XL-2 1024-MS: https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS/tree/main/transformer
- PixArt-α XL-2 1024-MS: https://huggingface.co/PixArt-alpha/PixArt-XL-2-1024-MS/tree/main/transformer
- Download the T5-XXL text encoder
- safetensors: https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS/tree/main/text_encoder
- Download the VAE
- PixArt-Σ: https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS/tree/main/vae
- PixArt-α: https://huggingface.co/PixArt-alpha/PixArt-XL-2-1024-MS/tree/main/vae
- Use the VAE matching the checkpoint's latent space. For TAE decoding or
preview, use TAESDXL for PixArt-Σ and TAESD for PixArt-α.
- Tokenizer: the T5 vocabulary is embedded; no extra tokenizer file is needed.
## Examples
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\pixart_sigma_xl2_1024_ms.safetensors --t5xxl ..\models\text_encoders\t5xxl.safetensors --vae ..\models\vae\pixart_vae.safetensors -p "a lovely cat" --cfg-scale 4.5 -W 1024 -H 1024 --steps 20 -v
```
## Notes
- The VAE scaling factor defaults to `0.13025` for PixArt-Σ. PixArt-α
checkpoints with resolution micro-condition weights use `0.18215`.
PixArt-α 512 has the same tensor layout as PixArt-Σ, so it requires an
explicit override: `--model-args "pixart_vae_scale_factor=0.18215"`.
This argument can also override the scale for other compatible checkpoints.
- PixArt-Σ checkpoints compute 2D sincos positional embeddings at runtime;
the trained grid is 64x64 patches with an interpolation scale of 2.
For checkpoints trained at a different resolution, the positional embedding
parameters can be adjusted via model args:
`--model-args "pixart_pos_embed_base_size=<trained grid>,pixart_interpolation_scale=<scale>"`
(e.g. `pixart_pos_embed_base_size=32,pixart_interpolation_scale=1,pixart_vae_scale_factor=0.18215` for
PixArt-α XL-2 512).
- Checkpoints carrying resolution/aspect-ratio micro-condition weights are
detected but those conditions are not applied yet; a warning is logged and
generation proceeds with the timestep embedding only.
- The transformer predicts 8 channels (noise + learned variance); only the
noise half is used for sampling, matching the reference implementation.
+1 -1
View File
@@ -14,7 +14,7 @@
## Examples
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen-image-Q8_0.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\Qwen2.5-VL-7B-Instruct-Q8_0.gguf -p '一个穿着"QWEN"标志的T恤的中国美女正拿着黑色的马克笔面相镜头微笑。她身后的玻璃板上手写体写着 “一、Qwen-Image的技术路线: 探索视觉生成基础模型的极限,开创理解与生成一体化的未来。二、Qwen-Image的模型特色:1、复杂文字渲染。支持中英渲染、自动布局; 2、精准图像编辑。支持文字编辑、物体增减、风格变换。三、Qwen-Image的未来愿景:赋能专业内容创作、助力生成式AI发展。”' --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu -H 1024 -W 1024 --diffusion-fa --flow-shift 3
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen-image-Q8_0.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\Qwen2.5-VL-7B-Instruct-Q8_0.gguf -p '一个穿着"QWEN"标志的T恤的中国美女正拿着黑色的马克笔面相镜头微笑。她身后的玻璃板上手写体写着 “一、Qwen-Image的技术路线: 探索视觉生成基础模型的极限,开创理解与生成一体化的未来。二、Qwen-Image的模型特色:1、复杂文字渲染。支持中英渲染、自动布局; 2、精准图像编辑。支持文字编辑、物体增减、风格变换。三、Qwen-Image的未来愿景:赋能专业内容创作、助力生成式AI发展。”' --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu -H 1024 -W 1024 --flow-shift 3
```
<img alt="qwen example" src="../assets/qwen/example.png" />
+99
View File
@@ -0,0 +1,99 @@
# How to Use
Qwen Image 2.1 supports text-to-image generation and image editing, using Qwen3-VL-8B as the text encoder and its own VAE.
## Download weights
- Download Qwen Image 2.1
- safetensors: https://huggingface.co/Comfy-Org/Qwen-Image-2.1/tree/main/diffusion_models
- gguf: https://huggingface.co/leejet/Qwen-Image-2.1-GGUF/tree/main
- Download vae
- safetensors: https://huggingface.co/Comfy-Org/Qwen-Image-2.1/tree/main/vae
- Download Qwen3-VL-8B-Instruct
- safetensors (BF16 or INT8 convrot): https://huggingface.co/Comfy-Org/Qwen-Image-2.1/tree/main/text_encoders
- gguf: https://huggingface.co/Qwen/Qwen3-VL-8B-Instruct-GGUF/tree/main
- For image editing with a GGUF text encoder, also download `mmproj-Qwen3VL-8B-Instruct-F16.gguf` from the same repository and pass it with `--llm_vision`.
Use `qwen_image_2.1_vae_bf16.safetensors` with this model. The earlier Qwen Image and Wan 2.2 VAE weights are not interchangeable with the Qwen Image 2.1 VAE weights.
For faster encoding, decoding, or previews, see [TAEQI2.1](taesd.md#qwen-image-21-taeqi21).
## Examples
Run the following commands from the build directory. Use image dimensions divisible by 32. The resolution-dependent flow schedule is selected automatically.
### Text to image
```powershell
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen_image_2.1_int8_convrot.safetensors --vae ..\models\vae\qwen_image_2.1_vae_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf -p "a lovely cat holding a sign says 'qwen2.1.cpp'" --cfg-scale 6.0 --sampling-method euler -v --offload-to-cpu -o qwen_image_2.1.png
```
<img alt="Qwen Image 2.1 example" src="../assets/qwen/qwen_image_2.1.png" />
To use GGUF diffusion weights, set `--diffusion-model` to the path of a file such as `qwen_image_2.1-Q4_K.gguf`.
### Turbo
For [Qwen Image 2.1 Turbo](https://huggingface.co/Qwen/Qwen-Image-2.1-Turbo), use the Turbo diffusion weights and select its fixed 8-step sigma schedule with `--sigmas qwen-image-2.1-turbo`. For example, add these sampling options to the command above:
```text
--sigmas qwen-image-2.1-turbo --steps 8 --cfg-scale 1.0 --sampling-method euler
```
The preset is equivalent to `--sigmas 1.0,0.978453,0.95418,0.926626,0.89508,0.845148,0.704534,0.414568,0.0`. It only supplies the sigma values; CFG scale and sampling method remain independently configurable. As with an explicit sigma list, the sigma count determines the actual number of sampling steps. The same preset name is also accepted by `--hires-sigmas` for the highres fix second pass.
### Image editing
Pass the reference image with `-r` and describe the edit in `-p`. Vision weights are required; the example below loads them separately with `--llm_vision`.
```powershell
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen_image_2.1_int8_convrot.safetensors --vae ..\models\vae\qwen_image_2.1_vae_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --llm_vision ..\models\text_encoders\Qwen3VL-8B-Instruct-mmproj-BF16.gguf -r ..\assets\qwen\qwen_image_2.1.png -p "change 'qwen2.1.cpp' to 'sd.cpp'" --cfg-scale 6.0 --sampling-method euler -v --offload-to-cpu -o qwen_image_2.1_edit.png
```
For multiple reference images, repeat `-r` in the desired order, for example `-r first.png -r second.png`.
### Prefix cache
By default, the first denoising call for each fixed condition saves the text and reference-image keys and values from every transformer layer. Later calls only compute the target-image tokens. Positive and negative conditions use separate caches, which are released when sampling ends.
Set `qwen_image_2_1_prefix_cache_type` in `--model-args` to `auto` or a type name using the same parser and case-sensitive names as `--type`:
- `auto` (default): use FP16 only when Flash Attention is enabled, Sage Attention is disabled, the attention scale is unchanged, and every attention operation in the cache-writing or cache-reading graph selects Flash Attention after backend support checks. If an operation falls back, rebuild the prefix in FP32 before executing and keep FP32 for the rest of that sampling run.
- `f32`: always store FP32 keys and values.
- `f16`: always store FP16 keys and values, including with ordinary attention or custom attention scaling. This saves cache memory but can introduce additional rounding error.
- Other types, such as `bf16`, `q4_1`, `q5_0`, `q5_1`, `q8_0`, `q4_K`, `q6_K`, `iq4_nl`, and `iq4_xs`: use the requested storage type if the ggml build provides runtime conversion to and from FP32. Quantization is lossy and must be selected explicitly; `auto` never selects a quantized type.
Cache data is packed into contiguous rows of `hidden_size` elements before conversion, so 256-element quantization blocks work with the model's 128-element attention heads without padding. The type's block size must divide `hidden_size`. Unknown types, types lacking runtime conversion (for example `q8_1` and several IQ formats), and incompatible block sizes are ignored with a warning, leaving the previous setting or the default `auto` unchanged.
For example, use `--model-args qwen_image_2_1_prefix_cache_type=q8_0` to enable 8-bit cache storage. Cached keys and values are converted back to the attention input type before concatenating with the current target tokens. This reduces persistent cache memory; attention working buffers still use floating-point values, and conversion adds work on each step. Backends without the required conversion operations use the existing CPU fallback.
For the default 32-layer model, a prefix of 4096 tokens takes approximately the following memory per condition, excluding weights, working buffers, and allocation overhead:
| Cache type | Memory |
| --- | ---: |
| `f32` | 4 GiB |
| `f16` | 2 GiB |
| `q8_0` | 1.0625 GiB |
| `q4_0` | 0.5625 GiB |
The runner accounts for the cache when checking the memory budget. If a cached execution runs out of memory, it releases the prefix caches, disables caching for the rest of that sampling run, and retries the full sequence once. Per-step conditioning extensions currently use the full-sequence path.
Disable this optimization with `--model-args qwen_image_2_1_prefix_cache=false`. It reuses step-independent activations; numerical results can still differ slightly because the matrix sizes change.
### Alpha channel
This model supports alpha channel output. As the model determines whether to output a regular image or with transparency through the prompt, according to [official recommendation](https://github.com/QwenLM/Qwen-Image-2.1#transparent-image-generation-rgba), use the following prompt format for better results:
> `This is an RGBA image with transparency. <your description>. The image has alpha channel and the background is transparent.`
Since transparency is decided by the prompt rather than by the input or an explicit switch, the same format applies equally to editing, whether or not the reference image itself has an alpha channel. Note that alpha is kept only in `.png` and `.webp` outputs; saving as `.jpg` drops the transparency.
Here are some examples ran with Q6_K quantization:
| Input | Prompt | Output |
| --- | --- | --- |
| ![Qwen Image 2.1 alpha input example 1](../assets/qwen/qwen-image-2.1-alpha-in1.png) | This is an RGBA image with transparency. Replace the text "BLOOM" with "Qwen Image 2.1", keeping the same font of the original text. The image has alpha channel and the background is transparent. | ![Qwen Image 2.1 alpha output example 1](../assets/qwen/qwen-image-2.1-alpha-out1.png) |
| ![Qwen Image 2.1 alpha input example 2](../assets/logo.png) | This is an RGBA image with transparency. Remove the background of the image, keeping only the text and cat. The image has alpha channel and the background is transparent. | ![Qwen Image 2.1 alpha output example 2](../assets/qwen/qwen-image-2.1-alpha-out2.png) |
### Other features
Other features of the model could be found on the [model card from QwenLM/Qwen-Image-2.1 repo](https://github.com/QwenLM/Qwen-Image-2.1), including 2 finetuned prompt rewriting Qwen3.5-9B model.
+3 -3
View File
@@ -23,7 +23,7 @@
### Qwen Image Edit
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Qwen_Image_Edit-Q8_0.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --diffusion-fa --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'edit.cpp'" --seed 1118877715456453
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Qwen_Image_Edit-Q8_0.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'edit.cpp'" --seed 1118877715456453
```
<img alt="qwen_image_edit" src="../assets/qwen/qwen_image_edit.png" />
@@ -32,7 +32,7 @@
### Qwen Image Edit 2509
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Qwen-Image-Edit-2509-Q4_K_S.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\Qwen2.5-VL-7B-Instruct-Q8_0.gguf --llm_vision ..\models\text_encoders\Qwen2.5-VL-7B-Instruct.mmproj-Q8_0.gguf --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --diffusion-fa --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'Qwen Image Edit 2509'"
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Qwen-Image-Edit-2509-Q4_K_S.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\Qwen2.5-VL-7B-Instruct-Q8_0.gguf --llm_vision ..\models\text_encoders\Qwen2.5-VL-7B-Instruct.mmproj-Q8_0.gguf --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'Qwen Image Edit 2509'"
```
<img alt="qwen_image_edit_2509" src="../assets/qwen/qwen_image_edit_2509.png" />
@@ -42,7 +42,7 @@
To use the new Qwen Image Edit 2511 mode, `--model-args qwen_image_zero_cond_t=true` must be enabled; otherwise, image editing quality will degrade significantly.
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen-image-edit-2511-Q4_K_M.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --diffusion-fa --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'edit.cpp'" --model-args qwen_image_zero_cond_t=true
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen-image-edit-2511-Q4_K_M.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'edit.cpp'" --model-args qwen_image_zero_cond_t=true
```
<img alt="qwen_image_edit_2509" src="../assets/qwen/qwen_image_edit_2511.png" />
+66
View File
@@ -0,0 +1,66 @@
# SageAttention
`--sage-attn` enables native CUDA SageAttention in the diffusion model, including
the high-noise diffusion model when present. Python, PyTorch, and Triton are not
required at build time or runtime.
The CUDA backend automatically selects a kernel supported by both the GPU and
the compiled CUDA toolkit:
| GPU / toolkit | Implementation |
| --- | --- |
| SM89 or newer, CUDA 12.8 or newer (except SM90) | SageAttention2++: per-thread INT8 Q/K, FP8 PV, FP16 instruction accumulation with an FP32 buffer |
| SM89 or newer, CUDA 12.4 or newer; SM90 also uses this path with newer toolkits | SageAttention2: per-thread INT8 Q/K, FP8 PV, two-level FP32 accumulation |
| SM80 or newer, CUDA 12.0 or newer | INT8 Q/K, FP16 PV compatibility path |
The FP8 paths smooth K, quantize V per channel, and pad and permute V for FP8
Tensor Cores. The 2++ path uses the upstream V scale limit of 2.25 to avoid
overflow in its FP16 instruction accumulator. The public output remains FP32.
These are the upstream **INT8** SageAttention2/2++ variants; the paper's INT4
variant and Hopper-specific WGMMA kernel are not implemented here.
## Build
Use the bundled patched GGML, CUDA Toolkit 12.0 or newer, and an NVIDIA GPU with
compute capability 8.0 or newer. Compile kernels for the GPU being used.
```sh
cmake -S . -B build -DSD_CUDA=ON -DSD_USE_UPSTREAM_GGML=OFF
cmake --build build --config Release
```
No separate SageAttention build option is needed. Upstream GGML builds do not
support it. A system GGML must include the matching patched API and CUDA
backend. Enabling `--sage-attn` with an unavailable build or diffusion device
reports an error. Building with CUDA 12.4 selects SageAttention2 on an RTX 4090;
rebuild with CUDA 12.8 or newer to use SageAttention2++.
## Use
Add `--sage-attn` to enable SageAttention. For example,
from the build directory:
```powershell
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-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 --offload-to-cpu --video-frames 33 --sage-attn
```
SageAttention currently handles unmasked attention with head dimensions from
1 through 128, including grouped-query attention, different query/key lengths,
and multiple batches. Dimensions below 64 are zero-padded to 64; dimensions
between 65 and 127 are zero-padded to 128. The original softmax scale is preserved,
and the output is cropped back to the original dimension. Other attention
operations fall back to FlashAttention when supported, then ordinary attention.
SageAttention takes precedence over FlashAttention in diffusion. Existing attention scaling overrides remain effective.
Attention quantization changes numerical results. Compare image quality and
end-to-end generation time using the same seed, dimensions, and sampling
settings. Compare sampling steps after the first step for warmed-up inference
speed, and report model loading and first-step initialization separately.
Quantization, smoothing, and format conversion costs are included in generation
time, so short sequences may not benefit.
Library callers set `sd_ctx_params_t.sage_attn = true` before `new_sd_ctx()`,
like `diffusion_flash_attn`. Context creation fails if the requested feature is
unavailable. Initialize the parameter structure with `sd_ctx_params_init()`.
Rebuild library callers against the updated public header.
+3 -3
View File
@@ -32,19 +32,19 @@ The dispatcher picks `alpha` from the filename (`turbo` substring => 1.0, otherw
### 1B / 2B turbo
```
./build/bin/sd-cli --diffusion-model /path/to/sefi_1b_turbo.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_2b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 1.0 --steps 4 -W 1024 -H 1024 -s 42 --diffusion-fa --offload-to-cpu -o out.png
./build/bin/sd-cli --diffusion-model /path/to/sefi_1b_turbo.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_2b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 1.0 --steps 4 -W 1024 -H 1024 -s 42 --offload-to-cpu -o out.png
```
### 1B / 2B base
```
./build/bin/sd-cli --diffusion-model /path/to/sefi_1b_base.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_2b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 4.0 --steps 50 -W 1024 -H 1024 -s 42 --diffusion-fa --offload-to-cpu -o out.png
./build/bin/sd-cli --diffusion-model /path/to/sefi_1b_base.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_2b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 4.0 --steps 50 -W 1024 -H 1024 -s 42 --offload-to-cpu -o out.png
```
### 5B (needs streaming on 12 GiB VRAM)
```
./build/bin/sd-cli --diffusion-model /path/to/sefi_5b_turbo.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_4b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 1.0 --steps 4 -W 1024 -H 1024 -s 42 --diffusion-fa --max-vram 8 --offload-to-cpu -o out.png
./build/bin/sd-cli --diffusion-model /path/to/sefi_5b_turbo.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_4b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 1.0 --steps 4 -W 1024 -H 1024 -s 42 --max-vram 8 --offload-to-cpu -o out.png
```
<img alt="SeFi-Image 5B turbo example" src="../assets/sefi_image/example.png" />
-1
View File
@@ -28,7 +28,6 @@ and the tokenizer files.
--seed 42 \
--sampling-method euler \
--rng cuda \
--fa \
--output output.png
```
+12
View File
@@ -37,3 +37,15 @@ sd.cpp also supports [TAEHV](https://github.com/madebyollin/taehv) (#937), which
```
Then simply replace the `--vae xxx.safetensors` with `--tae xxx.safetensors` in the commands. If it still out of VRAM, add `--vae-conv-direct` to your command though might be slower.
### Qwen Image 2.1 (TAEQI2.1)
For Qwen Image 2.1, use [taeqi2_1](https://github.com/madebyollin/taesd), which supports 64-channel latents, 16x spatial scaling, and RGBA images.
Download the official [safetensors weights](https://huggingface.co/madebyollin/taeqi2_1/blob/main/taeqi2_1.safetensors) directly; no conversion is needed:
```bash
curl -L -o taeqi2_1.safetensors https://huggingface.co/madebyollin/taeqi2_1/resolve/main/taeqi2_1.safetensors
```
Replace `--vae PATH` with `--taesd taeqi2_1.safetensors` in the [Qwen Image 2.1 examples](qwen_image_2.1.md) to use it for encoding and decoding. For previews only, keep `--vae PATH` and add `--taesd taeqi2_1.safetensors --taesd-preview-only --preview tae`.
+10 -2
View File
@@ -1,5 +1,14 @@
# Troubleshooting
## Video model used in image generation mode
If generation reports that a model cannot be run with `generate_image()`, add
`--mode vid_gen` to the CLI command. `--video-frames` alone does not select video
mode. Video models require this mode even when generating a single frame.
Library callers must use `generate_video()` for these models; use
`sd_ctx_supports_image_generation()` and `sd_ctx_supports_video_generation()` to
check the available generation modes.
## Completely black or white images or videos / NaNs
Some ggml backends can encounter numerical overflow during inference, producing
@@ -31,8 +40,7 @@ the intended output scale:
- `--linear-scale` scales Linear inputs before matrix multiplication and rescales
the result.
- `--attn-scale` scales attention keys and values (K/V). It takes effect only in
the Flash Attention path, where `--fa` or `--diffusion-fa` is enabled and the
backend supports it.
the Flash Attention path, when selected and supported by the backend.
The two values can be set independently and apply across model components. The
default `0` preserves each model's built-in settings; `1` explicitly disables the
+45 -18
View File
@@ -1,5 +1,7 @@
# How to Use
Wan models require `-M vid_gen`, including single-frame generation. `--video-frames` alone does not select video mode. Library callers must use `generate_video()` instead of `generate_image()`.
## Download weights
- Download Wan
@@ -38,6 +40,9 @@
- 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
@@ -62,7 +67,7 @@
### Wan2.1 T2V 1.3B
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1_t2v_1.3B_fp16.safetensors --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --video-frames 33 --flow-shift 3.0
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1_t2v_1.3B_fp16.safetensors --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --video-frames 33 --flow-shift 3.0
```
<video src=../assets/wan/Wan2.1_1.3B_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -70,7 +75,7 @@
### Wan2.1 T2V 14B
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-t2v-14b-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 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --offload-to-cpu --video-frames 33 --flow-shift 3.0
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-t2v-14b-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 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --offload-to-cpu --video-frames 33 --flow-shift 3.0
```
<video src=../assets/wan/Wan2.1_14B_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -80,7 +85,7 @@
### Wan2.1 I2V 14B
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-i2v-14b-480p-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --clip_vision ..\models\clip_vision\clip_vision_h.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa --video-frames 33 --offload-to-cpu -i ..\assets\cat_with_sd_cpp_42.png --flow-shift 3.0
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-i2v-14b-480p-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --clip_vision ..\models\clip_vision\clip_vision_h.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --video-frames 33 --offload-to-cpu -i ..\assets\cat_with_sd_cpp_42.png --flow-shift 3.0
```
<video src=../assets/wan/Wan2.1_14B_i2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -88,7 +93,7 @@
### Wan2.2 T2V A14B
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-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 --offload-to-cpu --video-frames 33 --flow-shift 3.0
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-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 --offload-to-cpu --video-frames 33 --flow-shift 3.0
```
<video src=../assets/wan/Wan2.2_14B_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -96,7 +101,7 @@
### Wan2.2 I2V A14B
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-HighNoise-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 --offload-to-cpu --video-frames 33 --offload-to-cpu -i ..\assets\cat_with_sd_cpp_42.png --flow-shift 3.0
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-HighNoise-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 --offload-to-cpu --video-frames 33 --offload-to-cpu -i ..\assets\cat_with_sd_cpp_42.png --flow-shift 3.0
```
<video src=../assets/wan/Wan2.2_14B_i2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -109,7 +114,7 @@ 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
.\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 --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:
@@ -146,7 +151,7 @@ Notes:
### Wan2.2 T2V A14B T2I
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-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 --offload-to-cpu --flow-shift 3.0
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-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 --offload-to-cpu --flow-shift 3.0
```
<img width="832" height="480" alt="Wan2 2_14B_t2i" src="../assets/wan/Wan2.2_14B_t2i.png" />
@@ -154,7 +159,7 @@ Notes:
### Wan2.2 T2V 14B with Lora
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-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<lora:wan2.2_t2v_lightx2v_4steps_lora_v1.1_low_noise:1><lora:|high_noise|wan2.2_t2v_lightx2v_4steps_lora_v1.1_high_noise:1>" --cfg-scale 3.5 --sampling-method euler --steps 4 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 4 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --offload-to-cpu --lora-model-dir ..\models\loras --video-frames 33 --flow-shift 3.0
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-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<lora:wan2.2_t2v_lightx2v_4steps_lora_v1.1_low_noise:1><lora:|high_noise|wan2.2_t2v_lightx2v_4steps_lora_v1.1_high_noise:1>" --cfg-scale 3.5 --sampling-method euler --steps 4 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 4 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --offload-to-cpu --lora-model-dir ..\models\loras --video-frames 33 --flow-shift 3.0
```
<video src=../assets/wan/Wan2.2_14B_t2v_lora.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -166,7 +171,7 @@ Notes:
#### T2V
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.2_ti2v_5B_fp16.safetensors --vae ..\models\vae\wan2.2_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa --offload-to-cpu --video-frames 33 --flow-shift 3.0
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.2_ti2v_5B_fp16.safetensors --vae ..\models\vae\wan2.2_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --offload-to-cpu --video-frames 33 --flow-shift 3.0
```
<video src=../assets/wan/Wan2.2_5B_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -174,7 +179,7 @@ Notes:
#### I2V
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.2_ti2v_5B_fp16.safetensors --vae ..\models\vae\wan2.2_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa --offload-to-cpu --video-frames 33 -i ..\assets\cat_with_sd_cpp_42.png --flow-shift 3.0
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.2_ti2v_5B_fp16.safetensors --vae ..\models\vae\wan2.2_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --offload-to-cpu --video-frames 33 -i ..\assets\cat_with_sd_cpp_42.png --flow-shift 3.0
```
<video src=../assets/wan/Wan2.2_5B_i2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -182,7 +187,7 @@ Notes:
### Wan2.1 FLF2V 14B
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-flf2v-14b-720p-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --clip_vision ..\models\clip_vision\clip_vision_h.safetensors -p "glass flower blossom" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa --video-frames 33 --offload-to-cpu --init-img ..\..\ComfyUI\input\start_image.png --end-img ..\..\ComfyUI\input\end_image.png --flow-shift 3.0
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-flf2v-14b-720p-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --clip_vision ..\models\clip_vision\clip_vision_h.safetensors -p "glass flower blossom" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --video-frames 33 --offload-to-cpu --init-img ..\..\ComfyUI\input\start_image.png --end-img ..\..\ComfyUI\input\end_image.png --flow-shift 3.0
```
@@ -191,7 +196,7 @@ Notes:
### Wan2.2 FLF2V 14B
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --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 -p "glass flower blossom" -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa --video-frames 33 --offload-to-cpu --init-img ..\..\ComfyUI\input\start_image.png --end-img ..\..\ComfyUI\input\end_image.png --flow-shift 3.0
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --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 -p "glass flower blossom" -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --video-frames 33 --offload-to-cpu --init-img ..\..\ComfyUI\input\start_image.png --end-img ..\..\ComfyUI\input\end_image.png --flow-shift 3.0
```
<video src=../assets/wan/Wan2.2_14B_flf2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -201,7 +206,7 @@ Notes:
#### T2V
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-vace-1.3b-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 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --video-frames 1 --offload-to-cpu
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-vace-1.3b-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 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --video-frames 1 --offload-to-cpu
```
<video src=../assets/wan/Wan2.1_1.3B_vace_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -210,7 +215,7 @@ Notes:
#### R2V
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-vace-1.3b-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 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa -i ..\assets\cat_with_sd_cpp_42.png --video-frames 33 --offload-to-cpu
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-vace-1.3b-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 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 -i ..\assets\cat_with_sd_cpp_42.png --video-frames 33 --offload-to-cpu
```
<video src=../assets/wan/Wan2.1_1.3B_vace_r2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -221,7 +226,7 @@ Notes:
```
mkdir post+depth
ffmpeg -i ..\..\ComfyUI\input\post+depth.mp4 -qscale:v 1 -vf fps=8 post+depth\frame_%04d.jpg
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-vace-1.3b-q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "The girl is dancing in a sea of flowers, slowly moving her hands. There is a close - up shot of her upper body. The character is surrounded by other transparent glass flowers in the style of Nicoletta Ceccoli, creating a beautiful, surreal, and emotionally expressive movie scene with a white. transparent feel and a dreamyl atmosphere." --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa -i ..\..\ComfyUI\input\dance_girl.jpg --control-video ./post+depth --video-frames 33 --offload-to-cpu
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-vace-1.3b-q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "The girl is dancing in a sea of flowers, slowly moving her hands. There is a close - up shot of her upper body. The character is surrounded by other transparent glass flowers in the style of Nicoletta Ceccoli, creating a beautiful, surreal, and emotionally expressive movie scene with a white. transparent feel and a dreamyl atmosphere." --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 -i ..\..\ComfyUI\input\dance_girl.jpg --control-video ./post+depth --video-frames 33 --offload-to-cpu
```
<video src=../assets/wan/Wan2.1_1.3B_vace_v2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -231,7 +236,7 @@ ffmpeg -i ..\..\ComfyUI\input\post+depth.mp4 -qscale:v 1 -vf fps=8 post+depth\fr
#### T2V
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.1_14B_VACE-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 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --video-frames 33 --offload-to-cpu
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.1_14B_VACE-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 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --video-frames 33 --offload-to-cpu
```
<video src=../assets/wan/Wan2.1_14B_vace_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -240,7 +245,7 @@ ffmpeg -i ..\..\ComfyUI\input\post+depth.mp4 -qscale:v 1 -vf fps=8 post+depth\fr
#### R2V
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.1_14B_VACE-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 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa -i ..\assets\cat_with_sd_cpp_42.png --video-frames 33 --offload-to-cpu
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.1_14B_VACE-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 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 -i ..\assets\cat_with_sd_cpp_42.png --video-frames 33 --offload-to-cpu
```
<video src=../assets/wan/Wan2.1_14B_vace_r2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
@@ -250,7 +255,29 @@ ffmpeg -i ..\..\ComfyUI\input\post+depth.mp4 -qscale:v 1 -vf fps=8 post+depth\fr
#### V2V
```
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.1_14B_VACE-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "The girl is dancing in a sea of flowers, slowly moving her hands. There is a close - up shot of her upper body. The character is surrounded by other transparent glass flowers in the style of Nicoletta Ceccoli, creating a beautiful, surreal, and emotionally expressive movie scene with a white. transparent feel and a dreamyl atmosphere." --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa -i ..\..\ComfyUI\input\dance_girl.jpg --control-video ./post+depth --video-frames 33 --offload-to-cpu
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.1_14B_VACE-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "The girl is dancing in a sea of flowers, slowly moving her hands. There is a close - up shot of her upper body. The character is surrounded by other transparent glass flowers in the style of Nicoletta Ceccoli, creating a beautiful, surreal, and emotionally expressive movie scene with a white. transparent feel and a dreamyl atmosphere." --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 -i ..\..\ComfyUI\input\dance_girl.jpg --control-video ./post+depth --video-frames 33 --offload-to-cpu
```
<video src=../assets/wan/Wan2.1_14B_vace_v2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
### 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 -i ..\assets\cat_with_sd_cpp_42.png --video-frames 33 --offload-to-cpu
```
<video src=../assets/wan/Wan2.2_A14B_vace_r2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
#### 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.
+2 -2
View File
@@ -21,7 +21,7 @@ You can run Z-Image with stable-diffusion.cpp on GPUs with 4GB of VRAM — or ev
### Z-Image-Turbo
```
.\bin\Release\sd-cli.exe --diffusion-model z_image_turbo-Q3_K.gguf --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\Qwen3-4B-Instruct-2507-Q4_K_M.gguf -p "A cinematic, melancholic photograph of a solitary hooded figure walking through a sprawling, rain-slicked metropolis at night. The city lights are a chaotic blur of neon orange and cool blue, reflecting on the wet asphalt. The scene evokes a sense of being a single component in a vast machine. Superimposed over the image in a sleek, modern, slightly glitched font is the philosophical quote: 'THE CITY IS A CIRCUIT BOARD, AND I AM A BROKEN TRANSISTOR.' -- moody, atmospheric, profound, dark academic" --cfg-scale 1.0 -v --offload-to-cpu --diffusion-fa -H 1024 -W 512 --steps 8
.\bin\Release\sd-cli.exe --diffusion-model z_image_turbo-Q3_K.gguf --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\Qwen3-4B-Instruct-2507-Q4_K_M.gguf -p "A cinematic, melancholic photograph of a solitary hooded figure walking through a sprawling, rain-slicked metropolis at night. The city lights are a chaotic blur of neon orange and cool blue, reflecting on the wet asphalt. The scene evokes a sense of being a single component in a vast machine. Superimposed over the image in a sleek, modern, slightly glitched font is the philosophical quote: 'THE CITY IS A CIRCUIT BOARD, AND I AM A BROKEN TRANSISTOR.' -- moody, atmospheric, profound, dark academic" --cfg-scale 1.0 -v --offload-to-cpu -H 1024 -W 512 --steps 8
```
<img width="256" alt="z-image example" src="../assets/z_image/q3_K.png" />
@@ -29,7 +29,7 @@ You can run Z-Image with stable-diffusion.cpp on GPUs with 4GB of VRAM — or ev
### Z-Image-Base
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\z_image_bf16.safetensors --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "A cinematic, melancholic photograph of a solitary hooded figure walking through a sprawling, rain-slicked metropolis at night. The city lights are a chaotic blur of neon orange and cool blue, reflecting on the wet asphalt. The scene evokes a sense of being a single component in a vast machine. Superimposed over the image in a sleek, modern, slightly glitched font is the philosophical quote: 'THE CITY IS A CIRCUIT BOARD, AND I AM A BROKEN TRANSISTOR.' -- moody, atmospheric, profound, dark academic" --cfg-scale 5.0 -v --offload-to-cpu --diffusion-fa -H 1024 -W 512
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\z_image_bf16.safetensors --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "A cinematic, melancholic photograph of a solitary hooded figure walking through a sprawling, rain-slicked metropolis at night. The city lights are a chaotic blur of neon orange and cool blue, reflecting on the wet asphalt. The scene evokes a sense of being a single component in a vast machine. Superimposed over the image in a sleek, modern, slightly glitched font is the philosophical quote: 'THE CITY IS A CIRCUIT BOARD, AND I AM A BROKEN TRANSISTOR.' -- moody, atmospheric, profound, dark academic" --cfg-scale 5.0 -v --offload-to-cpu -H 1024 -W 512
```
<img width="256" alt="z-image example" src="../assets/z_image/base_bf16.png" />
+21
View File
@@ -0,0 +1,21 @@
# Z-Image L2P
[L2P](https://nju-pcalab.github.io/projects/L2P/) (Latent-to-Pixel) transfers Z-Image-Turbo to pixel space. The VAE is replaced by 16x16 patch tokenization on RGB pixels and a small convolutional local decoder that turns the last DiT hidden states back into pixels, so no VAE is needed. The text encoder is the Qwen3-4B used by Z-Image-Turbo.
## Download weights
- Download L2P (1K)
- safetensors: https://huggingface.co/zhen-nan/L2P/tree/main
- Download Qwen3 4b
- safetensors: https://huggingface.co/Comfy-Org/z_image_turbo/tree/main/split_files/text_encoders
- gguf: https://huggingface.co/unsloth/Qwen3-4B-Instruct-2507-GGUF/tree/main
## Text-to-image
Do not pass `--vae`. Image dimensions must be multiples of 16. The reference pipeline uses 30 steps with a CFG scale of 2.0.
The released checkpoint keeps part of the transformer in F32. Pass `--type bf16` to load it in BF16, as the reference pipeline runs it; this reduces the diffusion model from about 18.6 GB to 11.8 GB.
```bash
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\model-1k-merge.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "an origami pig on fire in the middle of a dark room with a pentagram on the floor" --cfg-scale 2.0 --steps 30 --sampling-method euler -W 1024 -H 1024 --type bf16 -v -o z_image_l2p.png
```
+12
View File
@@ -11,9 +11,21 @@ Logging defaults to `info`. Use `--log-level <level>` to select `debug`, `verbos
messages at that level and all less detailed levels. `-v` and `--verbose` are
equivalent to `--log-level verbose`. If repeated, the last logging option wins.
`--flash-attn on|off|auto` defaults to `auto`, selecting flash attention for each
compute backend during context creation. See [Backend selection](../../docs/backend.md#flash-attention---flash-attn-onoffauto)
for probing behavior.
Use module assignments such as `--flash-attn auto,te=off,vae=on` to override
individual modules.
For direct image repair or automatic post-generation YOLOv8 detection followed by cropped inpainting, see
[ADetailer](../../docs/adetailer.md).
Use repeatable `--image-preprocess` rules to select resizing, cropping, padding,
and resampling separately for each image input. Add `canny=true` to any input
rule for edge detection. See
[Image preprocessing](../../docs/image_preprocessing.md) for input selectors,
input defaults, downstream model processing, mask alignment, and examples.
Metadata mode inspects PNG/JPEG container metadata without loading any model:
```bash
+28 -54
View File
@@ -41,7 +41,6 @@ struct SDCliParams {
std::string metadata_format = "text";
sd_log_level_t log_level = SD_LOG_INFO;
bool canny_preprocess = false;
bool convert_name = false;
preview_t preview_method = PREVIEW_NONE;
@@ -107,10 +106,6 @@ struct SDCliParams {
};
options.bool_options = {
{"",
"--canny",
"apply canny preprocessor (edge detection)",
true, &canny_preprocess},
{"",
"--convert-name",
"convert tensor name (for convert mode)",
@@ -268,7 +263,6 @@ struct SDCliParams {
<< " metadata_format: \"" << metadata_format << "\",\n"
<< " log_level: " << log_level_name(log_level) << ",\n"
<< " color: " << (color ? "true" : "false") << ",\n"
<< " canny_preprocess: " << (canny_preprocess ? "true" : "false") << ",\n"
<< " convert_name: " << (convert_name ? "true" : "false") << ",\n"
<< " preview_method: " << previews_str[preview_method] << ",\n"
<< " preview_interval: " << preview_interval << ",\n"
@@ -328,9 +322,7 @@ void sd_log_cb(enum sd_log_level_t level, const char* log, void* data) {
bool load_images_from_dir(const std::string dir,
std::vector<SDImageOwner>& images,
int expected_width = 0,
int expected_height = 0,
int max_image_num = 0) {
int max_image_num = 0) {
if (!fs::exists(dir) || !fs::is_directory(dir)) {
LOG_ERROR("'%s' is not a valid directory\n", dir.c_str());
return false;
@@ -357,7 +349,8 @@ bool load_images_from_dir(const std::string dir,
LOG_VERBOSE("load image %zu from '%s'", images.size(), path.c_str());
int width = 0;
int height = 0;
uint8_t* image_buffer = load_image_from_file(path.c_str(), width, height, expected_width, expected_height);
int loaded_channel = 0;
uint8_t* image_buffer = load_image_from_file(path.c_str(), width, height, loaded_channel, 0, 0);
if (image_buffer == nullptr) {
LOG_ERROR("load image from '%s' failed", path.c_str());
return false;
@@ -365,7 +358,7 @@ bool load_images_from_dir(const std::string dir,
images.emplace_back(sd_image_t{(uint32_t)width,
(uint32_t)height,
3,
(uint32_t)loaded_channel,
image_buffer});
if (max_image_num > 0 && static_cast<int>(images.size()) >= max_image_num) {
@@ -651,10 +644,11 @@ int main(int argc, const char* argv[]) {
SDCliParams cli_params;
SDContextParams ctx_params;
ctx_params.conditioning_cache_size = 0;
SDGenerationParams gen_params;
parse_args(argc, argv, cli_params, ctx_params, gen_params);
sd_set_log_callback(sd_log_cb, (void*)&cli_params);
parse_args(argc, argv, cli_params, ctx_params, gen_params);
if (cli_params.mode == METADATA) {
MetadataReadOptions options;
@@ -722,6 +716,15 @@ int main(int argc, const char* argv[]) {
}
if (cli_params.mode == CONVERT) {
std::vector<sd_lora_t> loras;
loras.reserve(gen_params.lora_map.size() + gen_params.high_noise_lora_map.size());
for (const auto& [path, multiplier] : gen_params.lora_map) {
loras.push_back({false, multiplier, path.c_str()});
}
for (const auto& [path, multiplier] : gen_params.high_noise_lora_map) {
loras.push_back({true, multiplier, path.c_str()});
}
bool success = convert_with_components(ctx_params.model_path.c_str(),
ctx_params.clip_l_path.c_str(),
ctx_params.clip_g_path.c_str(),
@@ -732,7 +735,9 @@ int main(int argc, const char* argv[]) {
ctx_params.wtype,
ctx_params.tensor_type_rules.c_str(),
cli_params.convert_name,
ctx_params.n_threads);
ctx_params.n_threads,
loras.empty() ? nullptr : loras.data(),
(int)loras.size());
if (!success) {
LOG_ERROR("convert '%s'/'%s' to '%s' failed",
ctx_params.model_path.c_str(),
@@ -750,16 +755,8 @@ int main(int argc, const char* argv[]) {
auto load_image_and_update_size = [&](const std::string& path,
SDImageOwner& image,
bool resize_image = true,
int expected_channel = 3) -> bool {
int expected_width = 0;
int expected_height = 0;
if (resize_image && gen_params.width_and_height_are_set()) {
expected_width = gen_params.width;
expected_height = gen_params.height;
}
if (!load_sd_image_from_file(image.put(), path.c_str(), expected_width, expected_height, expected_channel)) {
if (!load_sd_image_from_file(image.put(), path.c_str(), 0, 0, expected_channel)) {
LOG_ERROR("load image from '%s' failed", path.c_str());
return false;
}
@@ -781,7 +778,8 @@ int main(int argc, const char* argv[]) {
};
if (gen_params.init_image_path.size() > 0) {
if (!load_image_and_update_size(gen_params.init_image_path, gen_params.init_image)) {
const bool native_init = cli_params.mode == IMG_GEN || cli_params.mode == ADETAILER;
if (!load_image_and_update_size(gen_params.init_image_path, gen_params.init_image, native_init ? 0 : 3)) {
return 1;
}
}
@@ -795,8 +793,8 @@ int main(int argc, const char* argv[]) {
if (gen_params.ref_image_paths.size() > 0) {
gen_params.ref_images.clear();
for (auto& path : gen_params.ref_image_paths) {
SDImageOwner ref_image({0, 0, 3, nullptr});
if (!load_image_and_update_size(path, ref_image, false)) {
SDImageOwner ref_image({0, 0, 0, nullptr});
if (!load_image_and_update_size(path, ref_image, 0)) {
return 1;
}
gen_params.ref_images.push_back(std::move(ref_image));
@@ -837,41 +835,22 @@ int main(int argc, const char* argv[]) {
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(),
gen_params.get_resolved_width(),
gen_params.get_resolved_height(),
0,
0,
1)) {
LOG_ERROR("load image from '%s' failed", gen_params.mask_image_path.c_str());
return 1;
}
} else {
sd_image_t generated_mask = {0, 0, 1, nullptr};
generated_mask.data = (uint8_t*)malloc(gen_params.get_resolved_width() * gen_params.get_resolved_height());
if (generated_mask.data == nullptr) {
LOG_ERROR("malloc mask image failed");
return 1;
}
generated_mask.width = gen_params.get_resolved_width();
generated_mask.height = gen_params.get_resolved_height();
memset(generated_mask.data, 255, gen_params.get_resolved_width() * gen_params.get_resolved_height());
gen_params.mask_image.reset(generated_mask);
}
if (gen_params.control_image_path.size() > 0) {
if (!load_sd_image_from_file(gen_params.control_image.put(),
gen_params.control_image_path.c_str(),
gen_params.get_resolved_width(),
gen_params.get_resolved_height())) {
0,
0)) {
LOG_ERROR("load image from '%s' failed", gen_params.control_image_path.c_str());
return 1;
}
if (cli_params.canny_preprocess) { // apply preprocessor
preprocess_canny(gen_params.control_image.get(),
0.08f,
0.08f,
0.8f,
1.0f,
false);
}
}
if (gen_params.ip_adapter_image_path.size() > 0) {
@@ -888,8 +867,6 @@ int main(int argc, const char* argv[]) {
gen_params.control_frames.clear();
if (!load_images_from_dir(gen_params.control_video_path,
gen_params.control_frames,
gen_params.get_resolved_width(),
gen_params.get_resolved_height(),
gen_params.video_frames)) {
return 1;
}
@@ -898,10 +875,7 @@ int main(int argc, const char* argv[]) {
if (!gen_params.pm_id_images_dir.empty()) {
gen_params.pm_id_images.clear();
if (!load_images_from_dir(gen_params.pm_id_images_dir,
gen_params.pm_id_images,
0,
0,
0)) {
gen_params.pm_id_images)) {
return 1;
}
}
+203 -132
View File
@@ -378,6 +378,23 @@ static int parse_scale_override(int argc, const char** argv, int index, float& s
return 1;
}
static int parse_on_off_arg(int argc, const char** argv, int index, const char* option, bool& value) {
if (++index >= argc) {
LOG_ERROR("%s requires 'on' or 'off'", option);
return -1;
}
const std::string arg = argv[index];
if (arg == "on") {
value = true;
} else if (arg == "off") {
value = false;
} else {
LOG_ERROR("invalid %s value '%s'; expected 'on' or 'off'", option, argv[index]);
return -1;
}
return 1;
}
ArgOptions SDContextParams::get_options() {
ArgOptions options;
options.string_options = {
@@ -518,7 +535,8 @@ ArgOptions SDContextParams::get_options() {
{"",
"--model-args",
"extra model args, key=value list. Supports chroma_use_dit_mask, chroma_use_t5_mask, "
"chroma_t5_mask_pad, qwen_image_zero_cond_t",
"chroma_t5_mask_pad, qwen_image_zero_cond_t, qwen_image_2_1_prefix_cache, "
"qwen_image_2_1_prefix_cache_type (auto or a type name from --type)",
(int)',',
&model_args},
{"",
@@ -571,6 +589,10 @@ ArgOptions SDContextParams::get_options() {
"number of threads to use during computation (default: -1). "
"If threads <= 0, then threads will be set to the number of CPU physical cores",
&n_threads},
{"",
"--conditioning-cache-size",
"maximum number of conditioning results cached per model context (default: " + std::to_string(conditioning_cache_size) + ", 0 disables caching)",
&conditioning_cache_size},
};
options.bool_options = {
@@ -611,40 +633,19 @@ ArgOptions SDContextParams::get_options() {
"deprecated; use --backend vae=cpu",
true, &vae_on_cpu},
{"",
"--fa",
"use flash attention",
true, &flash_attn},
{"",
"--diffusion-fa",
"use flash attention in the diffusion model only",
true, &diffusion_flash_attn},
"--sage-attn",
"use native CUDA SageAttention in the diffusion model, with flash/default attention fallback",
true, &sage_attn},
{"",
"--diffusion-conv-direct",
"use ggml_conv2d_direct in the diffusion model",
true, &diffusion_conv_direct},
{"",
"--vae-conv-direct",
"use ggml_conv2d_direct in the vae model",
"use direct 2D and 3D convolutions in the vae model",
true, &vae_conv_direct},
};
auto on_auto_fit_arg = [&](int argc, const char** argv, int index) {
if (++index >= argc) {
LOG_ERROR("--auto-fit requires 'on' or 'off'");
return -1;
}
const std::string arg = argv[index];
if (arg == "on") {
auto_fit = true;
} else if (arg == "off") {
auto_fit = false;
} else {
LOG_ERROR("invalid --auto-fit value '%s'; expected 'on' or 'off'", argv[index]);
return -1;
}
return 1;
};
auto on_type_arg = [&](int argc, const char** argv, int index) {
if (++index >= argc) {
return -1;
@@ -724,16 +725,56 @@ ArgOptions SDContextParams::get_options() {
}},
{"",
"--attn-scale",
"flash-attention K/V scale override (float, default: 0 = model default, 1 = no scaling); requires --fa or --diffusion-fa",
"flash-attention K/V scale override (float, default: 0 = model default, 1 = no scaling); applies when flash attention is selected",
[this](int argc, const char** argv, int index) {
return parse_scale_override(argc, argv, index, attn_scale);
}},
{"",
"--flash-attn",
"on|off|auto or module assignments (default: auto), e.g. auto,te=off,vae=on. Last repeated option wins",
[this](int argc, const char** argv, int index) {
if (++index >= argc) {
LOG_ERROR("--flash-attn requires on, off, or auto");
return -1;
}
const std::string value = argv[index];
if (value.empty()) {
LOG_ERROR("--flash-attn requires a nonempty value");
return -1;
}
flash_attn_mode = value;
return 1;
}},
{"",
"--fa",
"deprecated; use --flash-attn on",
[this](int, const char**, int) {
LOG_WARN("--fa is deprecated; use --flash-attn on instead.");
flash_attn_mode = "on";
return 0;
}},
{"",
"--diffusion-fa",
"deprecated; use --flash-attn off,diffusion=on",
[this](int, const char**, int) {
LOG_WARN("--diffusion-fa is deprecated; use --flash-attn off,diffusion=on instead.");
flash_attn_mode = "off,diffusion=on";
return 0;
}},
{"",
"--auto-fit",
"on|off (default: on). Preserve --backend (otherwise select one GPU) and place weights on the compute GPU, "
"RAM, another GPU, or disk in that order, according to available memory (--max-vram limits GPU budgets). "
"Disabled by explicit --params-backend; uses automatic graph segmentation when needed",
on_auto_fit_arg},
[this](int argc, const char** argv, int index) {
return parse_on_off_arg(argc, argv, index, "--auto-fit", auto_fit);
}},
{"",
"--batched-cfg",
"on|off (default: on). Run the conditional and unconditional CFG branches in one batched UNet forward when supported",
[this](int argc, const char** argv, int index) {
return parse_on_off_arg(argc, argv, index, "--batched-cfg", batched_cfg);
}},
{"",
"--type",
"weight type (examples: f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_K, q3_K, q4_K). "
@@ -818,6 +859,10 @@ bool SDContextParams::resolve(SDMode mode) {
}
bool SDContextParams::validate(SDMode mode) {
if (conditioning_cache_size < 0) {
LOG_ERROR("error: conditioning-cache-size must be non-negative");
return false;
}
if (mode == CONVERT) {
const bool has_convert_input = model_path.length() != 0 ||
clip_l_path.length() != 0 ||
@@ -894,6 +939,7 @@ std::string SDContextParams::to_string() const {
std::ostringstream oss;
oss << "SDContextParams {\n"
<< " n_threads: " << n_threads << ",\n"
<< " conditioning_cache_size: " << conditioning_cache_size << ",\n"
<< " model_path: \"" << model_path << "\",\n"
<< " clip_l_path: \"" << clip_l_path << "\",\n"
<< " clip_g_path: \"" << clip_g_path << "\",\n"
@@ -926,6 +972,7 @@ std::string SDContextParams::to_string() const {
<< " max_vram: \"" << max_vram << "\",\n"
<< " disable_prefetch: " << (disable_prefetch ? "true" : "false") << ",\n"
<< " disable_segmented_compute: " << (disable_segmented_compute ? "true" : "false") << ",\n"
<< " batched_cfg: " << (batched_cfg ? "true" : "false") << ",\n"
<< " eager_load: " << (eager_load ? "true" : "false") << ",\n"
<< " backend: \"" << backend << "\",\n"
<< " params_backend: \"" << params_backend << "\",\n"
@@ -936,8 +983,8 @@ std::string SDContextParams::to_string() const {
<< " control_net_cpu: " << (control_net_cpu ? "true" : "false") << ",\n"
<< " clip_on_cpu: " << (clip_on_cpu ? "true" : "false") << ",\n"
<< " vae_on_cpu: " << (vae_on_cpu ? "true" : "false") << ",\n"
<< " flash_attn: " << (flash_attn ? "true" : "false") << ",\n"
<< " diffusion_flash_attn: " << (diffusion_flash_attn ? "true" : "false") << ",\n"
<< " flash_attn_mode: " << flash_attn_mode << ",\n"
<< " sage_attn: " << (sage_attn ? "true" : "false") << ",\n"
<< " linear_scale: " << linear_scale << ",\n"
<< " attn_scale: " << attn_scale << ",\n"
<< " diffusion_conv_direct: " << (diffusion_conv_direct ? "true" : "false") << ",\n"
@@ -987,14 +1034,15 @@ sd_ctx_params_t SDContextParams::to_sd_ctx_params_t(bool taesd_preview) {
sd_ctx_params.pulid_weights_path = pulid_weights_path.c_str();
sd_ctx_params.tensor_type_rules = tensor_type_rules.c_str();
sd_ctx_params.n_threads = n_threads;
sd_ctx_params.conditioning_cache_size = conditioning_cache_size;
sd_ctx_params.wtype = wtype;
sd_ctx_params.rng_type = rng_type;
sd_ctx_params.sampler_rng_type = sampler_rng_type;
sd_ctx_params.prediction = prediction;
sd_ctx_params.lora_apply_mode = lora_apply_mode;
sd_ctx_params.enable_mmap = enable_mmap;
sd_ctx_params.flash_attn = flash_attn;
sd_ctx_params.diffusion_flash_attn = diffusion_flash_attn;
sd_ctx_params.flash_attn_mode = flash_attn_mode.c_str();
sd_ctx_params.sage_attn = sage_attn;
sd_ctx_params.linear_scale = linear_scale;
sd_ctx_params.attn_scale = attn_scale;
sd_ctx_params.tae_preview_only = taesd_preview;
@@ -1005,6 +1053,7 @@ sd_ctx_params_t SDContextParams::to_sd_ctx_params_t(bool taesd_preview) {
sd_ctx_params.max_vram = max_vram.c_str();
sd_ctx_params.disable_prefetch = disable_prefetch;
sd_ctx_params.disable_segmented_compute = disable_segmented_compute;
sd_ctx_params.batched_cfg = batched_cfg;
sd_ctx_params.eager_load = eager_load;
sd_ctx_params.backend = effective_backend.c_str();
sd_ctx_params.params_backend = effective_params_backend.c_str();
@@ -1109,7 +1158,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; beta scheduler supports alpha, beta; logit_normal supports mu, std, logsnr_min, logsnr_max, resolution_aware; lms supports lms_max_order, lms_shift, lms_divisions; noise-injecting samplers support noise_sampler with value iid (default except for dpm++2m_sde_bt) or brownian_tree; brownian_tree_rng supports cpu (default), cuda, std_default or sampler_rng",
"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; llada_image supports uniform; lms supports lms_max_order, lms_shift, lms_divisions; noise-injecting samplers support noise_sampler with value iid (default except for dpm++2m_sde_bt) or brownian_tree; brownian_tree_rng supports cpu (default), cuda, std_default or sampler_rng",
(int)',',
&extra_sample_args},
{"",
@@ -1122,6 +1171,9 @@ ArgOptions SDGenerationParams::get_options() {
"Key-value list to set up the way the reference images are processed (empty = auto-detect from model weigths)",
(int)',',
&ref_image_args},
{"", "--image-preprocess",
"Image preprocessing rule: target=init|end|mask|control|ref|ip-adapter|id|control-frame,index=N,mode=auto|none|stretch|crop|crop-resize|fit-pad,filter=auto|nearest|nearest-exact|bilinear|bicubic|lanczos,antialias=auto|true|false,width=W,height=H,anchor=center|top|bottom|left|right,pad_color=#RRGGBB[AA],canny=true|false. Repeat for multiple rules.",
(int)';', &image_preprocess},
};
options.int_options = {
@@ -1302,11 +1354,6 @@ ArgOptions SDGenerationParams::get_options() {
"automatically increase the indices of references images based on the order they are listed (starting with 1).",
true,
&increase_ref_index},
{"",
"--disable-auto-resize-ref-image",
"disable auto resize of ref images",
false,
&auto_resize_ref_image},
{"",
"--circular",
"enable circular padding on both axes for tileable output",
@@ -1326,7 +1373,7 @@ ArgOptions SDGenerationParams::get_options() {
&embed_image_metadata},
{"",
"--vae-tiling",
"process vae in tiles to reduce memory usage",
"process vae encode and decode in spatial tiles to reduce memory usage (default: 256x256 image pixels)",
true,
&vae_tiling_params.enabled},
{"",
@@ -1450,6 +1497,9 @@ ArgOptions SDGenerationParams::get_options() {
return -1;
}
std::string sigmas_str = value;
if (sigmas_str == "qwen-image-2.1-turbo") {
sigmas_str = "1.0,0.978453,0.95418,0.926626,0.89508,0.845148,0.704534,0.414568,0.0";
}
if (!sigmas_str.empty() && sigmas_str.front() == '[') {
sigmas_str.erase(0, 1);
}
@@ -1590,12 +1640,12 @@ ArgOptions SDGenerationParams::get_options() {
size_t x_pos = tile_size_str.find('x');
try {
if (x_pos != std::string::npos) {
std::string tile_x_str = tile_size_str.substr(0, x_pos);
std::string tile_y_str = tile_size_str.substr(x_pos + 1);
vae_tiling_params.tile_size_x = std::stoi(tile_x_str);
vae_tiling_params.tile_size_y = std::stoi(tile_y_str);
std::string tile_w_str = tile_size_str.substr(0, x_pos);
std::string tile_h_str = tile_size_str.substr(x_pos + 1);
vae_tiling_params.tile_size_w = std::stoi(tile_w_str);
vae_tiling_params.tile_size_h = std::stoi(tile_h_str);
} else {
vae_tiling_params.tile_size_x = vae_tiling_params.tile_size_y = std::stoi(tile_size_str);
vae_tiling_params.tile_size_w = vae_tiling_params.tile_size_h = std::stoi(tile_size_str);
}
} catch (const std::invalid_argument&) {
return -1;
@@ -1613,12 +1663,12 @@ ArgOptions SDGenerationParams::get_options() {
size_t x_pos = rel_size_str.find('x');
try {
if (x_pos != std::string::npos) {
std::string rel_x_str = rel_size_str.substr(0, x_pos);
std::string rel_y_str = rel_size_str.substr(x_pos + 1);
vae_tiling_params.rel_size_x = std::stof(rel_x_str);
vae_tiling_params.rel_size_y = std::stof(rel_y_str);
std::string rel_w_str = rel_size_str.substr(0, x_pos);
std::string rel_h_str = rel_size_str.substr(x_pos + 1);
vae_tiling_params.rel_size_w = std::stof(rel_w_str);
vae_tiling_params.rel_size_h = std::stof(rel_h_str);
} else {
vae_tiling_params.rel_size_x = vae_tiling_params.rel_size_y = std::stof(rel_size_str);
vae_tiling_params.rel_size_w = vae_tiling_params.rel_size_h = std::stof(rel_size_str);
}
} catch (const std::invalid_argument&) {
return -1;
@@ -1696,11 +1746,11 @@ ArgOptions SDGenerationParams::get_options() {
on_scheduler_arg},
{"",
"--sigmas",
"custom sigma values for the sampler, comma-separated (e.g., \"14.61,7.8,3.5,0.0\").",
"custom sigma values for the sampler, comma-separated (e.g., \"14.61,7.8,3.5,0.0\") or preset: qwen-image-2.1-turbo.",
on_sigmas_arg},
{"",
"--hires-sigmas",
"custom sigma values for the highres fix second pass, comma-separated (e.g., \"0.85,0.725,0.421875,0.0\").",
"custom sigma values for the highres fix second pass, comma-separated (e.g., \"0.85,0.725,0.421875,0.0\") or preset: qwen-image-2.1-turbo.",
on_hires_sigmas_arg},
{"",
"--skip-layers",
@@ -1748,11 +1798,11 @@ ArgOptions SDGenerationParams::get_options() {
on_scm_policy_arg},
{"",
"--vae-tile-size",
"tile size for vae tiling, format [X]x[Y] (default: 32x32)",
"tile size for vae encode and decode in image pixels, format [W]x[H] or [S] (default: 256x256; requires --vae-tiling)",
on_tile_size_arg},
{"",
"--vae-relative-tile-size",
"relative tile size for vae tiling, format [X]x[Y], in fraction of image size if < 1, in number of tiles per dim if >=1 (overrides --vae-tile-size)",
"relative tile size for vae encode and decode, format [W]x[H] or [S]: <=1 is a dimension fraction, >1 a target tile count (overrides --vae-tile-size; requires --vae-tiling)",
on_relative_tile_size_arg},
{"",
"--prompt-file",
@@ -1842,28 +1892,28 @@ bool decode_base64_image(const std::string& encoded_input,
return false;
}
int decoded_width = 0;
int decoded_height = 0;
uint8_t* raw_data = load_image_from_memory(reinterpret_cast<const char*>(image_bytes.data()),
static_cast<int>(image_bytes.size()),
decoded_width,
decoded_height,
expected_width,
expected_height,
target_channels);
int decoded_width = 0;
int decoded_height = 0;
int resolved_channel = target_channels;
uint8_t* raw_data = load_image_from_memory(reinterpret_cast<const char*>(image_bytes.data()),
static_cast<int>(image_bytes.size()),
decoded_width,
decoded_height,
resolved_channel,
expected_width,
expected_height,
target_channels);
if (raw_data == nullptr) {
return false;
}
out_image.reset({(uint32_t)decoded_width, (uint32_t)decoded_height, (uint32_t)target_channels, raw_data});
out_image.reset({(uint32_t)decoded_width, (uint32_t)decoded_height, (uint32_t)resolved_channel, raw_data});
return true;
}
static bool parse_image_json_field(const json& parent,
const char* key,
int channels,
int expected_width,
int expected_height,
SDImageOwner& out_image) {
if (!parent.contains(key)) {
return true;
@@ -1875,14 +1925,12 @@ static bool parse_image_json_field(const json& parent,
if (!parent.at(key).is_string()) {
return false;
}
return decode_base64_image(parent.at(key).get<std::string>(), channels, expected_width, expected_height, out_image);
return decode_base64_image(parent.at(key).get<std::string>(), channels, 0, 0, out_image);
}
static bool parse_image_array_json_field(const json& parent,
const char* key,
int channels,
int expected_width,
int expected_height,
std::vector<SDImageOwner>& out_images) {
if (!parent.contains(key)) {
return true;
@@ -1901,7 +1949,7 @@ static bool parse_image_array_json_field(const json& parent,
return false;
}
SDImageOwner image;
if (!decode_base64_image(item.get<std::string>(), channels, expected_width, expected_height, image)) {
if (!decode_base64_image(item.get<std::string>(), channels, 0, 0, image)) {
return false;
}
out_images.push_back(std::move(image));
@@ -2000,6 +2048,29 @@ static bool resolve_model_file_from_dir(const std::string& model_name,
return false;
}
bool SDGenerationParams::parse_image_preprocess_json(const std::string& json_str) {
const auto value = json::parse(json_str, nullptr, false);
std::string rules;
if (value.is_string()) {
rules = value.get<std::string>();
} else if (value.is_array()) {
for (const auto& item : value) {
if (!item.is_string()) {
LOG_ERROR("image_preprocess must contain rule strings");
return false;
}
if (!rules.empty())
rules += ";";
rules += item.get<std::string>();
}
} else {
LOG_ERROR("image_preprocess must be a string or array of strings");
return false;
}
image_preprocess = std::move(rules);
return true;
}
bool SDGenerationParams::from_json_str(
const std::string& json_str,
const std::function<std::string(const std::string&)>& lora_path_resolver) {
@@ -2011,6 +2082,9 @@ bool SDGenerationParams::from_json_str(
return false;
}
if (j.contains("image_preprocess") && !parse_image_preprocess_json(j["image_preprocess"].dump()))
return false;
auto load_if_exists = [&](const char* key, auto& out) {
if (j.contains(key)) {
using T = std::decay_t<decltype(out)>;
@@ -2048,6 +2122,7 @@ bool SDGenerationParams::from_json_str(
load_if_exists("cache_mode", cache_mode);
load_if_exists("cache_option", cache_option);
load_if_exists("scm_mask", scm_mask);
load_if_exists("ref_image_args", ref_image_args);
load_if_exists("clip_skip", clip_skip);
load_if_exists("width", width);
@@ -2065,7 +2140,6 @@ bool SDGenerationParams::from_json_str(
load_if_exists("moe_boundary", moe_boundary);
load_if_exists("vace_strength", vace_strength);
load_if_exists("auto_resize_ref_image", auto_resize_ref_image);
load_if_exists("increase_ref_index", increase_ref_index);
load_if_exists("embed_image_metadata", embed_image_metadata);
@@ -2185,20 +2259,20 @@ bool SDGenerationParams::from_json_str(
if (tiling_json.contains("temporal_tiling") && tiling_json["temporal_tiling"].is_boolean()) {
vae_tiling_params.temporal_tiling = tiling_json["temporal_tiling"];
}
if (tiling_json.contains("tile_size_x") && tiling_json["tile_size_x"].is_number_integer()) {
vae_tiling_params.tile_size_x = tiling_json["tile_size_x"];
if (tiling_json.contains("tile_size_w") && tiling_json["tile_size_w"].is_number_integer()) {
vae_tiling_params.tile_size_w = tiling_json["tile_size_w"];
}
if (tiling_json.contains("tile_size_y") && tiling_json["tile_size_y"].is_number_integer()) {
vae_tiling_params.tile_size_y = tiling_json["tile_size_y"];
if (tiling_json.contains("tile_size_h") && tiling_json["tile_size_h"].is_number_integer()) {
vae_tiling_params.tile_size_h = tiling_json["tile_size_h"];
}
if (tiling_json.contains("target_overlap") && tiling_json["target_overlap"].is_number()) {
vae_tiling_params.target_overlap = tiling_json["target_overlap"];
}
if (tiling_json.contains("rel_size_x") && tiling_json["rel_size_x"].is_number()) {
vae_tiling_params.rel_size_x = tiling_json["rel_size_x"];
if (tiling_json.contains("rel_size_w") && tiling_json["rel_size_w"].is_number()) {
vae_tiling_params.rel_size_w = tiling_json["rel_size_w"];
}
if (tiling_json.contains("rel_size_y") && tiling_json["rel_size_y"].is_number()) {
vae_tiling_params.rel_size_y = tiling_json["rel_size_y"];
if (tiling_json.contains("rel_size_h") && tiling_json["rel_size_h"].is_number()) {
vae_tiling_params.rel_size_h = tiling_json["rel_size_h"];
}
if (tiling_json.contains("extra_tiling_args") && tiling_json["extra_tiling_args"].is_string()) {
extra_tiling_args = tiling_json["extra_tiling_args"].get<std::string>();
@@ -2209,46 +2283,38 @@ bool SDGenerationParams::from_json_str(
LOG_ERROR("invalid lora");
return false;
}
if (!parse_image_json_field(j, "init_image", 3, width, height, init_image)) {
LOG_ERROR("invalid init_image");
auto load_image = [&](const char* key, int channels, SDImageOwner& image) {
if (!parse_image_json_field(j, key, channels, image)) {
LOG_ERROR("invalid %s", key);
return false;
}
return true;
};
if (!load_image("init_image", 0, init_image) ||
!load_image("end_image", 3, end_image) ||
!load_image("mask_image", 1, mask_image) ||
!load_image("control_image", 3, control_image) ||
!load_image("ip_adapter_image", 3, ip_adapter_image)) {
return false;
}
if (!parse_image_json_field(j, "end_image", 3, width, height, end_image)) {
LOG_ERROR("invalid end_image");
return false;
}
if (!parse_image_array_json_field(j, "ref_images", 3, width, height, ref_images)) {
LOG_ERROR("invalid ref_images");
return false;
}
if (!parse_image_array_json_field(j, "control_frames", 3, width, height, control_frames)) {
LOG_ERROR("invalid control_frames");
return false;
}
if (!parse_image_json_field(j, "mask_image", 1, width, height, mask_image)) {
LOG_ERROR("invalid mask_image");
return false;
}
if (!parse_image_json_field(j, "control_image", 3, width, height, control_image)) {
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");
if (!parse_image_array_json_field(j, "ref_images", 0, ref_images) ||
!parse_image_array_json_field(j, "control_frames", 3, control_frames)) {
LOG_ERROR("invalid input image array");
return false;
}
return true;
}
void SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_dir) {
bool SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_dir) {
if (lora_model_dir.empty()) {
return;
return true;
}
static const std::regex re(R"(<lora:([^:>]+):([^>]+)>)");
static const std::vector<std::string> valid_ext = {".gguf", ".safetensors", ".pt", ".ckpt"};
std::smatch m;
bool missing = false;
std::string tmp = prompt;
while (std::regex_search(tmp, m, re)) {
@@ -2290,8 +2356,9 @@ void SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_d
}
if (!found) {
LOG_WARN("can not found lora %s", final_path.lexically_normal().string().c_str());
tmp = m.suffix().str();
prompt = std::regex_replace(prompt, re, "", std::regex_constants::format_first_only);
tmp = m.suffix().str();
prompt = std::regex_replace(prompt, re, "", std::regex_constants::format_first_only);
missing = true;
continue;
}
}
@@ -2307,6 +2374,8 @@ void SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_d
tmp = m.suffix().str();
}
return !missing;
}
bool SDGenerationParams::width_and_height_are_set() const {
@@ -2428,7 +2497,7 @@ bool SDGenerationParams::initialize_cache_params() {
return true;
}
bool SDGenerationParams::resolve(const std::string& lora_model_dir, const std::string& hires_upscalers_dir, bool strict) {
bool SDGenerationParams::resolve(const std::string& lora_model_dir, const std::string& hires_upscalers_dir, bool strict, bool validate_missing_loras) {
vae_tiling_params.extra_tiling_args = extra_tiling_args.empty() ? nullptr : extra_tiling_args.c_str();
if (high_noise_sample_params.sample_steps <= 0) {
@@ -2472,12 +2541,18 @@ bool SDGenerationParams::resolve(const std::string& lora_model_dir, const std::s
prompt_with_lora = prompt;
if (!lora_model_dir.empty()) {
extract_and_remove_lora(lora_model_dir);
if (!extract_and_remove_lora(lora_model_dir) && validate_missing_loras) {
return false;
}
}
return true;
}
bool SDGenerationParams::validate(SDMode mode) {
if (!image_preprocess.empty() && mode != IMG_GEN && mode != VID_GEN) {
LOG_ERROR("--image-preprocess requires img_gen or vid_gen mode");
return false;
}
if (batch_count <= 0) {
LOG_ERROR("error: batch_count must be greater than 0");
return false;
@@ -2596,7 +2671,7 @@ bool SDGenerationParams::resolve_and_validate(SDMode mode,
const std::string& lora_model_dir,
const std::string& hires_upscalers_dir,
bool strict) {
if (!resolve(lora_model_dir, hires_upscalers_dir, strict)) {
if (!resolve(lora_model_dir, hires_upscalers_dir, strict, mode == CONVERT)) {
return false;
}
if (!validate(mode)) {
@@ -2653,14 +2728,6 @@ sd_img_gen_params_t SDGenerationParams::to_sd_img_gen_params_t() {
pulid_id_weight,
};
if (!auto_resize_ref_image) {
if (!ref_image_args.empty()) {
ref_image_args += ",";
}
ref_image_args += "resize_before_vae=0";
LOG_WARN("Notice: --disable-auto-resize-ref-image is deprecated. Use --ref-image-args \"resize_before_vae=off\" instead.");
}
if (increase_ref_index) {
if (!ref_image_args.empty()) {
ref_image_args += ",";
@@ -2708,6 +2775,7 @@ sd_img_gen_params_t SDGenerationParams::to_sd_img_gen_params_t() {
params.hires.custom_sigmas_count = static_cast<int>(hires_custom_sigmas.size());
params.circular_x = circular || circular_x;
params.circular_y = circular || circular_y;
params.image_preprocess = {image_preprocess.c_str()};
return params;
}
@@ -2810,6 +2878,7 @@ sd_vid_gen_params_t SDGenerationParams::to_sd_vid_gen_params_t() {
params.hires.custom_sigmas_count = static_cast<int>(hires_custom_sigmas.size());
params.circular_x = circular || circular_x;
params.circular_y = circular || circular_y;
params.image_preprocess = {image_preprocess.c_str()};
return params;
}
@@ -2866,7 +2935,8 @@ std::string SDGenerationParams::to_string() const {
<< " 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"
<< " image_preprocess: " << image_preprocess << ",\n"
<< " ref_image_args: " << ref_image_args << ",\n"
<< " increase_ref_index: " << (increase_ref_index ? "true" : "false") << ",\n"
<< " pm_id_images_dir: \"" << pm_id_images_dir << "\",\n"
<< " pm_id_embed_path: \"" << pm_id_embed_path << "\",\n"
@@ -2905,11 +2975,11 @@ std::string SDGenerationParams::to_string() const {
<< " vae_tiling_params: { "
<< vae_tiling_params.enabled << ", "
<< vae_tiling_params.temporal_tiling << ", "
<< vae_tiling_params.tile_size_x << ", "
<< vae_tiling_params.tile_size_y << ", "
<< vae_tiling_params.tile_size_w << ", "
<< vae_tiling_params.tile_size_h << ", "
<< vae_tiling_params.target_overlap << ", "
<< vae_tiling_params.rel_size_x << ", "
<< vae_tiling_params.rel_size_y << ", "
<< vae_tiling_params.rel_size_w << ", "
<< vae_tiling_params.rel_size_h << ", "
<< "\"" << extra_tiling_args << "\" },\n"
<< "}";
return oss.str();
@@ -3017,12 +3087,13 @@ std::string build_sdcpp_image_metadata_json(const SDContextParams& ctx_params,
set_json_basename_if_not_empty(models, "control_net", ctx_params.control_net_path);
root["models"] = std::move(models);
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;
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["ref_image_args"] = gen_params.ref_image_args;
root["image_preprocess"] = gen_params.image_preprocess;
root["increase_ref_index"] = gen_params.increase_ref_index;
if (mode == VID_GEN) {
root["video"] = {
{"frame_count", gen_params.video_frames},
@@ -3110,11 +3181,11 @@ std::string build_sdcpp_image_metadata_json(const SDContextParams& ctx_params,
root["vae_tiling"] = {
{"enabled", gen_params.vae_tiling_params.enabled},
{"temporal_tiling", gen_params.vae_tiling_params.temporal_tiling},
{"tile_size_x", gen_params.vae_tiling_params.tile_size_x},
{"tile_size_y", gen_params.vae_tiling_params.tile_size_y},
{"tile_size_w", gen_params.vae_tiling_params.tile_size_w},
{"tile_size_h", gen_params.vae_tiling_params.tile_size_h},
{"target_overlap", gen_params.vae_tiling_params.target_overlap},
{"rel_size_x", gen_params.vae_tiling_params.rel_size_x},
{"rel_size_y", gen_params.vae_tiling_params.rel_size_y},
{"rel_size_w", gen_params.vae_tiling_params.rel_size_w},
{"rel_size_h", gen_params.vae_tiling_params.rel_size_h},
{"extra_tiling_args", gen_params.extra_tiling_args},
};
}
+26 -23
View File
@@ -116,7 +116,8 @@ bool decode_base64_image(const std::string& encoded_input,
SDImageOwner& out_image);
struct SDContextParams {
int n_threads = -1;
int n_threads = -1;
int conditioning_cache_size = 4;
std::string model_path;
std::string clip_l_path;
std::string clip_g_path;
@@ -156,6 +157,7 @@ struct SDContextParams {
bool disable_prefetch = false;
bool disable_segmented_compute = false;
bool eager_load = false;
bool batched_cfg = true;
std::string backend;
std::string params_backend;
std::string split_mode;
@@ -164,14 +166,14 @@ struct SDContextParams {
std::string rpc_servers;
std::string effective_backend;
std::string effective_params_backend;
bool enable_mmap = false;
bool control_net_cpu = false;
bool clip_on_cpu = false;
bool vae_on_cpu = false;
bool flash_attn = false;
bool diffusion_flash_attn = false;
bool diffusion_conv_direct = false;
bool vae_conv_direct = false;
bool enable_mmap = false;
bool control_net_cpu = false;
bool clip_on_cpu = false;
bool vae_on_cpu = false;
std::string flash_attn_mode = "auto";
bool sage_attn = false;
bool diffusion_conv_direct = false;
bool vae_conv_direct = false;
prediction_t prediction = PREDICTION_COUNT;
lora_apply_mode_t lora_apply_mode = LORA_APPLY_AUTO;
@@ -199,18 +201,17 @@ struct SDGenerationParams {
std::string ad_prompt;
std::string ad_negative_prompt;
std::string extra_ad_args;
int clip_skip = -1; // <= 0 represents unspecified
int width = -1;
int height = -1;
int batch_count = 1;
int qwen_image_layers = 3;
int64_t seed = 42;
float strength = 0.75f;
float control_strength = 0.9f;
float ip_adapter_strength = 1.0f;
bool auto_resize_ref_image = true;
bool increase_ref_index = false;
bool embed_image_metadata = true;
int clip_skip = -1; // <= 0 represents unspecified
int width = -1;
int height = -1;
int batch_count = 1;
int qwen_image_layers = 3;
int64_t seed = 42;
float strength = 0.75f;
float control_strength = 0.9f;
float ip_adapter_strength = 1.0f;
bool increase_ref_index = false;
bool embed_image_metadata = true;
std::string init_image_path;
std::string end_image_path;
@@ -246,6 +247,7 @@ struct SDGenerationParams {
std::string extra_tiling_args;
std::string ref_image_args;
std::string image_preprocess;
std::string pm_id_images_dir;
std::string pm_id_embed_path;
@@ -309,13 +311,14 @@ struct SDGenerationParams {
ArgOptions get_options();
bool from_json_str(const std::string& json_str,
const std::function<std::string(const std::string&)>& lora_path_resolver = {});
bool parse_image_preprocess_json(const std::string& json_str);
bool initialize_cache_params();
void extract_and_remove_lora(const std::string& lora_model_dir);
bool extract_and_remove_lora(const std::string& lora_model_dir);
bool width_and_height_are_set() const;
void set_width_and_height_if_unset(int w, int h);
int get_resolved_width() const;
int get_resolved_height() const;
bool resolve(const std::string& lora_model_dir, const std::string& hires_upscalers_dir, bool strict = false);
bool resolve(const std::string& lora_model_dir, const std::string& hires_upscalers_dir, bool strict = false, bool validate_missing_loras = false);
bool validate(SDMode mode);
bool resolve_and_validate(SDMode mode,
const std::string& lora_model_dir,
+18 -21
View File
@@ -91,34 +91,34 @@ void log_print(enum sd_log_level_t level, const char* log, sd_log_level_t min_le
switch (level) {
case SD_LOG_DEBUG:
tag_color = 37;
level_str = "DEBUG";
level_str = "D";
break;
case SD_LOG_VERBOSE:
tag_color = 37;
level_str = "VERBOSE";
level_str = "V";
break;
case SD_LOG_INFO:
tag_color = 34;
level_str = "INFO";
level_str = "I";
break;
case SD_LOG_WARN:
tag_color = 35;
level_str = "WARN";
level_str = "W";
break;
case SD_LOG_ERROR:
tag_color = 31;
level_str = "ERROR";
level_str = "E";
break;
default:
tag_color = 33;
level_str = "?????";
level_str = "?";
break;
}
if (color) {
fprintf(out_stream, "\033[%d;1m[%-7s]\033[0m ", tag_color, level_str);
fprintf(out_stream, "\033[%d;1m[%s]\033[0m ", tag_color, level_str);
} else {
fprintf(out_stream, "[%-7s] ", level_str);
fprintf(out_stream, "[%s] ", level_str);
}
fflush(out_stream);
print_utf8(out_stream, log);
@@ -131,18 +131,15 @@ void example_log_printf(sd_log_level_t level, const char* file, int line, const
va_list args;
va_start(args, format);
static char log_buffer[LOG_BUFFER_SIZE + 1];
int written = snprintf(log_buffer, LOG_BUFFER_SIZE, "%s:%-4d - ", sd_basename(file).c_str(), line);
if (written >= 0 && written < static_cast<int>(LOG_BUFFER_SIZE)) {
vsnprintf(log_buffer + written, LOG_BUFFER_SIZE - written, format, args);
}
size_t len = strlen(log_buffer);
if (len == 0 || log_buffer[len - 1] != '\n') {
strncat(log_buffer, "\n", LOG_BUFFER_SIZE - len);
}
log_print(level, log_buffer, log_level, log_color);
char log_buffer[LOG_BUFFER_SIZE + 1] = {};
vsnprintf(log_buffer, sizeof(log_buffer), format, args);
va_end(args);
std::string message = log_buffer;
while (!message.empty() && (message.back() == '\n' || message.back() == '\r')) {
message.pop_back();
}
message += " --- " + sd_basename(file) + ":" + std::to_string(line) + '\n';
log_print(level, message.c_str(), log_level, log_color);
}
+41 -6
View File
@@ -261,6 +261,10 @@ uint8_t* decode_webp_image_to_buffer(const uint8_t* data,
height = features.height;
source_channel_count = features.has_alpha ? 4 : 3;
if (expected_channel == 0) {
expected_channel = source_channel_count;
}
const size_t pixel_count = static_cast<size_t>(width) * static_cast<size_t>(height);
if (expected_channel == 1) {
@@ -481,7 +485,8 @@ uint8_t* load_image_common(bool from_memory,
int& height,
int expected_width,
int expected_height,
int expected_channel) {
int expected_channel,
int& out_channel) {
const char* image_path;
FreeUniquePtr<uint8_t> image_buffer;
int source_channel_count = 0;
@@ -538,6 +543,32 @@ uint8_t* load_image_common(bool from_memory,
LOG_ERROR("load image from '%s' failed", image_path);
return nullptr;
}
if (expected_channel == 0) {
expected_channel = source_channel_count == 2 ? 4 : (source_channel_count == 1 ? 3 : source_channel_count);
if (expected_channel != source_channel_count) {
FreeUniquePtr<uint8_t> promoted((uint8_t*)malloc((size_t)width * height * expected_channel));
if (promoted == nullptr) {
LOG_ERROR("error: allocate memory for channel promotion, image_path = %s", image_path);
return nullptr;
}
const size_t pixel_count = (size_t)width * (size_t)height;
for (size_t i = 0; i < pixel_count; ++i) {
if (source_channel_count == 1) {
promoted.get()[i * 3 + 0] = image_buffer.get()[i];
promoted.get()[i * 3 + 1] = image_buffer.get()[i];
promoted.get()[i * 3 + 2] = image_buffer.get()[i];
} else {
promoted.get()[i * 4 + 0] = image_buffer.get()[i * 2];
promoted.get()[i * 4 + 1] = image_buffer.get()[i * 2];
promoted.get()[i * 4 + 2] = image_buffer.get()[i * 2];
promoted.get()[i * 4 + 3] = image_buffer.get()[i * 2 + 1];
}
}
image_buffer = std::move(promoted);
source_channel_count = expected_channel;
}
}
// stb reports the source channel count even when it converts the output.
if (source_channel_count < expected_channel) {
fprintf(stderr,
"the number of channels for the input image must be >= %d,"
@@ -597,7 +628,7 @@ uint8_t* load_image_common(bool from_memory,
}
stbir_resize(image_buffer.get(), width, height, 0,
resized_image_buffer.get(), expected_width, expected_height, 0, STBIR_TYPE_UINT8,
expected_channel, STBIR_ALPHA_CHANNEL_NONE, 0,
expected_channel, expected_channel == 4 ? 3 : STBIR_ALPHA_CHANNEL_NONE, 0,
STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP,
STBIR_FILTER_BOX, STBIR_FILTER_BOX,
STBIR_COLORSPACE_SRGB, nullptr);
@@ -605,6 +636,7 @@ uint8_t* load_image_common(bool from_memory,
height = expected_height;
image_buffer = std::move(resized_image_buffer);
}
out_channel = expected_channel;
return image_buffer.release();
}
@@ -777,10 +809,11 @@ bool write_image_to_file(const std::string& path,
uint8_t* load_image_from_file(const char* image_path,
int& width,
int& height,
int& out_channel,
int expected_width,
int expected_height,
int expected_channel) {
return load_image_common(false, image_path, 0, width, height, expected_width, expected_height, expected_channel);
return load_image_common(false, image_path, 0, width, height, expected_width, expected_height, expected_channel, out_channel);
}
bool load_sd_image_from_file(sd_image_t* image,
@@ -790,13 +823,14 @@ bool load_sd_image_from_file(sd_image_t* image,
int expected_channel) {
int width;
int height;
image->data = load_image_common(false, image_path, 0, width, height, expected_width, expected_height, expected_channel);
int resolved_channel = expected_channel;
image->data = load_image_common(false, image_path, 0, width, height, expected_width, expected_height, expected_channel, resolved_channel);
if (image->data == nullptr) {
return false;
}
image->width = width;
image->height = height;
image->channel = expected_channel;
image->channel = resolved_channel;
return true;
}
@@ -804,10 +838,11 @@ uint8_t* load_image_from_memory(const char* image_bytes,
int len,
int& width,
int& height,
int& out_channel,
int expected_width,
int expected_height,
int expected_channel) {
return load_image_common(true, image_bytes, len, width, height, expected_width, expected_height, expected_channel);
return load_image_common(true, image_bytes, len, width, height, expected_width, expected_height, expected_channel, out_channel);
}
static void append_avi_metadata(std::vector<uint8_t>& data, const std::string& parameters) {
+4
View File
@@ -32,9 +32,12 @@ bool write_image_to_file(const std::string& path,
const std::string& parameters = "",
int quality = 90);
// expected_channel == 0 preserves native channels (grayscale -> RGB, gray+alpha -> RGBA).
// out_channel receives the output channel count.
uint8_t* load_image_from_file(const char* image_path,
int& width,
int& height,
int& out_channel,
int expected_width = 0,
int expected_height = 0,
int expected_channel = 3);
@@ -49,6 +52,7 @@ uint8_t* load_image_from_memory(const char* image_bytes,
int len,
int& width,
int& height,
int& out_channel,
int expected_width = 0,
int expected_height = 0,
int expected_channel = 3);
+3 -3
View File
@@ -21,21 +21,21 @@ if(SD_SERVER_BUILD_FRONTEND AND EXISTS "${FRONTEND_DIR}")
set(HAVE_FRONTEND_BUILD ON)
add_custom_target(${TARGET}_frontend_install
COMMAND "${PNPM_EXECUTABLE}" -C "${FRONTEND_DIR}" install
COMMAND "${PNPM_EXECUTABLE}" --ignore-workspace -C "${FRONTEND_DIR}" install
WORKING_DIRECTORY "${FRONTEND_DIR}"
COMMENT "Installing frontend dependencies"
VERBATIM
)
add_custom_target(${TARGET}_frontend_build
COMMAND "${PNPM_EXECUTABLE}" -C "${FRONTEND_DIR}" run build
COMMAND "${PNPM_EXECUTABLE}" --ignore-workspace -C "${FRONTEND_DIR}" run build
WORKING_DIRECTORY "${FRONTEND_DIR}"
COMMENT "Building frontend"
VERBATIM
)
add_custom_target(${TARGET}_frontend_header
COMMAND "${PNPM_EXECUTABLE}" -C "${FRONTEND_DIR}" run build:header
COMMAND "${PNPM_EXECUTABLE}" --ignore-workspace -C "${FRONTEND_DIR}" run build:header
WORKING_DIRECTORY "${FRONTEND_DIR}"
COMMENT "Generating gen_index_html.h"
VERBATIM
+1 -2
View File
@@ -3,7 +3,7 @@
The following example starts `sd-server` with a standalone diffusion model, VAE, and LLM text encoder:
```
.\bin\Release\sd-server.exe --diffusion-model ..\models\diffusion_models\z_image_turbo_bf16.safetensors --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\qwen_3_4b.safetensors --diffusion-fa --offload-to-cpu -v --cfg-scale 1.0
.\bin\Release\sd-server.exe --diffusion-model ..\models\diffusion_models\z_image_turbo_bf16.safetensors --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\qwen_3_4b.safetensors --offload-to-cpu -v --cfg-scale 1.0
```
What this example does:
@@ -11,7 +11,6 @@ What this example does:
* `--diffusion-model` selects the standalone diffusion model
* `--vae` selects the VAE decoder
* `--llm` selects the text encoder / language model used by this pipeline
* `--diffusion-fa` enables flash attention in the diffusion model
* `--offload-to-cpu` reduces VRAM pressure by keeping weights in RAM when possible
* `-v` enables verbose logging (equivalent to `--log-level verbose`)
* `--cfg-scale 1.0` sets the default CFG scale for generation
+176 -27
View File
@@ -37,6 +37,7 @@ Current generation-related endpoints include:
- `POST /sdapi/v1/txt2img`
- `POST /sdapi/v1/img2img`
- `GET /sdapi/v1/progress`
- `GET /sdapi/v1/loras`
- `GET /sdapi/v1/upscalers`
- `GET /sdapi/v1/latent-upscale-modes`
@@ -56,6 +57,7 @@ Current endpoints include:
- `GET /sdcpp/v1/jobs/{id}`
- `POST /sdcpp/v1/jobs/{id}/cancel`
- `POST /sdcpp/v1/vid_gen`
- `POST /sdcpp/v1/upscale`
## `sd_cpp_extra_args`
@@ -148,6 +150,19 @@ Native extension fields:
- any `sdcpp API` fields embedded through `sd_cpp_extra_args` inside `prompt`
Uploaded images are decoded at their original dimensions. The first decoded
image establishes the generation dimensions if `size` is omitted. Input
geometry follows `image_preprocess`: references preserve their dimensions by
default, while init and mask use the generation canvas preset.
Reference encoding then follows model presets and `ref_image_args`. To skip
input geometry for references and disable resizing before VAE encoding, include
this in `prompt`:
```text
edit this image <sd_cpp_extra_args>{"image_preprocess":"target=ref,mode=none","ref_image_args":"resize_before_vae=false"}</sd_cpp_extra_args>
```
Response fields:
| Field | Type | Notes |
@@ -264,6 +279,26 @@ Response fields:
| `parameters` | `object` | Echo of the parsed outer request body |
| `info` | `string` | Currently empty string |
#### `GET /sdapi/v1/progress`
Poll this endpoint while a synchronous SDAPI generation request is running.
An optional `id_task` query parameter selects the task with the same `id_task`
provided in the generation request (default: `"sdapi"`). Set
`skip_current_image=true` or `1` to omit the preview image.
`current_image` contains the latest base64 JPEG latent projection, or `null`
when unavailable. `state.sampling_step` is the positive logical step and
`state.sampling_steps` is the actual step count for the current sampling pass,
including schedule and img2img strength adjustments. `state.job_no` is the
zero-based pass index; `state.job_count` includes one pass per batch image and
an additional pass per image when highres fix is enabled. `progress` weights
these passes equally. Sampling progress can reach `1` before final decoding
finishes; `eta_relative` is currently always `0`.
When no SDAPI task is active, or `id_task` does not match, the endpoint returns
`progress=0`, `state.job=""`, zero job/step counts, and `current_image=null`.
Completed and failed requests do not retain an active preview.
#### Discovery / Compatibility Endpoints
Currently exposed:
@@ -396,6 +431,20 @@ Field types:
| `queue_position` | `integer` |
| `result` | `object \| null` |
| `error` | `object \| null` |
| `preview` | `object \| null` |
`preview` sub-fields:
| Field | Type | Notes |
| --- | --- | --- |
| `preview.pass` | `integer` | Sampling pass number, starting at 1 within the job |
| `preview.step` | `integer` | Positive logical sample step within this pass |
| `preview.total_steps` | `integer` | Actual sample steps in this pass, after schedule and strength adjustments |
| `preview.b64_json` | `string` | Base64-encoded PNG of the current preview frame |
The preview is updated in place as sampling progresses; poll `GET /sdcpp/v1/jobs/{id}` to retrieve the latest frame. Only the most recent preview is retained.
`step / total_steps` describes the current sampling pass, not overall job completion. A new batch item, high/low-noise stage, or highres pass increments `pass` and restarts `step`. Samplers with multiple denoiser evaluations per logical step may update a preview more than once at the same step. The frame and its pass/step metadata are published together.
### Endpoints
@@ -421,7 +470,9 @@ Top-level fields:
| `samplers` | `array<string>` | Available sampling methods |
| `schedulers` | `array<string>` | Available schedulers |
| `loras` | `array<object>` | Available LoRA entries |
| `upscalers` | `array<object>` | Available model-backed highres upscalers |
| `upscalers` | `array<object>` | Available highres upscalers, built-in and model-backed |
| `upscale` | `boolean` | Whether a compatible RGB ESRGAN model is available for `POST /sdcpp/v1/upscale` |
| `preview_modes` | `array<string>` | Available preview modes, e.g. `["none", "proj", "tae", "vae"]` |
| `limits` | `object` | Shared queue and size limits |
`model`
@@ -463,6 +514,8 @@ Shared nested fields:
| Field | Type | Notes |
| --- | --- | --- |
| `upscalers[].name` | `string` | Built-in name or model stem; use this value in `hires.upscaler` |
| `upscalers[].model` | `boolean` | True for a model-backed upscaler, false for a built-in scaling filter |
| `upscalers[].image_upscale` | `boolean` | Whether this model can be selected by `POST /sdcpp/v1/upscale`; false for latent upscalers and built-in filters |
Built-in entries include `None`, `Lanczos`, `Nearest`, `Latent`, `Latent (nearest)`, `Latent (nearest-exact)`, `Latent (antialiased)`, `Latent (bicubic)`, and `Latent (bicubic antialiased)`. Model-backed entries are scanned from the top level of `--hires-upscalers-dir`; subdirectories are not scanned.
@@ -476,6 +529,8 @@ Built-in entries include `None`, `Lanczos`, `Nearest`, `Latent`, `Latent (neares
| `limits.max_height` | `integer` |
| `limits.max_batch_count` | `integer` |
| `limits.max_queue_size` | `integer` |
| `limits.max_upscale_width` | `integer` |
| `limits.max_upscale_height` | `integer` |
Shared default fields used by both `img_gen` and `vid_gen`:
@@ -505,11 +560,11 @@ Shared default fields used by both `img_gen` and `vid_gen`:
| `vae_tiling_params` | `object` |
| `vae_tiling_params.enabled` | `boolean` |
| `vae_tiling_params.temporal_tiling` | `boolean` |
| `vae_tiling_params.tile_size_x` | `integer` |
| `vae_tiling_params.tile_size_y` | `integer` |
| `vae_tiling_params.tile_size_w` | `integer` |
| `vae_tiling_params.tile_size_h` | `integer` |
| `vae_tiling_params.target_overlap` | `number` |
| `vae_tiling_params.rel_size_x` | `number` |
| `vae_tiling_params.rel_size_y` | `number` |
| `vae_tiling_params.rel_size_w` | `number` |
| `vae_tiling_params.rel_size_h` | `number` |
| `vae_tiling_params.extra_tiling_args` | `string` |
| `cache_mode` | `string` |
| `cache_option` | `string` |
@@ -518,6 +573,8 @@ Shared default fields used by both `img_gen` and `vid_gen`:
| `output_format` | `string` |
| `output_compression` | `integer` |
`vae_tiling_params.tile_size_w` and `tile_size_h` are in **image pixels**, with `0` selecting the 256-pixel default. Both encode and decode use these sizes without an encoding multiplier. Positive `rel_size_w`/`rel_size_h` values override the corresponding absolute size: values up to 1 are dimension fractions, and values greater than 1 are target tile counts. Set `enabled` to use spatial tiling. Sizes are aligned down to the VAE scale factor and capped at the input dimensions; explicit sizes below the minimum supported tile size are rejected. These fields previously used latent units; see [VAE tiling](../../docs/performance.md#use-vae-tiling-to-reduce-encode-and-decode-memory-usage) for migration and OOM retry behavior.
`vae_tiling_params.extra_tiling_args` accepts a key=value list. Supported video VAEs accept `temporal_tile_frames` (alias `temporal_tile_size`, default `4`) and `temporal_tile_overlap` (default `1`).
LTX and Wan preserve causal state between temporal tiles. Hunyuan Video and TAEHV use overlap blending. MiniMax H3 keeps its model-specific fixed temporal windows because its latent-to-frame mapping is non-linear.
@@ -526,7 +583,7 @@ LTX and Wan preserve causal state between temporal tiles. Hunyuan Video and TAEH
| Field | Type |
| --- | --- |
| `batch_count` | `integer` |
| `auto_resize_ref_image` | `boolean` |
| `ref_image_args` | `string` |
| `increase_ref_index` | `boolean` |
| `control_strength` | `number` |
| `ip_adapter_strength` | `number` |
@@ -577,6 +634,7 @@ Fields returned in `features_by_mode.img_gen`:
- `cache`
- `cancel_queued`
- `cancel_generating`
- `preview`
Fields returned in `features_by_mode.vid_gen`:
@@ -589,6 +647,7 @@ Fields returned in `features_by_mode.vid_gen`:
- `cache`
- `cancel_queued`
- `cancel_generating`
- `preview`
#### `POST /sdcpp/v1/img_gen`
@@ -628,6 +687,52 @@ Typical status codes:
- `404 Not Found`
- `410 Gone`
#### `POST /sdcpp/v1/upscale`
Runs one RGB ESRGAN upscaler over an image, with no generation involved. Latent upscaler models remain available for hires generation but cannot be used here.
This is the HTTP equivalent of `sd-cli -M upscale`: no diffusion model, text
encoder or sampling is used, so it is fast enough to answer synchronously and
does not create a job.
Request fields:
| Field | Type | Notes |
| --- | --- | --- |
| `image` | `string` | Required. Base64 or data URL image |
| `upscaler` | `string` | A name from `upscalers` with `image_upscale: true`; the first compatible entry when omitted |
| `repeats` | `integer` | Run the upscaler this many times, 1 to 4 (default `1`) |
| `tile_size` | `integer` | Tile size, defaulting to the server's `--upscale-tile-size` |
| `output_format` | `string` | `png`, `jpeg`, or `webp` when built with WebP support (default `png`); unsupported formats return 400 |
| `output_compression` | `integer` | Range is clamped to `0..100` |
Response fields:
| Field | Type | Notes |
| --- | --- | --- |
| `images` | `array<object>` | One image |
| `images[].index` | `integer` | |
| `images[].b64_json` | `string` | Base64-encoded image bytes |
| `upscaler` | `string` | The upscaler actually used |
| `scale` | `integer` | The model's scale factor |
| `repeats` | `integer` | How many times it was run |
| `width` | `integer` | Result width |
| `height` | `integer` | Result height |
| `output_format` | `string` | Final encoded image format |
Typical status codes:
- `200 OK`
- `400 Bad Request` (invalid request, unsupported output format, unreadable image, incompatible upscaler, or output dimensions exceeding the limit)
- `500 Internal Server Error`
Notes:
- Final output dimensions, including all repeats, must not exceed 8192 pixels on either axis (`limits.max_upscale_width` and `limits.max_upscale_height`). Requests exceeding this bound are rejected before upscaling.
- The upscaler models are three-channel; alpha is not preserved.
- The request holds the generation context lock, so an upscale and a
generation never run on the device at the same time.
#### `POST /sdcpp/v1/jobs/{id}/cancel`
Attempts to cancel an accepted job.
@@ -653,7 +758,7 @@ Example:
"strength": 0.75,
"seed": -1,
"batch_count": 1,
"auto_resize_ref_image": true,
"ref_image_args": "",
"increase_ref_index": false,
"control_strength": 0.9,
"ip_adapter_strength": 1.0,
@@ -702,11 +807,11 @@ Example:
"vae_tiling_params": {
"enabled": false,
"temporal_tiling": false,
"tile_size_x": 0,
"tile_size_y": 0,
"tile_size_w": 0,
"tile_size_h": 0,
"target_overlap": 0.5,
"rel_size_x": 0.0,
"rel_size_y": 0.0,
"rel_size_w": 0.0,
"rel_size_h": 0.0,
"extra_tiling_args": ""
},
@@ -715,6 +820,9 @@ Example:
"scm_mask": "",
"scm_policy_dynamic": true,
"preview": "none",
"preview_interval": 1,
"output_format": "png",
"output_compression": 100
}
@@ -728,6 +836,17 @@ Example:
### Image Encoding Rules
Native image/video requests and SDAPI accept `image_preprocess` as a rule string
or array of rule strings. OpenAI-compatible requests can supply it in
`sd_cpp_extra_args`. See [Image preprocessing](../../docs/image_preprocessing.md)
for one-time input geometry, native-resolution decoding, mask alignment, and
`canny=true` for edge detection on any supported image input.
Image generation also accepts `ref_image_args` as a string (for example,
`"resize_before_vae=false"`) in native and SDAPI requests, or through
`sd_cpp_extra_args` in OpenAI-compatible requests. It controls downstream
reference encoding and is independent of input geometry rules.
Any image field accepts:
- a raw base64 string, or
@@ -735,12 +854,15 @@ Any image field accepts:
Channel expectations:
- `init_image`: 3 channels
- `ref_images[]`: 3 channels
- `init_image`: native channels (3 or 4); alpha is preserved and applied per model
- `ref_images[]`: native channels (3 or 4); alpha is preserved and applied per model
- `control_image`: 3 channels
- `ip_adapter_image`: 3 channels
- `mask_image`: 1 channel
Models that support RGBA (e.g. Qwen-Image 2.1) use the alpha channel of `init_image`
and `ref_images[]`. RGB-only models drop it, so sending RGBA is safe for every model.
If omitted or null:
- single-image fields map to an empty `sd_image_t`
@@ -760,11 +882,14 @@ Top-level scalar fields:
| `strength` | `number` |
| `seed` | `integer` |
| `batch_count` | `integer` |
| `auto_resize_ref_image` | `boolean` |
| `ref_image_args` | `string` |
| `image_preprocess` | `string \| array<string>` |
| `increase_ref_index` | `boolean` |
| `control_strength` | `number` |
| `ip_adapter_strength` | `number` |
| `embed_image_metadata` | `boolean` |
| `preview` | `string` |
| `preview_interval` | `integer` |
Image fields:
@@ -820,11 +945,11 @@ Other native fields:
| `vae_tiling_params` | `object` |
| `vae_tiling_params.enabled` | `boolean` |
| `vae_tiling_params.temporal_tiling` | `boolean` |
| `vae_tiling_params.tile_size_x` | `integer` |
| `vae_tiling_params.tile_size_y` | `integer` |
| `vae_tiling_params.tile_size_w` | `integer` |
| `vae_tiling_params.tile_size_h` | `integer` |
| `vae_tiling_params.target_overlap` | `number` |
| `vae_tiling_params.rel_size_x` | `number` |
| `vae_tiling_params.rel_size_y` | `number` |
| `vae_tiling_params.rel_size_w` | `number` |
| `vae_tiling_params.rel_size_h` | `number` |
| `vae_tiling_params.extra_tiling_args` | `string` |
| `cache_mode` | `string` |
| `cache_option` | `string` |
@@ -852,6 +977,10 @@ When omitted, backend defaults apply to these fields:
- `sample_params.flow_shift`
- `sample_params.guidance.img_cfg`
### Preview Interval Semantics
`preview_interval` controls the period (in sample steps) at which preview frames are generated. The default is `1` (every step). Any non-positive values will be clamped to `1`. Note that if `preview` is set to `"none"`, no previews are produced regardless of the interval.
### Completion Result
Example completed job:
@@ -966,7 +1095,8 @@ Response fields:
Compared with `img_gen`, the `vid_gen` request body:
- `vid_gen` is a single video sequence job, so `batch_count` is not part of the request schema
- `ref_images`, `mask_image`, `control_image`, `control_strength`, `ip_adapter_image`, `ip_adapter_strength`, and `embed_image_metadata` are not part of the request schema
- `mask_image`, `control_image`, `control_strength`, `ip_adapter_image`, `ip_adapter_strength`, and `embed_image_metadata` are not part of the request schema
- `ref_images` is accepted for MiniMax-H3 Ref2VA conditioning; other video model families currently ignore it
- `vid_gen` adds `end_image`, `control_frames`, `high_noise_sample_params`, `video_frames`, `fps`, `moe_boundary`, and `vace_strength`
Example:
@@ -987,6 +1117,7 @@ Example:
"init_image": null,
"end_image": null,
"ref_images": [],
"control_frames": [],
"sample_params": {
@@ -1035,11 +1166,11 @@ Example:
"vae_tiling_params": {
"enabled": false,
"temporal_tiling": false,
"tile_size_x": 0,
"tile_size_y": 0,
"tile_size_w": 0,
"tile_size_h": 0,
"target_overlap": 0.5,
"rel_size_x": 0.0,
"rel_size_y": 0.0,
"rel_size_w": 0.0,
"rel_size_h": 0.0,
"extra_tiling_args": ""
},
@@ -1048,11 +1179,25 @@ Example:
"scm_mask": "",
"scm_policy_dynamic": true,
"preview": "none",
"preview_interval": 1,
"output_format": "webm",
"output_compression": 100
}
```
### Reference Image Rules
- `ref_images` contains reference images for MiniMax-H3 Ref2VA conditioning.
- Images retain request order and correspond to `<Picture 1>`, `<Picture 2>`, and so on in the prompt.
- For MiniMax-H3, non-empty `ref_images` cannot be combined with `init_image` or `end_image`. A conflicting API request fails during generation; the WebUI checks this before submission.
- MiniMax-H3 does not support `control_frames`; leave that array empty.
- Other video model families currently ignore `ref_images`.
- `features_by_mode.vid_gen` does not currently advertise `ref_images`; its absence is not an indication that MiniMax-H3 reference images are unsupported.
See [MiniMax-H3 reference conditioning](../../docs/minimax_h3.md#reference-to-audio-video-conditioning) for model requirements and prompt examples.
### LoRA Rules
- The server only accepts explicit LoRA entries from the `lora` field.
@@ -1070,6 +1215,7 @@ Channel expectations:
- `init_image`: 3 channels
- `end_image`: 3 channels
- `ref_images[]`: decoded with native channels, then converted to RGB by MiniMax-H3
- `control_frames[]`: 3 channels
Frame ordering rules:
@@ -1099,6 +1245,8 @@ Top-level scalar fields:
| `fps` | `integer` |
| `moe_boundary` | `number` |
| `vace_strength` | `number` |
| `preview` | `string` |
| `preview_interval` | `integer` |
Image and frame fields:
@@ -1106,6 +1254,7 @@ Image and frame fields:
| --- | --- |
| `init_image` | `string \| null` |
| `end_image` | `string \| null` |
| `ref_images` | `array<string>` |
| `control_frames` | `array<string>` |
LoRA fields:
@@ -1160,11 +1309,11 @@ Other native fields:
| `vae_tiling_params` | `object` |
| `vae_tiling_params.enabled` | `boolean` |
| `vae_tiling_params.temporal_tiling` | `boolean` |
| `vae_tiling_params.tile_size_x` | `integer` |
| `vae_tiling_params.tile_size_y` | `integer` |
| `vae_tiling_params.tile_size_w` | `integer` |
| `vae_tiling_params.tile_size_h` | `integer` |
| `vae_tiling_params.target_overlap` | `number` |
| `vae_tiling_params.rel_size_x` | `number` |
| `vae_tiling_params.rel_size_y` | `number` |
| `vae_tiling_params.rel_size_w` | `number` |
| `vae_tiling_params.rel_size_h` | `number` |
| `vae_tiling_params.extra_tiling_args` | `string` |
| `cache_mode` | `string` |
| `cache_option` | `string` |
+65
View File
@@ -10,6 +10,55 @@
#include "common/media_io.h"
#include "common/resource_owners.hpp"
static void preview_callback(int step, int frame_count, sd_image_t* frames, bool is_noisy, void* data) {
(void)is_noisy;
if (frame_count <= 0 || frames == nullptr || frames[0].data == nullptr) {
return;
}
AsyncGenerationJob* job = static_cast<AsyncGenerationJob*>(data);
const sd_image_t& frame = frames[0];
const sd_preview_info_t info = sd_get_preview_info();
auto image_bytes = encode_image_to_vector(EncodedImageFormat::PNG,
frame.data,
frame.width,
frame.height,
frame.channel,
"",
80);
if (image_bytes.empty()) {
return;
}
std::string b64 = base64_encode(image_bytes);
std::lock_guard<std::mutex> lock(job->preview_mutex);
job->preview_b64 = std::move(b64);
job->preview_step = step < 0 ? -step : step;
job->preview_pass = info.sample_pass;
job->preview_total_steps = info.total_steps;
}
static void clear_preview_callback() {
sd_set_preview_callback(nullptr, PREVIEW_NONE, 1, false, false, nullptr);
}
static void set_preview_callback(AsyncGenerationJob& job) {
preview_t mode = str_to_preview(job.kind == AsyncJobKind::ImgGen
? job.img_gen.preview_mode.c_str()
: job.vid_gen.preview_mode.c_str());
if (mode == PREVIEW_COUNT || mode == PREVIEW_NONE) {
return;
}
int interval = job.kind == AsyncJobKind::ImgGen
? job.img_gen.preview_interval
: job.vid_gen.preview_interval;
if (interval <= 0) {
interval = 1;
}
sd_set_preview_callback(preview_callback, mode, interval, true, false, &job);
}
const char* async_job_kind_name(AsyncJobKind kind) {
switch (kind) {
case AsyncJobKind::ImgGen:
@@ -161,6 +210,18 @@ json make_async_job_json(const AsyncJobManager& manager, const AsyncGenerationJo
result["error"] = nullptr;
}
if (job.status == AsyncJobStatus::Generating) {
std::lock_guard<std::mutex> lock(job.preview_mutex);
if (!job.preview_b64.empty()) {
result["preview"] = {
{"pass", job.preview_pass},
{"step", job.preview_step},
{"total_steps", job.preview_total_steps},
{"b64_json", job.preview_b64},
};
}
}
return result;
}
@@ -174,12 +235,14 @@ bool execute_img_gen_job(ServerRuntime& runtime,
{
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
set_preview_callback(job);
sd_image_t* raw_results = nullptr;
int num_results = 0;
if (!generate_image(runtime.sd_ctx, &params, &raw_results, &num_results)) {
raw_results = nullptr;
num_results = 0;
}
clear_preview_callback();
results.adopt(raw_results, num_results);
}
@@ -247,10 +310,12 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
{
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
set_preview_callback(job);
sd_image_t* raw_results = nullptr;
if (!generate_video(runtime.sd_ctx, &params, &raw_results, &num_results, &generated_audio, &output_fps)) {
raw_results = nullptr;
}
clear_preview_callback();
results.adopt(raw_results, num_results);
}
+5
View File
@@ -44,6 +44,11 @@ struct AsyncGenerationJob {
int result_fps = 0;
std::string error_code;
std::string error_message;
mutable std::mutex preview_mutex;
std::string preview_b64;
int preview_pass = 0;
int preview_step = 0;
int preview_total_steps = 0;
};
struct AsyncJobManager {
+3 -1
View File
@@ -75,10 +75,12 @@ int main(int argc, const char** argv) {
}
SDSvrParams svr_params;
SDContextParams ctx_params;
// Preserve the CLI default while disabling cwd scans for sd-server.
ctx_params.lora_model_dir.clear();
SDGenerationParams default_gen_params;
parse_args(argc, argv, svr_params, ctx_params, default_gen_params);
sd_set_log_callback(sd_log_cb, (void*)&svr_params);
parse_args(argc, argv, svr_params, ctx_params, default_gen_params);
LOG_VERBOSE("version: %s", version_string().c_str());
LOG_VERBOSE("%s", sd_get_system_info());
+26 -23
View File
@@ -157,42 +157,46 @@ static bool build_openai_edit_request(const httplib::Request& req,
request.gen_params.height = height;
request.gen_params.batch_count = n;
std::string sd_cpp_extra_args_str = extract_and_remove_sd_cpp_extra_args(request.gen_params.prompt);
for (auto& bytes : images_bytes) {
int img_w = 0;
int img_h = 0;
uint8_t* raw_pixels = load_image_from_memory(
reinterpret_cast<const char*>(bytes.data()),
static_cast<int>(bytes.size()),
img_w, img_h,
width, height, 3);
int img_w = 0;
int img_h = 0;
int resolved_channel = 0;
uint8_t* raw_pixels = load_image_from_memory(
reinterpret_cast<const char*>(bytes.data()),
static_cast<int>(bytes.size()),
img_w, img_h, resolved_channel,
0, 0,
0);
if (raw_pixels == nullptr) {
continue;
}
SDImageOwner image_owner({(uint32_t)img_w, (uint32_t)img_h, 3, raw_pixels});
const bool is_first_ref_image = request.gen_params.ref_images.empty();
SDImageOwner image_owner({(uint32_t)img_w, (uint32_t)img_h, (uint32_t)resolved_channel, raw_pixels});
request.gen_params.set_width_and_height_if_unset(image_owner.get().width, image_owner.get().height);
if (is_first_ref_image) {
request.gen_params.init_image = image_owner;
if (request.gen_params.init_image.get().data == nullptr) {
error_message = "could not allocate init image";
return false;
}
}
request.gen_params.ref_images.push_back(std::move(image_owner));
}
if (!request.gen_params.ref_images.empty()) {
request.gen_params.init_image = request.gen_params.ref_images.front();
}
if (!mask_bytes.empty()) {
int expected_width = 0;
int expected_height = 0;
if (request.gen_params.width_and_height_are_set()) {
expected_width = request.gen_params.width;
expected_height = request.gen_params.height;
}
int mask_w = 0;
int mask_h = 0;
int mask_w = 0;
int mask_h = 0;
int mask_channel = 0;
uint8_t* mask_raw = load_image_from_memory(
reinterpret_cast<const char*>(mask_bytes.data()),
static_cast<int>(mask_bytes.size()),
mask_w, mask_h,
expected_width, expected_height, 1);
mask_w, mask_h, mask_channel,
0, 0, 1);
request.gen_params.mask_image.reset({(uint32_t)mask_w, (uint32_t)mask_h, 1, mask_raw});
const sd_image_t& mask_image = request.gen_params.mask_image.get();
request.gen_params.set_width_and_height_if_unset(mask_image.width, mask_image.height);
@@ -205,7 +209,6 @@ static bool build_openai_edit_request(const httplib::Request& req,
});
}
std::string sd_cpp_extra_args_str = extract_and_remove_sd_cpp_extra_args(request.gen_params.prompt);
if (!sd_cpp_extra_args_str.empty() && !request.gen_params.from_json_str(sd_cpp_extra_args_str)) {
error_message = "invalid sd_cpp_extra_args";
return false;
+153 -26
View File
@@ -80,17 +80,6 @@ static enum sample_method_t get_sdapi_sample_method(std::string name) {
return it != hardcoded.end() ? it->second : SAMPLE_METHOD_COUNT;
}
static void assign_solid_mask(SDImageOwner& mask_owner, int width, int height) {
const size_t pixel_count = static_cast<size_t>(width) * static_cast<size_t>(height);
uint8_t* raw_mask = static_cast<uint8_t*>(malloc(pixel_count));
if (raw_mask == nullptr) {
mask_owner.reset({0, 0, 1, nullptr});
return;
}
std::memset(raw_mask, 255, pixel_count);
mask_owner.reset({(uint32_t)width, (uint32_t)height, 1, raw_mask});
}
static bool build_sdapi_img_gen_request(const json& j,
ServerRuntime& runtime,
bool img2img,
@@ -193,15 +182,25 @@ static bool build_sdapi_img_gen_request(const json& j,
}
}
if (img2img) {
const int expected_width = request.gen_params.width_and_height_are_set() ? request.gen_params.width : 0;
const int expected_height = request.gen_params.width_and_height_are_set() ? request.gen_params.height : 0;
if (j.contains("ref_image_args")) {
if (!j["ref_image_args"].is_string()) {
error_message = "ref_image_args must be a string";
return false;
}
request.gen_params.ref_image_args = j["ref_image_args"].get<std::string>();
}
if (j.contains("image_preprocess") && !request.gen_params.parse_image_preprocess_json(j["image_preprocess"].dump())) {
error_message = "invalid image_preprocess";
return false;
}
if (img2img) {
if (j.contains("init_images") && j["init_images"].is_array() && !j["init_images"].empty()) {
if (decode_base64_image(j["init_images"][0].get<std::string>(),
3,
expected_width,
expected_height,
0,
0,
0,
request.gen_params.init_image)) {
const sd_image_t& image = request.gen_params.init_image.get();
request.gen_params.set_width_and_height_if_unset(image.width, image.height);
@@ -211,8 +210,8 @@ static bool build_sdapi_img_gen_request(const json& j,
if (j.contains("mask") && j["mask"].is_string()) {
if (decode_base64_image(j["mask"].get<std::string>(),
1,
expected_width,
expected_height,
0,
0,
request.gen_params.mask_image)) {
const sd_image_t& image = request.gen_params.mask_image.get();
request.gen_params.set_width_and_height_if_unset(image.width, image.height);
@@ -225,9 +224,7 @@ static bool build_sdapi_img_gen_request(const json& j,
}
}
} else {
const int resolved_width = request.gen_params.get_resolved_width();
const int resolved_height = request.gen_params.get_resolved_height();
assign_solid_mask(request.gen_params.mask_image, resolved_width, resolved_height);
request.gen_params.mask_image.reset({0, 0, 1, nullptr});
}
float denoising_strength = j.value("denoising_strength", -1.f);
@@ -243,9 +240,8 @@ static bool build_sdapi_img_gen_request(const json& j,
}
SDImageOwner image_owner;
if (decode_base64_image(extra_image.get<std::string>(),
3,
request.gen_params.width_and_height_are_set() ? request.gen_params.width : 0,
request.gen_params.width_and_height_are_set() ? request.gen_params.height : 0,
0,
0, 0,
image_owner)) {
const sd_image_t& image = image_owner.get();
request.gen_params.set_width_and_height_if_unset(image.width, image.height);
@@ -305,6 +301,76 @@ static nlohmann::json prepare_info_field(const SDContextParams& ctx_params,
return jsoninfo;
}
struct SDAPIPreviewState {
std::mutex mutex;
std::string b64;
std::string id_task;
std::string job_type;
int step = 0;
int total = 0;
bool active = false;
int64_t job_count = 0;
int job_no = 0;
};
static SDAPIPreviewState g_preview;
static void preview_callback(int step, int frame_count, sd_image_t* frames, bool is_noisy, void* data) {
(void)is_noisy;
(void)data;
if (frame_count <= 0 || frames == nullptr || frames[0].data == nullptr) {
return;
}
const sd_image_t& frame = frames[0];
const sd_preview_info_t info = sd_get_preview_info();
auto image_bytes = encode_image_to_vector(EncodedImageFormat::JPEG,
frame.data,
frame.width,
frame.height,
frame.channel,
"",
80);
if (image_bytes.empty()) {
return;
}
std::string b64 = base64_encode(image_bytes);
std::lock_guard<std::mutex> lock(g_preview.mutex);
g_preview.b64 = std::move(b64);
g_preview.step = static_cast<int>(std::min<int64_t>(step < 0 ? -static_cast<int64_t>(step) : step,
std::max(0, info.total_steps)));
g_preview.total = std::max(0, info.total_steps);
g_preview.job_no = std::max(0, info.sample_pass - 1);
}
struct SDAPIPreviewGuard {
SDAPIPreviewGuard(const std::string& id_task, const sd_img_gen_params_t& params, const std::string& job_type) {
std::lock_guard<std::mutex> lock(g_preview.mutex);
g_preview.id_task = id_task;
g_preview.total = 0;
g_preview.job_type = job_type;
g_preview.b64.clear();
g_preview.step = 0;
g_preview.active = true;
// Each batch item has a base pass and, when enabled, a hires pass.
g_preview.job_count = static_cast<int64_t>(std::max(1, params.batch_count)) * (params.hires.enabled ? 2 : 1);
g_preview.job_no = 0;
sd_set_preview_callback(preview_callback, PREVIEW_PROJ, 1, true, false, nullptr);
}
~SDAPIPreviewGuard() {
sd_set_preview_callback(nullptr, PREVIEW_NONE, 1, false, false, nullptr);
std::lock_guard<std::mutex> lock(g_preview.mutex);
g_preview.active = false;
g_preview.b64.clear();
}
SDAPIPreviewGuard(const SDAPIPreviewGuard&) = delete;
SDAPIPreviewGuard& operator=(const SDAPIPreviewGuard&) = delete;
};
void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
ServerRuntime* runtime = &rt;
@@ -321,7 +387,9 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
return;
}
json j = json::parse(req.body);
std::string id_task = "sdapi";
json j = json::parse(req.body);
id_task = j.value("id_task", id_task);
ImgGenJobRequest request;
std::string error_message;
if (!build_sdapi_img_gen_request(j, *runtime, img2img, request, error_message)) {
@@ -338,6 +406,7 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
{
std::lock_guard<std::mutex> lock(*runtime->sd_ctx_mutex);
SDAPIPreviewGuard preview_guard(id_task, img_gen_params, img2img ? "img2img" : "txt2img");
sd_image_t* raw_results = nullptr;
if (!generate_image(runtime->sd_ctx, &img_gen_params, &raw_results, &num_results)) {
raw_results = nullptr;
@@ -532,4 +601,62 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
r["sd_model_checkpoint"] = model_path.stem();
res.set_content(r.dump(), "application/json");
});
svr.Get("/sdapi/v1/progress", [](const httplib::Request& req, httplib::Response& res) {
bool skip_current_image = false;
bool has_id_param = req.has_param("id_task");
std::string req_id_task = has_id_param ? req.get_param_value("id_task") : "";
if (req.has_param("skip_current_image")) {
std::string skip_str = req.get_param_value("skip_current_image");
skip_current_image = (skip_str == "true" || skip_str == "1");
}
json r;
r["eta_relative"] = 0.0f;
json state;
state["skipped"] = false;
state["interrupted"] = false;
state["job_timestamp"] = "0";
r["textinfo"] = nullptr;
{
std::lock_guard<std::mutex> lock(g_preview.mutex);
bool is_matching_task = g_preview.active && (!has_id_param || req_id_task == g_preview.id_task);
int step = is_matching_task ? g_preview.step : 0;
int steps = is_matching_task ? g_preview.total : 0;
std::string b64 = is_matching_task ? g_preview.b64 : "";
std::string job_type = is_matching_task ? g_preview.job_type : "";
int64_t job_count = is_matching_task ? g_preview.job_count : 0;
int job_no = is_matching_task ? g_preview.job_no : 0;
float batch_progress = (steps > 0) ? (static_cast<float>(step) / static_cast<float>(steps)) : 0.0f;
if (batch_progress > 1.0f)
batch_progress = 1.0f;
float overall_progress = job_count > 0
? (static_cast<float>(job_no) + batch_progress) / static_cast<float>(job_count)
: 0.0f;
if (overall_progress > 1.0f)
overall_progress = 1.0f;
r["progress"] = overall_progress;
state["job"] = job_type;
state["sampling_step"] = step;
state["sampling_steps"] = steps;
state["job_count"] = job_count;
state["job_no"] = job_no;
r["state"] = state;
if (!skip_current_image && !b64.empty()) {
r["current_image"] = b64;
} else {
r["current_image"] = nullptr;
}
}
res.set_content(r.dump(), "application/json");
});
}
+233 -5
View File
@@ -3,12 +3,33 @@
#include <algorithm>
#include <cmath>
#include <filesystem>
#include <limits>
#include "async_jobs.h"
#include "common/common.h"
#include "common/media_io.h"
#include "common/resource_owners.hpp"
namespace fs = std::filesystem;
static constexpr uint32_t k_max_upscale_dimension = 8192;
static bool valid_upscale_dimensions(const sd_image_t& image, int factor, int repeats) {
if (image.width == 0 || image.height == 0 || factor < 1 || repeats < 1 || repeats > 4) {
return false;
}
uint32_t width = image.width;
uint32_t height = image.height;
for (int i = 0; i < repeats; ++i) {
if (width > k_max_upscale_dimension / factor || height > k_max_upscale_dimension / factor) {
return false;
}
width *= factor;
height *= factor;
}
return true;
}
static bool parse_cache_mode(const std::string& mode_str, sd_cache_mode_t& mode_out) {
if (mode_str == "disabled") {
mode_out = SD_CACHE_DISABLED;
@@ -57,11 +78,11 @@ static json make_vae_tiling_json(const sd_tiling_params_t& params) {
return {
{"enabled", params.enabled},
{"temporal_tiling", params.temporal_tiling},
{"tile_size_x", params.tile_size_x},
{"tile_size_y", params.tile_size_y},
{"tile_size_w", params.tile_size_w},
{"tile_size_h", params.tile_size_h},
{"target_overlap", params.target_overlap},
{"rel_size_x", params.rel_size_x},
{"rel_size_y", params.rel_size_y},
{"rel_size_w", params.rel_size_w},
{"rel_size_h", params.rel_size_h},
{"extra_tiling_args", params.extra_tiling_args ? params.extra_tiling_args : ""},
};
}
@@ -127,7 +148,8 @@ static json make_img_gen_defaults_json(const SDGenerationParams& defaults, const
{"seed", defaults.seed},
{"batch_count", defaults.batch_count},
{"qwen_image_layers", defaults.qwen_image_layers},
{"auto_resize_ref_image", defaults.auto_resize_ref_image},
{"ref_image_args", defaults.ref_image_args},
{"image_preprocess", defaults.image_preprocess},
{"increase_ref_index", defaults.increase_ref_index},
{"control_strength", defaults.control_strength},
{"ip_adapter_strength", defaults.ip_adapter_strength},
@@ -153,6 +175,7 @@ static json make_vid_gen_defaults_json(const SDGenerationParams& defaults, const
{"strength", defaults.strength},
{"seed", defaults.seed},
{"video_frames", defaults.video_frames},
{"image_preprocess", defaults.image_preprocess},
{"fps", defaults.fps},
{"moe_boundary", defaults.moe_boundary},
{"vace_strength", defaults.vace_strength},
@@ -182,6 +205,7 @@ static json make_img_gen_features_json() {
{"cache", true},
{"cancel_queued", true},
{"cancel_generating", false},
{"preview", true},
};
}
@@ -196,9 +220,18 @@ static json make_vid_gen_features_json() {
{"cache", true},
{"cancel_queued", true},
{"cancel_generating", false},
{"preview", true},
};
}
static json make_preview_modes_json() {
json modes = json::array();
for (int i = 0; i < PREVIEW_COUNT; ++i) {
modes.push_back(sd_preview_name((preview_t)i));
}
return modes;
}
static json make_capabilities_json(ServerRuntime& runtime) {
refresh_lora_cache(runtime);
refresh_upscaler_cache(runtime);
@@ -239,37 +272,59 @@ static json make_capabilities_json(ServerRuntime& runtime) {
available_upscalers.push_back({
{"name", "None"},
{"model", false},
{"image_upscale", false},
});
available_upscalers.push_back({
{"name", "Lanczos"},
{"model", false},
{"image_upscale", false},
});
available_upscalers.push_back({
{"name", "Nearest"},
{"model", false},
{"image_upscale", false},
});
available_upscalers.push_back({
{"name", "Latent"},
{"model", false},
{"image_upscale", false},
});
available_upscalers.push_back({
{"name", "Latent (nearest)"},
{"model", false},
{"image_upscale", false},
});
available_upscalers.push_back({
{"name", "Latent (nearest-exact)"},
{"model", false},
{"image_upscale", false},
});
available_upscalers.push_back({
{"name", "Latent (antialiased)"},
{"model", false},
{"image_upscale", false},
});
available_upscalers.push_back({
{"name", "Latent (bicubic)"},
{"model", false},
{"image_upscale", false},
});
available_upscalers.push_back({
{"name", "Latent (bicubic antialiased)"},
{"model", false},
{"image_upscale", false},
});
bool have_upscaler_models = false;
{
std::lock_guard<std::mutex> lock(*runtime.upscaler_mutex);
for (const auto& entry : *runtime.upscaler_cache) {
available_upscalers.push_back({
{"name", entry.name},
{"model", true},
{"image_upscale", entry.image_upscale_factor > 0},
});
have_upscaler_models = have_upscaler_models || entry.image_upscale_factor > 0;
}
}
@@ -339,6 +394,8 @@ static json make_capabilities_json(ServerRuntime& runtime) {
{"max_height", 4096},
{"max_batch_count", 8},
{"max_queue_size", manager.max_pending_jobs},
{"max_upscale_width", k_max_upscale_dimension},
{"max_upscale_height", k_max_upscale_dimension},
};
result["samplers"] = samplers;
result["schedulers"] = schedulers;
@@ -348,6 +405,8 @@ static json make_capabilities_json(ServerRuntime& runtime) {
result["features_by_mode"] = features_by_mode;
result["loras"] = available_loras;
result["upscalers"] = available_upscalers;
result["upscale"] = have_upscaler_models;
result["preview_modes"] = make_preview_modes_json();
return result;
}
@@ -370,6 +429,8 @@ static bool parse_img_gen_request(const json& body,
if (!assign_output_options(request, output_format, output_compression, true, error_message)) {
return false;
}
request.preview_mode = body.value("preview", std::string("none"));
request.preview_interval = body.value("preview_interval", 1);
// Intentionally disable prompt-embedded LoRA tag parsing for server APIs.
if (!request.gen_params.resolve_and_validate(IMG_GEN, "", runtime.ctx_params->hires_upscalers_dir, true)) {
error_message = "invalid generation parameters";
@@ -397,6 +458,8 @@ static bool parse_vid_gen_request(const json& body,
if (!assign_output_options(request, output_format, output_compression, error_message)) {
return false;
}
request.preview_mode = body.value("preview", std::string("none"));
request.preview_interval = body.value("preview_interval", 1);
// Intentionally disable prompt-embedded LoRA tag parsing for server APIs.
if (!request.gen_params.resolve_and_validate(VID_GEN, "", runtime.ctx_params->hires_upscalers_dir, true)) {
error_message = "invalid generation parameters";
@@ -413,6 +476,171 @@ void register_sdcpp_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
res.set_content(make_capabilities_json(*runtime).dump(), "application/json");
});
svr.Post("/sdcpp/v1/upscale", [runtime](const httplib::Request& req, httplib::Response& res) {
try {
if (req.body.empty()) {
res.status = 400;
res.set_content(R"({"error":"empty body"})", "application/json");
return;
}
json body = json::parse(req.body);
if (!body.is_object()) {
res.status = 400;
res.set_content(R"({"error":"body must be an object"})", "application/json");
return;
}
for (const char* key : {"repeats", "tile_size", "output_compression"}) {
if (!body.contains(key)) {
continue;
}
const auto& value = body[key];
const bool valid = value.is_number_unsigned()
? value.get<uint64_t>() <= static_cast<uint64_t>(std::numeric_limits<int>::max())
: value.is_number_integer() && value.get<int64_t>() >= std::numeric_limits<int>::min() &&
value.get<int64_t>() <= std::numeric_limits<int>::max();
if (!valid) {
res.status = 400;
res.set_content(json({{"error", std::string(key) + " must be a 32-bit integer"}}).dump(), "application/json");
return;
}
}
ImgGenJobRequest output_options;
std::string error_message;
if (!assign_output_options(output_options,
body.value("output_format", std::string("png")),
body.value("output_compression", 100),
true,
error_message)) {
res.status = 400;
res.set_content(json({{"error", error_message}}).dump(), "application/json");
return;
}
const int tile_size = std::max(32, body.value("tile_size", runtime->default_gen_params->upscale_tile_size));
const int repeats = std::clamp(body.value("repeats", 1), 1, 4);
const std::string wanted = body.value("upscaler", std::string());
const std::string encoded = body.value("image", std::string());
if (encoded.empty()) {
res.status = 400;
res.set_content(R"({"error":"image is required"})", "application/json");
return;
}
SDImageOwner input;
if (!decode_base64_image(encoded, 3, 0, 0, input) || input.get().data == nullptr) {
res.status = 400;
res.set_content(R"({"error":"image could not be read"})", "application/json");
return;
}
refresh_upscaler_cache(*runtime);
int model_scale = 0;
std::string model_path;
std::string used_name;
{
std::lock_guard<std::mutex> lock(*runtime->upscaler_mutex);
for (const auto& entry : *runtime->upscaler_cache) {
if (entry.image_upscale_factor > 0 && (wanted.empty() || entry.name == wanted)) {
model_path = entry.fullpath;
used_name = entry.name;
model_scale = entry.image_upscale_factor;
break;
}
}
}
if (model_path.empty()) {
res.status = 400;
res.set_content(json({{"error", wanted.empty()
? std::string("no RGB ESRGAN upscaler models are available; "
"start the server with --hires-upscalers-dir")
: "no compatible image upscaler called " + wanted}})
.dump(),
"application/json");
return;
}
if (!valid_upscale_dimensions(input.get(), model_scale, repeats)) {
res.status = 400;
res.set_content(R"({"error":"upscaled dimensions must not exceed 8192 x 8192"})", "application/json");
return;
}
// One GPU: an upscale must not run while a generation is using it.
std::lock_guard<std::mutex> ctx_lock(*runtime->sd_ctx_mutex);
UpscalerCtxPtr upscaler_ctx(new_upscaler_ctx(model_path.c_str(),
runtime->ctx_params->diffusion_conv_direct,
runtime->ctx_params->n_threads,
tile_size,
runtime->ctx_params->backend.c_str(),
runtime->ctx_params->params_backend.c_str()));
if (upscaler_ctx == nullptr) {
res.status = 500;
res.set_content(R"({"error":"the upscaler model could not be loaded"})", "application/json");
return;
}
const int factor = get_upscale_factor(upscaler_ctx.get());
// The model file may have changed since its metadata was cached.
if (!valid_upscale_dimensions(input.get(), factor, repeats)) {
res.status = 400;
res.set_content(R"({"error":"upscaled dimensions must not exceed 8192 x 8192"})", "application/json");
return;
}
SDImageOwner current(input.release());
for (int i = 0; i < repeats; ++i) {
sd_image_t* out_images = nullptr;
int out_count = 0;
if (!upscale(upscaler_ctx.get(), current.get(), (uint32_t)factor, &out_images, &out_count) ||
out_count <= 0 || out_images[0].data == nullptr) {
free_sd_images(out_images, out_count);
res.status = 500;
res.set_content(R"({"error":"upscale failed"})", "application/json");
return;
}
sd_image_t produced = out_images[0];
out_images[0] = {0, 0, 0, nullptr};
free_sd_images(out_images, out_count);
current.reset(produced);
}
const std::string& format = output_options.output_format;
const int compression = output_options.output_compression;
const sd_image_t result = current.get();
auto image_bytes = encode_image_to_vector(format == "jpeg" ? EncodedImageFormat::JPEG
: format == "webp" ? EncodedImageFormat::WEBP
: EncodedImageFormat::PNG,
result.data,
result.width,
result.height,
result.channel,
"",
compression);
if (image_bytes.empty()) {
res.status = 500;
res.set_content(R"({"error":"the result could not be encoded"})", "application/json");
return;
}
json out;
out["upscaler"] = used_name;
out["scale"] = factor;
out["repeats"] = repeats;
out["width"] = result.width;
out["height"] = result.height;
out["output_format"] = format;
json images = json::array();
images.push_back({{"index", 0}, {"b64_json", base64_encode(image_bytes)}});
out["images"] = std::move(images);
res.set_content(out.dump(), "application/json");
res.status = 200;
} catch (const json::exception& e) {
res.status = 400;
res.set_content(json({{"error", "invalid request"}, {"message", e.what()}}).dump(), "application/json");
} catch (const std::exception& e) {
res.status = 500;
res.set_content(json({{"error", std::string("server_error: ") + e.what()}}).dump(), "application/json");
}
});
svr.Post("/sdcpp/v1/img_gen", [runtime](const httplib::Request& req, httplib::Response& res) {
try {
if (req.body.empty()) {
+108 -31
View File
@@ -254,28 +254,55 @@ std::string SDSvrParams::to_string() const {
void refresh_lora_cache(ServerRuntime& rt) {
std::vector<LoraEntry> new_cache;
if (rt.ctx_params->lora_model_dir.empty()) {
std::lock_guard<std::mutex> lock(*rt.lora_mutex);
rt.lora_cache->clear();
return;
}
fs::path lora_dir = rt.ctx_params->lora_model_dir;
if (fs::exists(lora_dir) && fs::is_directory(lora_dir)) {
for (auto& entry : fs::recursive_directory_iterator(lora_dir, fs::directory_options::skip_permission_denied)) {
if (!entry.is_regular_file()) {
continue;
}
const fs::path& p = entry.path();
if (!is_supported_model_ext(p)) {
continue;
}
std::error_code ec;
if (fs::exists(lora_dir, ec) && !ec && fs::is_directory(lora_dir, ec) && !ec) {
try {
auto it = fs::recursive_directory_iterator(
lora_dir,
fs::directory_options::skip_permission_denied,
ec);
auto end = fs::recursive_directory_iterator();
while (!ec && it != end) {
std::error_code entry_ec;
bool is_reg = it->is_regular_file(entry_ec);
if (entry_ec || !is_reg) {
it.increment(ec);
continue;
}
const fs::path& p = it->path();
if (!is_supported_model_ext(p)) {
it.increment(ec);
continue;
}
LoraEntry lora_entry;
lora_entry.name = p.stem().u8string();
lora_entry.fullpath = p.u8string();
std::string rel = p.lexically_relative(lora_dir).u8string();
std::replace(rel.begin(), rel.end(), '\\', '/');
lora_entry.path = rel;
LoraEntry lora_entry;
lora_entry.name = p.stem().u8string();
lora_entry.fullpath = p.u8string();
std::string rel = p.lexically_relative(lora_dir).u8string();
std::replace(rel.begin(), rel.end(), '\\', '/');
lora_entry.path = rel;
new_cache.push_back(std::move(lora_entry));
new_cache.push_back(std::move(lora_entry));
it.increment(ec);
}
} catch (const std::exception& e) {
LOG_WARN("error while scanning lora directory '%s': %s", lora_dir.string().c_str(), e.what());
return;
}
}
if (ec) {
LOG_WARN("error while scanning lora directory '%s': %s", lora_dir.string().c_str(), ec.message().c_str());
return;
}
std::sort(new_cache.begin(), new_cache.end(), [](const LoraEntry& a, const LoraEntry& b) {
return a.path < b.path;
});
@@ -295,28 +322,78 @@ std::string get_lora_full_path(ServerRuntime& rt, const std::string& path) {
void refresh_upscaler_cache(ServerRuntime& rt) {
std::vector<UpscalerEntry> new_cache;
std::vector<UpscalerEntry> previous_cache;
{
std::lock_guard<std::mutex> lock(*rt.upscaler_mutex);
previous_cache = *rt.upscaler_cache;
}
fs::path upscaler_dir = rt.ctx_params->hires_upscalers_dir;
if (fs::exists(upscaler_dir) && fs::is_directory(upscaler_dir)) {
for (auto& entry : fs::directory_iterator(upscaler_dir)) {
if (!entry.is_regular_file()) {
continue;
}
const fs::path& p = entry.path();
if (!is_supported_model_ext(p)) {
continue;
}
std::error_code ec;
if (fs::exists(upscaler_dir, ec) && !ec && fs::is_directory(upscaler_dir, ec) && !ec) {
try {
auto it = fs::directory_iterator(
upscaler_dir,
fs::directory_options::skip_permission_denied,
ec);
auto end = fs::directory_iterator();
while (!ec && it != end) {
std::error_code entry_ec;
bool is_reg = it->is_regular_file(entry_ec);
if (entry_ec || !is_reg) {
it.increment(ec);
continue;
}
const fs::path& p = it->path();
if (!is_supported_model_ext(p)) {
it.increment(ec);
continue;
}
UpscalerEntry upscaler_entry;
upscaler_entry.name = p.stem().u8string();
upscaler_entry.fullpath = fs::absolute(p).lexically_normal().u8string();
upscaler_entry.model_name = "ESRGAN_4x";
upscaler_entry.path = p.filename().u8string();
UpscalerEntry upscaler_entry;
upscaler_entry.name = p.stem().u8string();
std::error_code abs_ec;
fs::path abs_path = fs::absolute(p, abs_ec);
upscaler_entry.fullpath = (abs_ec ? p : abs_path.lexically_normal()).u8string();
upscaler_entry.model_name = "ESRGAN_4x";
upscaler_entry.path = p.filename().u8string();
new_cache.push_back(std::move(upscaler_entry));
std::error_code size_ec;
upscaler_entry.file_size = it->file_size(size_ec);
if (size_ec) {
it.increment(ec);
continue;
}
std::error_code time_ec;
upscaler_entry.last_modified = it->last_write_time(time_ec);
auto previous = std::find_if(previous_cache.begin(), previous_cache.end(), [&](const UpscalerEntry& cached) {
return cached.fullpath == upscaler_entry.fullpath &&
cached.file_size == upscaler_entry.file_size &&
(!time_ec && cached.last_modified == upscaler_entry.last_modified);
});
upscaler_entry.image_upscale_factor = previous != previous_cache.end()
? previous->image_upscale_factor
: get_upscaler_model_scale(upscaler_entry.fullpath.c_str());
if (upscaler_entry.image_upscale_factor > 0) {
upscaler_entry.scale = upscaler_entry.image_upscale_factor;
upscaler_entry.model_name = "ESRGAN_" + std::to_string(upscaler_entry.scale) + "x";
}
new_cache.push_back(std::move(upscaler_entry));
it.increment(ec);
}
} catch (const std::exception& e) {
LOG_WARN("error while scanning upscalers directory '%s': %s", upscaler_dir.string().c_str(), e.what());
return;
}
}
if (ec) {
LOG_WARN("error while scanning upscalers directory '%s': %s", upscaler_dir.string().c_str(), ec.message().c_str());
return;
}
std::sort(new_cache.begin(), new_cache.end(), [](const UpscalerEntry& a, const UpscalerEntry& b) {
return a.name < b.name;
});
+8
View File
@@ -2,6 +2,7 @@
#include <algorithm>
#include <cstdint>
#include <filesystem>
#include <mutex>
#include <string>
#include <vector>
@@ -43,6 +44,9 @@ struct UpscalerEntry {
std::string fullpath;
std::string model_name;
int scale = 4;
int image_upscale_factor = 0;
uintmax_t file_size = 0;
std::filesystem::file_time_type last_modified;
};
struct ServerRuntime {
@@ -62,6 +66,8 @@ struct ImgGenJobRequest {
SDGenerationParams gen_params;
std::string output_format = "png";
int output_compression = 100;
std::string preview_mode = "none";
int preview_interval = 1;
sd_img_gen_params_t to_sd_img_gen_params_t() {
return gen_params.to_sd_img_gen_params_t();
@@ -72,6 +78,8 @@ struct VidGenJobRequest {
SDGenerationParams gen_params;
std::string output_format = "webm";
int output_compression = 100;
std::string preview_mode = "none";
int preview_interval = 1;
sd_vid_gen_params_t to_sd_vid_gen_params_t() {
return gen_params.to_sd_vid_gen_params_t();
+1 -1
Submodule ggml updated: e20c3a14aa...d25b121ce1
+32 -5
View File
@@ -79,6 +79,7 @@ enum scheduler_t {
FLUX2_SCHEDULER,
FLUX_SCHEDULER,
BETA_SCHEDULER,
LLADA_IMAGE_SCHEDULER,
SCHEDULER_COUNT
};
@@ -172,11 +173,13 @@ enum lora_apply_mode_t {
typedef struct {
bool enabled;
bool temporal_tiling;
int tile_size_x;
int tile_size_y;
// Spatial tile dimensions in image pixels for both encode and decode; 0 uses 256.
int tile_size_w;
int tile_size_h;
float target_overlap;
float rel_size_x;
float rel_size_y;
// Positive values override tile_size: <= 1 is a dimension fraction, > 1 a target tile count.
float rel_size_w;
float rel_size_h;
const char* extra_tiling_args;
} sd_tiling_params_t;
@@ -242,9 +245,13 @@ typedef struct {
const char* rpc_servers;
const char* model_args;
bool disable_segmented_compute; // Force monolithic graph execution even when automatic graph cutting would fit memory better
bool batched_cfg; // Run the conditional and unconditional CFG branches in one batched UNet forward when supported
float linear_scale; // Override linear input scaling; 0 keeps the model default
float attn_scale; // Override flash-attention K/V scaling; 0 keeps the model default
const char* tokenizer; // tokenizer.json path or main=FILE,clip-l=FILE,clip-g=FILE assignments; required for PiD and Lens
bool sage_attn;
int conditioning_cache_size; // Maximum cached conditioning entries per context; 0 disables caching (default: 4)
const char* flash_attn_mode; // on/off/auto or module assignments (e.g. "auto,te=off"); NULL/empty honors legacy flags.
} sd_ctx_params_t;
typedef struct {
@@ -261,6 +268,11 @@ typedef struct {
uint8_t* data;
} sd_image_t;
typedef struct {
// Semicolon-separated target=...,key=value rules. NULL preserves defaults.
const char* rules;
} sd_image_preprocess_params_t;
typedef struct {
sd_image_t* frames;
int frame_count;
@@ -408,6 +420,7 @@ typedef struct {
int qwen_image_layers;
bool circular_x;
bool circular_y;
sd_image_preprocess_params_t image_preprocess;
} sd_img_gen_params_t;
typedef struct {
@@ -441,6 +454,7 @@ typedef struct {
sd_hires_params_t hires;
bool circular_x;
bool circular_y;
sd_image_preprocess_params_t image_preprocess;
} sd_vid_gen_params_t;
typedef struct sd_ctx_t sd_ctx_t;
@@ -449,6 +463,11 @@ struct ggml_tensor;
typedef void (*sd_log_cb_t)(enum sd_log_level_t level, const char* text, void* data);
typedef void (*sd_progress_cb_t)(int step, int steps, float time, void* data);
typedef void (*sd_preview_cb_t)(int step, int frame_count, sd_image_t* frames, bool is_noisy, void* data);
typedef struct {
int sample_pass;
int total_steps;
} sd_preview_info_t;
typedef bool (*sd_graph_eval_callback_t)(struct ggml_tensor* t, bool ask, void* user_data);
SD_API void sd_set_log_callback(sd_log_cb_t sd_log_cb, void* data);
@@ -457,6 +476,10 @@ SD_API void sd_set_progress_callback(sd_progress_cb_t cb, void* data);
// negative interval previews only completed logical step -interval. Zero previews the final
// completed step of the first sampling pass (base-resolution or high-noise).
SD_API void sd_set_preview_callback(sd_preview_cb_t cb, enum preview_t mode, int interval, bool denoised, bool noisy, void* data);
// Query from a preview callback. Passes are numbered from 1 since the last
// sd_set_preview_callback call; total_steps is the actual count in that pass.
// Both fields are zero before sampling starts.
SD_API sd_preview_info_t sd_get_preview_info();
SD_API void sd_set_backend_eval_callback(sd_graph_eval_callback_t cb, void* data);
SD_API int32_t sd_get_num_physical_cores();
SD_API const char* sd_get_system_info();
@@ -548,6 +571,8 @@ SD_API bool upscale(upscaler_ctx_t* upscaler_ctx,
int* num_images_out);
SD_API int get_upscale_factor(upscaler_ctx_t* upscaler_ctx);
// Reads model metadata only; returns 0 if the file is not a recognized RGB ESRGAN model.
SD_API int get_upscaler_model_scale(const char* model_path);
typedef struct adetailer_ctx_t adetailer_ctx_t;
@@ -587,7 +612,9 @@ SD_API bool convert_with_components(const char* model_path,
enum sd_type_t output_type,
const char* tensor_type_rules,
bool convert_name,
int n_threads);
int n_threads,
const sd_lora_t* loras,
int lora_count);
SD_API bool preprocess_canny(sd_image_t image,
float high_threshold,
+2
View File
@@ -8,6 +8,8 @@ $patterns = @(
"src/core/*.cpp"
"src/core/*.h"
"src/core/*.hpp"
"src/core/probe/*.cpp"
"src/core/probe/*.h"
"src/extensions/*.cpp"
"src/extensions/*.h"
"src/extensions/*.hpp"
+1
View File
@@ -8,6 +8,7 @@ cd "$repo_root"
for f in src/*.cpp src/*.h src/*.hpp \
src/conditioning/*.cpp src/conditioning/*.h src/conditioning/*.hpp \
src/core/*.cpp src/core/*.h src/core/*.hpp \
src/core/probe/*.cpp src/core/probe/*.h \
src/extensions/*.cpp src/extensions/*.h src/extensions/*.hpp \
src/pipeline/*.cpp src/pipeline/*.h \
src/runtime/*.cpp src/runtime/*.h src/runtime/*.hpp \
+438 -17
View File
@@ -14,7 +14,9 @@
#include "core/util.h"
#include "model/diffusion/model.hpp"
#include "model/te/clip.hpp"
#include "model/te/llada_image_te.hpp"
#include "model/te/llm.hpp"
#include "model/te/ming_image_te.hpp"
#include "model/te/t5.hpp"
#include "model_loader.h"
#include "tokenizers/sensenova_u1_tokenizer.h"
@@ -397,7 +399,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner {
ss << "['" << item.first << "', " << item.second << "], ";
}
ss << "]";
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
}
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
@@ -815,7 +817,7 @@ struct SD3CLIPEmbedder : public Conditioner {
ss << "['" << item.first << "', " << item.second << "], ";
}
ss << "]";
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
}
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
@@ -907,6 +909,31 @@ struct SD3CLIPEmbedder : public Conditioner {
size_t chunk_count = std::max(std::max(clip_l_tokens.size(), clip_g_tokens.size()), t5_tokens.size()) / chunk_len;
// Each active encoder must cover every chunk of the longest token sequence.
Tokenizer* tokenizers[] = {
clip_l ? clip_l_tokenizer.get() : nullptr,
clip_g ? clip_g_tokenizer.get() : nullptr,
t5 ? &t5_tokenizer : nullptr,
};
const size_t target_length = chunk_count * chunk_len;
for (size_t encoder_idx = 0; encoder_idx < 3; encoder_idx++) {
auto& tokens = token_and_weights[encoder_idx].first;
auto& weights = token_and_weights[encoder_idx].second;
if (!tokenizers[encoder_idx] || tokens.size() >= target_length) {
continue;
}
std::vector<int> empty_tokens;
std::vector<float> empty_weights;
tokenizers[encoder_idx]->pad_tokens(empty_tokens, &empty_weights, nullptr, chunk_len, chunk_len, false);
while (tokens.size() < target_length) {
tokens.insert(tokens.end(), empty_tokens.begin(), empty_tokens.end());
weights.insert(weights.end(), empty_weights.begin(), empty_weights.end());
}
}
for (int chunk_idx = 0; chunk_idx < chunk_count; chunk_idx++) {
// clip_l
sd::Tensor<float> chunk_hidden_states_l;
@@ -1205,7 +1232,7 @@ struct FluxCLIPEmbedder : public Conditioner {
ss << "['" << item.first << "', " << item.second << "], ";
}
ss << "]";
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
}
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
@@ -1482,7 +1509,7 @@ struct T5CLIPEmbedder : public Conditioner {
ss << "['" << item.first << "', " << item.second << "], ";
}
ss << "]";
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
}
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
@@ -1871,7 +1898,7 @@ struct AnimaConditioner : public Conditioner {
ss << "['" << item.first << "', " << item.second << "], ";
}
ss << "]";
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
}
std::vector<int> qwen_tokens;
@@ -1978,15 +2005,17 @@ struct LLMEmbedder : public Conditioner {
arch = LLM::LLMArch::GPT_OSS_20B;
} else if (sd_version_is_pid(version)) {
arch = LLM::LLMArch::GEMMA2_2B;
} else if (sd_version_is_lingbot_video(version) ||
} else if (version == VERSION_QWEN_IMAGE_2_1 ||
sd_version_is_lingbot_video(version) ||
sd_version_is_ideogram4(version) ||
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)) {
sd_version_is_mage_flow(version) ||
version == VERSION_IRIS) {
arch = LLM::LLMArch::QWEN3_VL;
} else if (sd_version_is_z_image(version) || version == VERSION_OVIS_IMAGE || version == VERSION_FLUX2_KLEIN) {
} else if (sd_version_is_z_image(version) || sd_version_is_z_image_l2p(version) || version == VERSION_OVIS_IMAGE || version == VERSION_FLUX2_KLEIN) {
arch = LLM::LLMArch::QWEN3;
}
llm = std::make_shared<LLM::LLMRunner>(arch,
@@ -2110,9 +2139,10 @@ struct LLMEmbedder : public Conditioner {
std::tuple<std::vector<int>, std::vector<float>, std::vector<float>> tokenize(std::string text,
const std::pair<int, int>& attn_range,
size_t min_length = 0,
size_t max_length = 100000000,
bool spell_quotes = false) {
size_t min_length = 0,
size_t max_length = 100000000,
bool spell_quotes = false,
const std::string& suffix = "") {
std::vector<std::pair<std::string, float>> parsed_attention;
if (attn_range.first >= 0 && attn_range.second > 0) {
if (attn_range.first > 0) {
@@ -2141,7 +2171,7 @@ struct LLMEmbedder : public Conditioner {
ss << "['" << item.first << "', " << item.second << "], ";
}
ss << "]";
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
}
std::vector<int> tokens;
@@ -2157,6 +2187,20 @@ struct LLMEmbedder : public Conditioner {
weights.insert(weights.end(), curr_tokens.size(), curr_weight);
}
if (!suffix.empty()) {
std::vector<int> suffix_tokens;
if (!tokenizer->encode(suffix, suffix_tokens) || suffix_tokens.size() >= max_length) {
return {};
}
// Reserve the assistant-turn marker before truncating the caption.
if (tokens.size() > max_length - suffix_tokens.size()) {
tokens.resize(max_length - suffix_tokens.size());
weights.resize(tokens.size());
}
tokens.insert(tokens.end(), suffix_tokens.begin(), suffix_tokens.end());
weights.insert(weights.end(), suffix_tokens.size(), 1.f);
}
std::vector<float> mask;
tokenizer->pad_tokens(tokens, &weights, &mask, min_length, max_length);
@@ -2179,8 +2223,10 @@ struct LLMEmbedder : public Conditioner {
bool spell_quotes = false,
int max_length = 100000000,
const LLM::DeepStackImageEmbeds& deepstack_image_embeds = {},
const std::vector<LLM::ImageGrid>& image_grids = {}) {
auto tokens_weights_mask = tokenize(prompt, prompt_attn_range, min_length, max_length, spell_quotes);
const std::vector<LLM::ImageGrid>& image_grids = {},
const std::string& prompt_suffix = "",
sd::Tensor<float>* output_mask = nullptr) {
auto tokens_weights_mask = tokenize(prompt, prompt_attn_range, min_length, max_length, spell_quotes, prompt_suffix);
auto& tokens = std::get<0>(tokens_weights_mask);
auto& weights = std::get<1>(tokens_weights_mask);
auto& mask = std::get<2>(tokens_weights_mask);
@@ -2217,7 +2263,10 @@ struct LLMEmbedder : public Conditioner {
false,
deepstack_image_embeds,
image_grids);
GGML_ASSERT(!hidden_states.empty());
if (hidden_states.empty()) {
LOG_ERROR("LLM prompt encoding failed");
return {};
}
hidden_states = apply_token_weights(std::move(hidden_states), weights);
GGML_ASSERT(hidden_states.shape()[1] > prompt_template_encode_start_idx);
@@ -2240,6 +2289,12 @@ struct LLMEmbedder : public Conditioner {
1);
}
if (output_mask != nullptr) {
*output_mask = sd::Tensor<float>::zeros({new_hidden_states.shape()[1]});
for (size_t i = prompt_template_encode_start_idx; i < mask.size(); ++i) {
output_mask->data()[i - prompt_template_encode_start_idx] = mask[i];
}
}
return new_hidden_states;
}
@@ -2306,6 +2361,8 @@ struct LLMEmbedder : public Conditioner {
int hidden_states_min_length = 0; // zero pad hidden_states
bool spell_quotes = false;
std::set<int> out_layers;
std::string prompt_suffix;
sd::Tensor<float> text_mask;
int64_t t0 = ggml_time_ms();
RefImageResizeMode resize_mode = conditioner_params.ref_image_params.vlm_resize_mode;
@@ -2547,6 +2604,67 @@ struct LLMEmbedder : public Conditioner {
prompt += conditioner_params.text;
prompt_attn_range = {0, 0};
prompt += "<|im_end|>\n<|im_start|>assistant\n";
} else if (version == VERSION_QWEN_IMAGE_2_1) {
if (!llm->enable_vision && conditioner_params.ref_images != nullptr && !conditioner_params.ref_images->empty()) {
LOG_ERROR("Qwen Image 2.1 editing requires Qwen3-VL vision weights; provide --llm_vision or a combined encoder");
return {};
}
prompt = "<|im_start|>system\nComprehend and analyze the provided prompt.<|im_end|>\n";
std::vector<int> system_tokens;
if (!tokenizer->encode(prompt, system_tokens, nullptr)) {
return {};
}
prompt_template_encode_start_idx = static_cast<int>(system_tokens.size());
out_layers = {static_cast<int>(llm->config.num_layers)};
prompt += "<|im_start|>user\n";
if (llm->enable_vision && conditioner_params.ref_images != nullptr) {
for (size_t i = 0; i < conditioner_params.ref_images->size(); ++i) {
const auto& image = (*conditioner_params.ref_images)[i];
int64_t width = image.shape()[0];
int64_t height = image.shape()[1];
int64_t pixels = width * height;
if (width % 32 != 0 || height % 32 != 0) {
LOG_ERROR("Qwen Image 2.1 reference dimensions must be multiples of 32");
return {};
}
auto rgb = sd::Tensor<float>({width, height, 3, 1});
for (int64_t p = 0; p < pixels; ++p) {
float alpha = image.shape()[2] == 4 ? image[p + 3 * pixels] : 1.f;
for (int c = 0; c < 3; ++c) {
rgb[p + c * pixels] = 2.f * (image[p + c * pixels] * alpha + 1.f - alpha) - 1.f;
}
}
auto outputs = llm->encode_image_outputs(n_threads, rgb, false);
if (outputs.empty()) {
return {};
}
prompt += (i == 0 ? "" : " ") + std::string("<image") + std::to_string(i + 1) + "><|vision_start|>";
std::vector<int> prefix_tokens;
if (!tokenizer->encode(prompt, prefix_tokens, nullptr)) {
return {};
}
int index = static_cast<int>(prefix_tokens.size());
int count = static_cast<int>(outputs[0].shape()[1]);
image_embeds.emplace_back(index, std::move(outputs[0]));
if (deepstack_image_embeds.empty()) {
deepstack_image_embeds.resize(outputs.size() - 1);
}
for (size_t layer = 1; layer < outputs.size(); ++layer) {
deepstack_image_embeds[layer - 1].emplace_back(index, std::move(outputs[layer]));
}
image_grids.push_back({index, count,
static_cast<int>(height) / llm->config.vision.patch_size,
static_cast<int>(width) / llm->config.vision.patch_size});
for (int j = 0; j < count; ++j) {
prompt += "<|image_pad|>";
}
prompt += "<|vision_end|>";
}
}
prompt_attn_range.first = static_cast<int>(prompt.size());
prompt += conditioner_params.text.empty() ? " " : conditioner_params.text;
prompt_attn_range.second = static_cast<int>(prompt.size());
prompt += "<|im_end|>\n<|im_start|>assistant\n";
} else if (sd_version_is_qwen_image(version) || sd_version_is_mage_flow(version)) {
if (llm->enable_vision && conditioner_params.ref_images != nullptr && !conditioner_params.ref_images->empty()) {
LOG_INFO("%s", sd_version_is_mage_flow(version) ? "MageFlowEditPipeline" : "QwenImageEditPlusPipeline");
@@ -2886,7 +3004,7 @@ struct LLMEmbedder : public Conditioner {
prompt_attn_range.second = static_cast<int>(prompt.size());
prompt += "<|end|><|start|>assistant<|channel|>analysis<|message|>Need to generate one image according to the description.<|end|><|start|>assistant<|channel|>final<|message|>";
} else if (sd_version_is_z_image(version)) {
} else if (sd_version_is_z_image(version) || sd_version_is_z_image_l2p(version)) {
prompt_template_encode_start_idx = 0;
out_layers = {35}; // -2
@@ -2994,6 +3112,23 @@ struct LLMEmbedder : public Conditioner {
SDCondition result;
result.c_crossattn = std::move(hidden_states);
return result;
} else if (version == VERSION_IRIS) {
prompt =
"<|im_start|>system\n"
"Describe the image by detailing the color, shape, size, texture, quantity, text, spatial "
"relationships of the objects and background:<|im_end|>\n<|im_start|>user\n";
auto prefix_tokens = std::get<0>(tokenize(prompt, {0, 0}));
if (prefix_tokens.empty()) {
return {};
}
prompt_template_encode_start_idx = static_cast<int>(prefix_tokens.size());
hidden_states_min_length = 300;
max_length = prompt_template_encode_start_idx + hidden_states_min_length;
out_layers = {2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35};
prompt_attn_range.first = static_cast<int>(prompt.size());
prompt += conditioner_params.text;
prompt_attn_range.second = static_cast<int>(prompt.size());
prompt_suffix = "<|im_end|>\n<|im_start|>assistant\n";
} else {
GGML_ABORT("unknown version %d", version);
}
@@ -3009,7 +3144,9 @@ struct LLMEmbedder : public Conditioner {
spell_quotes,
max_length,
deepstack_image_embeds,
image_grids);
image_grids,
prompt_suffix,
version == VERSION_IRIS ? &text_mask : nullptr);
if (hidden_states.empty()) {
return {};
}
@@ -3074,6 +3211,22 @@ struct LLMEmbedder : public Conditioner {
SDCondition result;
result.c_crossattn = std::move(hidden_states);
result.extra_c_crossattns = std::move(extra_hidden_states_vec);
result.c_vector = std::move(text_mask);
if (version == VERSION_QWEN_IMAGE_2_1) {
auto slots = sd::Tensor<int32_t>::zeros({result.c_crossattn.shape()[1]});
for (size_t i = 0; i < image_embeds.size(); ++i) {
int64_t begin = image_embeds[i].first - prompt_template_encode_start_idx;
int64_t end = begin + image_embeds[i].second.shape()[1];
if (begin < 0 || end > slots.numel()) {
LOG_ERROR("Qwen Image 2.1 image slots exceed the encoded prompt");
return {};
}
for (int64_t j = begin; j < end; ++j) {
slots[j] = static_cast<int32_t>(i + 1);
}
}
result.c_token_types = std::move(slots);
}
if (sd_version_is_minimax_h3(version)) {
std::vector<int32_t> tags(static_cast<size_t>(result.c_crossattn.shape()[1]), 1);
for (const auto& [index, image_embed] : image_embeds) {
@@ -3085,6 +3238,66 @@ struct LLMEmbedder : public Conditioner {
int64_t tag_count = static_cast<int64_t>(tags.size());
result.c_token_types = sd::Tensor<int32_t>({tag_count}, std::move(tags));
}
return result;
}
};
struct MingImageEmbedder : public Conditioner {
std::shared_ptr<Tokenizer> tokenizer;
std::shared_ptr<MingImageTE::MingImageTextRunner> text_model;
const std::string prefix = "text_encoders.llm";
MingImageEmbedder(ggml_backend_t backend, const String2TensorStorage& tensors, std::shared_ptr<RunnerWeightManager> weight_manager, const TokenizerConfig& tokenizers) {
if (!tokenizers.has(TokenizerConfig::MAIN)) {
throw std::runtime_error("Ming-Image requires the Ling tokenizer.json; pass --tokenizer FILE");
}
text_model = std::make_shared<MingImageTE::MingImageTextRunner>(backend, tensors, prefix, weight_manager);
tokenizer = tokenizers.create(TokenizerConfig::MAIN, text_model->config.backbone.vocab_size, 156895);
}
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
text_model->get_param_tensors(tensors, prefix);
}
void get_param_tensor_ops(std::map<ggml_tensor*, enum ggml_op>& ops) override {
text_model->get_param_tensor_ops(ops);
}
void get_layer_split_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
text_model->get_param_tensors(tensors, prefix);
}
void set_flash_attention_enabled(bool enabled) override { text_model->set_flash_attention_enabled(enabled); }
void set_max_graph_vram_bytes(size_t bytes) override { text_model->set_max_graph_vram_bytes(bytes); }
void set_runtime_backends(const std::vector<ggml_backend_t>& backends) override { text_model->set_runtime_backends(backends); }
void set_graph_cut_layer_split_enabled(bool enabled) override { text_model->set_graph_cut_layer_split_enabled(enabled); }
void set_graph_cut_layer_split_backend_vram_limits(const std::vector<size_t>& limits) override { text_model->set_graph_cut_layer_split_backend_vram_limits(limits); }
void set_scale_overrides(float linear, float attention) override { text_model->set_scale_overrides(linear, attention); }
void set_weight_adapter(const std::shared_ptr<WeightAdapter>& adapter) override { text_model->set_weight_adapter(adapter); }
void runner_end() override { text_model->runner_end(); }
SDCondition get_learned_condition(int n_threads, const ConditionerParams& input) override {
if (input.ref_images != nullptr && !input.ref_images->empty()) {
LOG_ERROR("Ming-Image currently supports text-to-image only");
return {};
}
std::string prompt =
"<role>SYSTEM</role>你是一个友好的AI助手。\n\ndetailed thinking off<|role_end|>"
"<role>HUMAN</role>" +
input.text + "<|role_end|><role>ASSISTANT</role>";
std::vector<int> tokens;
if (!tokenizer->encode(prompt, tokens, nullptr)) {
return {};
}
if (tokens.size() + 258 > 32768) {
LOG_ERROR("Ming-Image prompt exceeds the text encoder context length");
return {};
}
auto output = text_model->compute(n_threads, tokens);
if (output.empty()) {
return {};
}
SDCondition result;
result.c_crossattn = sd::ops::slice(sd::ops::slice(output, 1, 0, 256), 0, 0, text_model->config.caption_dim);
result.extra_c_crossattns.push_back(sd::ops::slice(output, 1, 256, output.shape()[1]));
return result;
}
};
@@ -3159,6 +3372,214 @@ struct LTXAVTextProjectionRunner : public GGMLRunner {
}
};
// LLaDA-Image's text path is a three-stage pipeline rather than a single encoder pass:
// the token embeddings feed a QueryFormer whose 256 queries are appended to the backbone
// input, and the backbone's final hidden states are projected to the denoiser's caption dim.
// Ref: LLaDAImagePipeline._encode_text.
struct LLaDAImageEmbedder : public Conditioner {
std::shared_ptr<Tokenizer> tokenizer;
std::shared_ptr<LLM::LLMRunner> llm;
std::shared_ptr<LLaDAImageTE::QueryFormerRunner> query_former;
std::shared_ptr<LLaDAImageTE::TextProjectionRunner> text_projection;
std::shared_ptr<LLaDAImageTE::SigVQRunner> sigvq;
std::string llm_prefix;
std::string query_former_prefix;
std::string text_projection_prefix;
std::string sigvq_prefix;
LLaDAImageEmbedder(ggml_backend_t backend,
const String2TensorStorage& tensor_storage_map = {},
const std::string& llm_prefix = "text_encoders.llm",
const std::string& query_former_prefix = "queryformer",
const std::string& text_projection_prefix = "text_projection",
const std::string& sigvq_prefix = "sigvq",
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr,
const TokenizerConfig& tokenizers = {})
: llm_prefix(llm_prefix),
query_former_prefix(query_former_prefix),
text_projection_prefix(text_projection_prefix),
sigvq_prefix(sigvq_prefix) {
if (!tokenizers.has(TokenizerConfig::MAIN)) {
throw std::runtime_error("LLaDA-Image requires an external LLaDA2 tokenizer.json; pass --tokenizer FILE or set sd_ctx_params_t::tokenizer");
}
llm = std::make_shared<LLM::LLMRunner>(LLM::LLMArch::LLADA2_MOE,
backend,
tensor_storage_map,
llm_prefix,
false,
weight_manager);
// <|endoftext|> doubles as the pad token in LLaDA2's tokenizer.json.
tokenizer = tokenizers.create(TokenizerConfig::MAIN, llm->config.vocab_size, 156892);
query_former = std::make_shared<LLaDAImageTE::QueryFormerRunner>(backend,
tensor_storage_map,
query_former_prefix,
weight_manager);
text_projection = std::make_shared<LLaDAImageTE::TextProjectionRunner>(backend,
tensor_storage_map,
text_projection_prefix,
weight_manager);
// SigVQ is only present when the user supplies the editing weights.
for (const auto& [name, _] : tensor_storage_map) {
if (starts_with(name, sigvq_prefix + ".")) {
sigvq = std::make_shared<LLaDAImageTE::SigVQRunner>(backend,
tensor_storage_map,
sigvq_prefix,
weight_manager);
break;
}
}
}
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
llm->get_param_tensors(tensors, llm_prefix);
query_former->get_param_tensors(tensors, query_former_prefix);
text_projection->get_param_tensors(tensors, text_projection_prefix);
if (sigvq != nullptr) {
sigvq->get_param_tensors(tensors, sigvq_prefix);
}
}
void get_param_tensor_ops(std::map<ggml_tensor*, enum ggml_op>& tensor_ops) override {
llm->get_param_tensor_ops(tensor_ops);
}
void set_flash_attention_enabled(bool enabled) override {
llm->set_flash_attention_enabled(enabled);
query_former->set_flash_attention_enabled(enabled);
text_projection->set_flash_attention_enabled(enabled);
if (sigvq != nullptr) {
sigvq->set_flash_attention_enabled(enabled);
}
}
void set_max_graph_vram_bytes(size_t max_vram_bytes) override {
llm->set_max_graph_vram_bytes(max_vram_bytes);
query_former->set_max_graph_vram_bytes(max_vram_bytes);
text_projection->set_max_graph_vram_bytes(max_vram_bytes);
if (sigvq != nullptr) {
sigvq->set_max_graph_vram_bytes(max_vram_bytes);
}
}
void set_runtime_backends(const std::vector<ggml_backend_t>& backends) override {
llm->set_runtime_backends(backends);
}
void set_graph_cut_layer_split_enabled(bool enabled) override {
llm->set_graph_cut_layer_split_enabled(enabled);
}
void set_graph_cut_layer_split_backend_vram_limits(const std::vector<size_t>& limits) override {
llm->set_graph_cut_layer_split_backend_vram_limits(limits);
}
void get_layer_split_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
llm->get_param_tensors(tensors, llm_prefix);
}
void set_weight_adapter(const std::shared_ptr<WeightAdapter>& adapter) override {
llm->set_weight_adapter(adapter);
query_former->set_weight_adapter(adapter);
text_projection->set_weight_adapter(adapter);
if (sigvq != nullptr) {
sigvq->set_weight_adapter(adapter);
}
}
void runner_end() override {
llm->runner_end();
query_former->runner_end();
text_projection->runner_end();
if (sigvq != nullptr) {
sigvq->runner_end();
}
}
SDCondition get_learned_condition(int n_threads,
const ConditionerParams& conditioner_params) override {
const int64_t num_queries = 256;
const bool has_ref_images = conditioner_params.ref_images != nullptr && !conditioner_params.ref_images->empty();
if (has_ref_images && sigvq == nullptr) {
LOG_ERROR("LLaDA-Image editing requires connectors with SigVQ weights");
return {};
}
std::string text = conditioner_params.text;
while (!text.empty() && std::isspace(static_cast<unsigned char>(text.front()))) {
text.erase(text.begin());
}
while (!text.empty() && std::isspace(static_cast<unsigned char>(text.back()))) {
text.pop_back();
}
std::string prompt = text.empty()
? "<role>HUMAN</role> Generate an image.\n<role>ASSISTANT</role>\n<IMAGE1>"
: "<role>HUMAN</role> Generate an image: " + text + "\n<role>ASSISTANT</role>\n<IMAGE1>";
std::vector<int> tokens;
if (!tokenizer->encode(prompt, tokens, nullptr)) {
return {};
}
int64_t n_text = static_cast<int64_t>(tokens.size());
GGML_ASSERT(n_text > 0);
sd::Tensor<int32_t> text_ids({n_text}, std::vector<int32_t>(tokens.begin(), tokens.end()));
auto inputs_embeds = llm->compute_input_embeds(n_threads, text_ids);
auto query_embeds = query_former->compute(n_threads, inputs_embeds);
// splice_image_embeds() replaces tokens in place, so the query slots have to exist in
// input_ids; their ids are irrelevant because the embeddings are overwritten.
std::vector<int32_t> padded(tokens.begin(), tokens.end());
padded.resize(static_cast<size_t>(n_text + num_queries), tokenizer->PAD_TOKEN_ID);
int64_t n_total = static_cast<int64_t>(padded.size());
sd::Tensor<int32_t> input_ids({n_total}, padded);
// Bidirectional everywhere except that the text tokens must not see the appended
// queries, matching backbone_attention_mask[:, :, :text_length, text_length:] = min.
const float mask_min = std::numeric_limits<float>::lowest() / 4.0f;
sd::Tensor<float> attention_mask({n_total, n_total});
for (int64_t i1 = 0; i1 < n_total; ++i1) {
for (int64_t i0 = 0; i0 < n_total; ++i0) {
float value = (i1 < n_text && i0 >= n_text) ? mask_min : 0.0f;
attention_mask[i0 + n_total * i1] = value;
}
}
LLM::ImageEmbeds image_embeds;
image_embeds.emplace_back(static_cast<int>(n_text), query_embeds);
std::set<int> out_layers = {static_cast<int>(llm->config.num_layers) + 1};
auto hidden_states = llm->compute(n_threads,
input_ids,
attention_mask,
image_embeds,
out_layers);
SDCondition result;
result.c_crossattn = text_projection->compute(n_threads, hidden_states);
// Editing: SigVQ sees the reference at half the output resolution, as in
// LLaDAImagePipeline._encode_source_image.
if (has_ref_images) {
const auto& ref = conditioner_params.ref_images->front();
auto resized = sd::ops::interpolate(ref,
{conditioner_params.width / 2,
conditioner_params.height / 2,
ref.shape()[2],
ref.shape()[3]},
sd::ops::InterpolateMode::Bilinear);
resized = resized * 2.f - 1.f;
auto semantic = sigvq->compute(n_threads, resized);
if (semantic.empty()) {
return {};
}
result.extra_c_crossattns.push_back(std::move(semantic));
}
return result;
}
};
struct LTXAVEmbedder : public Conditioner {
static constexpr int64_t kHiddenSize = 3840;
static constexpr int64_t kNumStates = 49;
+107
View File
@@ -0,0 +1,107 @@
#ifndef __SD_CONDITIONING_CONDITIONING_CACHE_H__
#define __SD_CONDITIONING_CONDITIONING_CACHE_H__
#include <algorithm>
#include <list>
#include <tuple>
#include "conditioning/conditioner.hpp"
class ConditioningCache {
struct Entry {
ConditionerParams params;
std::vector<sd::Tensor<float>> ref_images;
std::vector<MiniMaxH3PresentationItem> references;
SDCondition condition;
Entry(const ConditionerParams& input, const SDCondition& output)
: params(input), condition(output) {
// Request-owned reference pointers must not outlive the request.
if (input.ref_images != nullptr) {
ref_images = *input.ref_images;
params.ref_images = &ref_images;
}
if (input.minimax_h3_references != nullptr) {
references = *input.minimax_h3_references;
params.minimax_h3_references = &references;
}
}
Entry(const Entry&) = delete;
Entry& operator=(const Entry&) = delete;
};
size_t capacity_ = 4;
std::list<Entry> entries_;
static bool same_images(const std::vector<sd::Tensor<float>>& a,
const std::vector<sd::Tensor<float>>& b) {
return std::equal(a.begin(), a.end(), b.begin(), b.end(),
[](const sd::Tensor<float>& x, const sd::Tensor<float>& y) {
return x.shape() == y.shape() && x.values() == y.values();
});
}
static bool same_params(const ConditionerParams& a, const ConditionerParams& b) {
const auto fields = [](const ConditionerParams& p) {
const auto& r = p.ref_image_params;
return std::tie(p.text, p.clip_skip, p.width, p.height, p.zero_out_masked,
r.pass_to_vlm, r.pass_to_dit, r.ref_index_mode,
r.force_ref_timestep_zero, r.resize_before_vae, r.vae_input_max_pixels,
r.vlm_resize_mode, r.vlm_min_size, r.vlm_max_size, r.resize_vae_to_target);
};
if (fields(a) != fields(b) ||
(a.ref_images == nullptr) != (b.ref_images == nullptr) ||
(a.minimax_h3_references == nullptr) != (b.minimax_h3_references == nullptr)) {
return false;
}
if (a.ref_images != nullptr && !same_images(*a.ref_images, *b.ref_images)) {
return false;
}
if (a.minimax_h3_references != nullptr &&
!std::equal(a.minimax_h3_references->begin(), a.minimax_h3_references->end(),
b.minimax_h3_references->begin(), b.minimax_h3_references->end(),
[](const MiniMaxH3PresentationItem& x, const MiniMaxH3PresentationItem& y) {
return x.kind == y.kind && x.timestamps == y.timestamps && same_images(x.frames, y.frames);
})) {
return false;
}
return true;
}
public:
void set_capacity(size_t capacity) {
capacity_ = capacity;
while (entries_.size() > capacity_) {
entries_.pop_back();
}
}
void clear() {
entries_.clear();
}
SDCondition get(Conditioner& conditioner, int n_threads, const ConditionerParams& params) {
if (capacity_ == 0) {
return conditioner.get_learned_condition(n_threads, params);
}
for (auto it = entries_.begin(); it != entries_.end(); ++it) {
if (same_params(it->params, params)) {
entries_.splice(entries_.begin(), entries_, it);
LOG_INFO("conditioning cache hit");
return entries_.front().condition;
}
}
auto condition = conditioner.get_learned_condition(n_threads, params);
if (!condition.empty()) {
if (entries_.size() == capacity_) {
entries_.pop_back();
}
entries_.emplace_front(params, condition);
LOG_VERBOSE("conditioning cache stored (%zu/%zu)", entries_.size(), capacity_);
}
return condition;
}
};
#endif // __SD_CONDITIONING_CONDITIONING_CACHE_H__
+243 -11
View File
@@ -4,6 +4,7 @@
#include <cstring>
#include <exception>
#include <fstream>
#include <map>
#include <memory>
#include <mutex>
#include <regex>
@@ -11,11 +12,15 @@
#include <thread>
#include <vector>
#include "core/ggml_extend_backend.h"
#include "core/util.h"
#include "model_io/gguf_io.h"
#include "model_io/safetensors_io.h"
#include "model_io/streaming_writer.h"
#include "model_loader.h"
#include "model_manager.h"
#include "name_conversion.h"
#include "runtime/imatrix.h"
struct TensorExportInfo {
TensorStorage storage;
@@ -75,9 +80,9 @@ static size_t export_tensor_nbytes(const TensorExportInfo& info) {
return static_cast<size_t>(output_storage.nbytes());
}
static TensorWritePlan tensor_write_plan_from_export_info(const TensorExportInfo& info) {
static TensorWritePlan tensor_write_plan_from_export_info(const TensorExportInfo& info, const std::string& name) {
TensorWritePlan plan;
plan.name = info.storage.name;
plan.name = name;
plan.type = info.type;
plan.n_dims = info.storage.n_dims;
for (int i = 0; i < SD_MAX_DIMS; i++) {
@@ -86,11 +91,14 @@ static TensorWritePlan tensor_write_plan_from_export_info(const TensorExportInfo
return plan;
}
static std::vector<TensorWritePlan> tensor_write_plans_from_export_infos(const std::vector<TensorExportInfo>& tensors) {
static std::vector<TensorWritePlan> tensor_write_plans_from_export_infos(const std::vector<TensorExportInfo>& tensors,
bool convert_name,
SDVersion version) {
std::vector<TensorWritePlan> plans;
plans.reserve(tensors.size());
for (const TensorExportInfo& info : tensors) {
plans.push_back(tensor_write_plan_from_export_info(info));
const std::string name = convert_name ? convert_tensor_name(info.storage.name, version) : info.storage.name;
plans.push_back(tensor_write_plan_from_export_info(info, name));
}
return plans;
}
@@ -158,16 +166,42 @@ static bool load_tensor_for_export(ModelLoader& model_loader, TensorExportJob& j
return true;
}
static bool export_tensor_from_memory(const TensorExportInfo& info,
const std::map<std::string, ggml_tensor*>& mem,
SDVersion version,
std::vector<uint8_t>& out) {
auto found = mem.find(info.storage.name);
if (found == mem.end() || found->second == nullptr || found->second->data == nullptr) {
return false;
}
const TensorStorage& storage = info.storage;
out.resize(export_tensor_nbytes(info));
if (out.size() > 0) {
std::vector<float> imatrix = get_imatrix_collector().get_values(convert_tensor_name(info.storage.name, version));
convert_tensor(found->second->data,
found->second->type,
out.data(),
info.type,
(int)(storage.nelements() / storage.ne[0]),
(int)storage.ne[0],
std::move(imatrix));
}
return true;
}
static bool stream_tensor_data(ModelLoader& model_loader,
const std::string& output_path,
const std::vector<TensorExportInfo>& tensors,
const StreamingModelWriter& writer,
int n_threads,
const std::map<std::string, ggml_tensor*>* mem,
std::string* error) {
n_threads = n_threads > 0 ? n_threads : sd_get_num_physical_cores();
n_threads = std::max(1, n_threads);
LOG_INFO("streaming convert with %d threads", n_threads);
const SDVersion version = model_loader.get_sd_version();
int64_t start_time = ggml_time_ms();
uint64_t bytes_written = 0;
size_t tensors_written = 0;
@@ -241,7 +275,10 @@ static bool stream_tensor_data(ModelLoader& model_loader,
TensorExportJob job;
job.info = tensors[tensor_index];
try {
job.success = load_tensor_for_export(model_loader, job);
job.success = (mem != nullptr) && export_tensor_from_memory(job.info, *mem, version, job.data);
if (!job.success) {
job.success = load_tensor_for_export(model_loader, job);
}
} catch (const std::exception& e) {
job.error = e.what();
job.success = false;
@@ -298,8 +335,10 @@ static bool write_model_file_streaming(ModelLoader& model_loader,
const std::vector<TensorExportInfo>& tensors,
StreamingModelWriter& writer,
int n_threads,
bool convert_name,
const std::map<std::string, ggml_tensor*>* mem,
std::string* error) {
std::vector<TensorWritePlan> plans = tensor_write_plans_from_export_infos(tensors);
std::vector<TensorWritePlan> plans = tensor_write_plans_from_export_infos(tensors, convert_name, model_loader.get_sd_version());
if (!writer.write_metadata(output_path, plans, error)) {
return false;
}
@@ -307,7 +346,7 @@ static bool write_model_file_streaming(ModelLoader& model_loader,
return false;
}
model_loader.process_model_files(false, false);
return stream_tensor_data(model_loader, output_path, tensors, writer, n_threads, error);
return stream_tensor_data(model_loader, output_path, tensors, writer, n_threads, mem, error);
}
static bool init_convert_path(ModelLoader& model_loader, const char* path, const char* prefix, bool& loaded_any) {
@@ -326,7 +365,9 @@ static bool export_loaded_model(ModelLoader& model_loader,
const char* output_path,
sd_type_t output_type,
const char* tensor_type_rules,
int n_threads) {
int n_threads,
bool convert_name,
const std::map<std::string, ggml_tensor*>* mem = nullptr) {
ggml_type type = sd_type_to_ggml_type(output_type);
bool output_is_safetensors = ends_with(output_path, ".safetensors");
TensorTypeRules type_rules = parse_tensor_type_rules(tensor_type_rules);
@@ -341,7 +382,7 @@ static bool export_loaded_model(ModelLoader& model_loader,
} else {
writer = std::make_unique<GGUFStreamingWriter>();
}
success = write_model_file_streaming(model_loader, output_path, tensors, *writer, n_threads, &error);
success = write_model_file_streaming(model_loader, output_path, tensors, *writer, n_threads, convert_name, mem, &error);
}
if (!success && !error.empty()) {
@@ -351,6 +392,174 @@ static bool export_loaded_model(ModelLoader& model_loader,
return success;
}
static bool has_active_loras(const sd_lora_t* loras, int lora_count) {
if (loras == nullptr) {
return false;
}
for (int i = 0; i < lora_count; i++) {
if (loras[i].multiplier != 0.0f) {
return true;
}
}
return false;
}
static std::vector<ModelManager::LoraSpec> build_lora_specs(const sd_lora_t* loras, int lora_count) {
std::vector<ModelManager::LoraSpec> specs;
specs.reserve(lora_count);
for (int i = 0; i < lora_count; i++) {
ModelManager::LoraSpec spec;
spec.path = loras[i].path != nullptr ? loras[i].path : "";
spec.multiplier = loras[i].multiplier;
spec.is_high_noise = loras[i].is_high_noise;
spec.required = true;
specs.push_back(std::move(spec));
}
return specs;
}
static ModelComponent component_for_tensor_name(const std::string& name) {
if (starts_with(name, "text_encoders.") || starts_with(name, "cond_stage_model.")) {
return ModelComponent::Conditioner;
}
if (starts_with(name, "model.high_noise_diffusion_model.")) {
return ModelComponent::HighNoiseDiffusion;
}
if (starts_with(name, "model.diffusion_model.")) {
return ModelComponent::Diffusion;
}
if (starts_with(name, "vae.") || starts_with(name, "first_stage_model.")) {
return ModelComponent::VAE;
}
return ModelComponent::Diffusion;
}
static bool load_model_into_memory(ModelLoader& model_loader,
const sd_lora_t* loras,
int lora_count,
int n_threads,
ModelManager& manager,
ggml_backend_t cpu,
ggml_context* ctx,
std::map<std::string, ggml_tensor*>& mem,
std::vector<ggml_tensor*>& all_tensors,
ggml_type output_type,
const TensorTypeRules& type_rules) {
const String2TensorStorage& storage_map = model_loader.get_tensor_storage_map();
if (storage_map.empty()) {
LOG_ERROR("no tensors to load into memory for convert");
return false;
}
const SDVersion version = model_loader.get_sd_version();
ModelLoader merge_loader = model_loader;
merge_loader.convert_tensors_name();
std::map<ModelComponent, std::map<std::string, ggml_tensor*>> groups;
size_t total_bytes = 0;
for (const auto& [name, storage] : storage_map) {
ggml_type load_type = storage.type;
const ggml_type export_type = get_export_tensor_type(model_loader, storage, output_type, type_rules);
const bool quant_src = ggml_is_quantized(storage.type);
const bool quant_dst = ggml_is_quantized(export_type);
if (quant_src && quant_dst) {
load_type = GGML_TYPE_F16;
} else if (quant_src || quant_dst) {
load_type = quant_src ? export_type : storage.type;
} else if (storage.type == GGML_TYPE_F32 || export_type == GGML_TYPE_F32) {
load_type = GGML_TYPE_F32;
}
ggml_tensor* tensor = ggml_new_tensor(ctx, load_type, storage.n_dims, storage.ne);
if (tensor == nullptr) {
LOG_ERROR("failed to create tensor '%s' for in-memory convert", name.c_str());
return false;
}
const std::string merge_name = convert_tensor_name(name, version);
auto& group = groups[component_for_tensor_name(merge_name)];
if (!group.emplace(merge_name, tensor).second) {
LOG_ERROR("duplicate tensor name '%s' after name conversion", merge_name.c_str());
return false;
}
mem[name] = tensor;
all_tensors.push_back(tensor);
total_bytes += ggml_nbytes(tensor);
}
LOG_INFO("loading %zu tensors (%.2f GB) into memory for convert",
all_tensors.size(), total_bytes / (1024.0 * 1024.0 * 1024.0));
manager.set_n_threads(n_threads);
manager.set_enable_mmap(false);
if (!manager.set_loader(std::move(merge_loader))) {
LOG_ERROR("failed to set model loader for in-memory convert");
return false;
}
for (const auto& [component, tensors] : groups) {
if (!manager.register_param_tensors(component, tensors, ModelManager::ResidencyMode::ParamBackend, cpu, cpu)) {
LOG_ERROR("failed to register %s tensors for in-memory convert", model_component_name(component));
return false;
}
}
std::vector<ModelManager::LoraSpec> lora_specs = build_lora_specs(loras, lora_count);
if (!manager.prepare_lora_sources(lora_specs)) {
LOG_ERROR("failed to prepare LoRA sources for convert");
return false;
}
if (!manager.set_loras(lora_specs, version)) {
LOG_ERROR("failed to set LoRAs for convert");
return false;
}
if (!manager.prepare_params(all_tensors)) {
LOG_ERROR("failed to load model into memory for convert");
return false;
}
return true;
}
static bool convert_model_in_memory(ModelLoader& model_loader,
const char* output_path,
sd_type_t output_type,
const char* tensor_type_rules,
bool convert_name,
int n_threads,
const sd_lora_t* loras,
int lora_count) {
auto cpu = std::unique_ptr<ggml_backend, decltype(&ggml_backend_free)>(sd_backend_cpu_init(), ggml_backend_free);
if (cpu == nullptr) {
LOG_ERROR("failed to init CPU backend for in-memory convert");
return false;
}
ggml_init_params ctx_params;
ctx_params.mem_size = model_loader.get_tensor_storage_map().size() * ggml_tensor_overhead();
ctx_params.mem_buffer = nullptr;
ctx_params.no_alloc = true;
ggml_context* ctx = ggml_init(ctx_params);
if (ctx == nullptr) {
LOG_ERROR("ggml_init failed for in-memory convert");
return false;
}
ggml_type type = sd_type_to_ggml_type(output_type);
TensorTypeRules type_rules = parse_tensor_type_rules(tensor_type_rules);
std::vector<ggml_tensor*> all_tensors;
std::map<std::string, ggml_tensor*> mem;
bool success = false;
{
// The manager owns the buffers backing `mem` and must outlive the export; it is
// destroyed (freeing those buffers) before the tensor context is freed below.
ModelManager manager;
if (load_model_into_memory(model_loader, loras, lora_count, n_threads, manager, cpu.get(), ctx, mem, all_tensors, type, type_rules)) {
success = export_loaded_model(model_loader, output_path, output_type, tensor_type_rules, n_threads, convert_name, &mem);
}
}
ggml_free(ctx);
return success;
}
bool convert_with_components(const char* model_path,
const char* clip_l_path,
const char* clip_g_path,
@@ -361,7 +570,22 @@ bool convert_with_components(const char* model_path,
sd_type_t output_type,
const char* tensor_type_rules,
bool convert_name,
int n_threads) {
int n_threads,
const sd_lora_t* loras,
int lora_count) {
if (!validate_tensor_types(output_type, tensor_type_rules)) {
return false;
}
if (loras != nullptr) {
for (int i = 0; i < lora_count; i++) {
LOG_INFO("lora %d: '%s' (multiplier %.2f%s)",
i + 1, loras[i].path != nullptr ? loras[i].path : "",
loras[i].multiplier,
loras[i].is_high_noise ? ", high noise" : "");
}
}
ModelLoader model_loader;
bool loaded_any = false;
@@ -379,11 +603,17 @@ bool convert_with_components(const char* model_path,
return false;
}
if (has_active_loras(loras, lora_count)) {
LOG_INFO("loading the full model into memory to apply LoRAs");
return convert_model_in_memory(model_loader, output_path, output_type, tensor_type_rules, convert_name, n_threads,
loras, lora_count);
}
if (convert_name) {
model_loader.convert_tensors_name();
}
return export_loaded_model(model_loader, output_path, output_type, tensor_type_rules, n_threads);
return export_loaded_model(model_loader, output_path, output_type, tensor_type_rules, n_threads, false, nullptr);
}
bool convert(const char* input_path,
@@ -402,5 +632,7 @@ bool convert(const char* input_path,
output_type,
tensor_type_rules,
convert_name,
0,
nullptr,
0);
}
+31 -14
View File
@@ -478,27 +478,44 @@ namespace sd::backend_fit {
return true;
}
bool prepare_vae_decode_retry_tiling(sd_tiling_params_t& tiling_params, bool prefer_temporal_tiling) {
bool prepare_vae_decode_retry_tiling(sd_tiling_params_t& tiling_params,
bool prefer_temporal_tiling,
ggml_status status,
int latent_tile_size_w,
int latent_tile_size_h,
int scale_factor) {
// Execution failures can leave the device unusable; tiling only helps with allocation failures.
if (status != GGML_STATUS_ALLOC_FAILED) {
return false;
}
const char* retry_mode = nullptr;
if (prefer_temporal_tiling && !tiling_params.temporal_tiling) {
tiling_params.temporal_tiling = true;
retry_mode = tiling_params.enabled ? "spatial+temporal" : "temporal";
} else if (!tiling_params.enabled) {
tiling_params.enabled = true;
tiling_params.rel_size_x = 0.5f;
tiling_params.rel_size_y = 0.5f;
if (tiling_params.tile_size_x <= 0) {
tiling_params.tile_size_x = 256;
}
if (tiling_params.tile_size_y <= 0) {
tiling_params.tile_size_y = 256;
}
retry_mode = tiling_params.temporal_tiling ? "spatial+temporal" : "spatial";
} else {
return false;
if (latent_tile_size_w <= 0 || latent_tile_size_h <= 0 || scale_factor <= 0) {
return false;
}
auto smaller_tile = [&](int size) {
return std::min(size, std::max(4, size / 2));
};
const int tile_size_w = smaller_tile(latent_tile_size_w);
const int tile_size_h = smaller_tile(latent_tile_size_h);
if (tile_size_w == latent_tile_size_w && tile_size_h == latent_tile_size_h) {
return false;
}
tiling_params.enabled = true;
tiling_params.rel_size_w = 0.0f;
tiling_params.rel_size_h = 0.0f;
tiling_params.tile_size_w = tile_size_w * scale_factor;
tiling_params.tile_size_h = tile_size_h * scale_factor;
retry_mode = tiling_params.temporal_tiling ? "spatial+temporal" : "spatial";
LOG_WARN("Reducing VAE decode tiles from %dx%d to %dx%d image pixels",
latent_tile_size_w * scale_factor, latent_tile_size_h * scale_factor,
tiling_params.tile_size_w, tiling_params.tile_size_h);
}
LOG_WARN("VAE decode failed (likely out of memory); retrying with %s tiling",
LOG_WARN("VAE decode ran out of memory; retrying with %s tiling",
retry_mode);
return true;
}
+5 -1
View File
@@ -16,7 +16,11 @@ namespace sd::backend_fit {
std::string& params_spec);
bool prepare_vae_decode_retry_tiling(sd_tiling_params_t& tiling_params,
bool prefer_temporal_tiling);
bool prefer_temporal_tiling,
ggml_status status,
int latent_tile_size_w,
int latent_tile_size_h,
int scale_factor);
} // namespace sd::backend_fit
+1 -1
View File
@@ -11,7 +11,7 @@
#include "core/ggml_graph_cut.h"
#include "core/util.h"
#include "ggml-cpu.h"
#include "ggml/src/ggml-impl.h"
#include "ggml-impl.h"
namespace sd {
ComputeWorkspace::~ComputeWorkspace() {
+93 -11
View File
@@ -1,6 +1,7 @@
#include "core/ggml_extend.h"
#include <cmath>
#include <stdexcept>
#include <utility>
#include "core/ggml_extend_backend.h"
@@ -222,13 +223,13 @@ ggml_tensor* ggml_ext_linear(ggml_context* ctx,
x = ggml_reshape_2d(ctx, x, x->ne[0], x->ne[1] * x->ne[2] * x->ne[3]);
x = ggml_mul_mat(ctx, w, x);
if (force_prec_f32) {
ggml_mul_mat_set_prec(x, GGML_PREC_F32);
ggml_prec_set_acc(x, GGML_PREC_F32);
}
x = ggml_reshape_4d(ctx, x, x->ne[0], x->ne[1] / ne2 / ne3, ne2, ne3);
} else {
x = ggml_mul_mat(ctx, w, x);
if (force_prec_f32) {
ggml_mul_mat_set_prec(x, GGML_PREC_F32);
ggml_prec_set_acc(x, GGML_PREC_F32);
}
}
if (scale != 1.f) {
@@ -247,20 +248,27 @@ ggml_tensor* ggml_ext_linear_i8_tensorwise(ggml_context* ctx,
ggml_tensor* b,
int convrot_group_size,
float scale) {
#ifndef SD_USE_UPSTREAM_GGML
GGML_ASSERT(x->type == GGML_TYPE_F32 || (x->type == GGML_TYPE_I8 && scale == 1.f));
if (scale != 1.f) {
x = ggml_ext_scale(ctx, x, scale);
}
ggml_tensor* fused_bias = scale == 1.f ? b : nullptr;
auto mul_mat = [&](ggml_tensor* input) {
if (input->type == GGML_TYPE_F32 && convrot_group_size > 0) {
input = ggml_quantize_i8_convrot(ctx, input, convrot_group_size);
}
return ggml_mul_mat_i8_tensorwise(ctx, w, input, weight_scale, fused_bias, convrot_group_size);
};
if (x->ne[2] * x->ne[3] > 1024) {
int64_t ne2 = x->ne[2];
int64_t ne3 = x->ne[3];
x = ggml_reshape_2d(ctx, x, x->ne[0], x->ne[1] * x->ne[2] * x->ne[3]);
x = ggml_mul_mat_i8_tensorwise(ctx, w, x, weight_scale, fused_bias, convrot_group_size);
x = mul_mat(x);
x = ggml_reshape_4d(ctx, x, x->ne[0], x->ne[1] / ne2 / ne3, ne2, ne3);
} else {
x = ggml_mul_mat_i8_tensorwise(ctx, w, x, weight_scale, fused_bias, convrot_group_size);
x = mul_mat(x);
}
if (scale != 1.f) {
@@ -270,6 +278,16 @@ ggml_tensor* ggml_ext_linear_i8_tensorwise(ggml_context* ctx,
}
}
return x;
#else
GGML_UNUSED(ctx);
GGML_UNUSED(x);
GGML_UNUSED(w);
GGML_UNUSED(weight_scale);
GGML_UNUSED(b);
GGML_UNUSED(convrot_group_size);
GGML_UNUSED(scale);
throw std::runtime_error("INT8 tensorwise/convrot is not supported by this ggml build");
#endif
}
ggml_tensor* ggml_ext_pad_ext(ggml_context* ctx,
@@ -452,8 +470,17 @@ ggml_tensor* ggml_ext_conv_3d(ggml_context* ctx,
int d0,
int d1,
int d2,
bool force_prec_f32) {
if (force_prec_f32) {
bool force_prec_f32,
bool direct,
float scale) {
if (scale != 1.f) {
x = ggml_ext_scale(ctx, x, scale);
}
if (direct) {
int64_t OC = w->ne[3] / IC;
int64_t N = x->ne[3] / IC;
x = ggml_conv_3d_direct(ctx, w, x, s0, s1, s2, p0, p1, p2, d0, d1, d2, (int)IC, (int)N, (int)OC);
} else if (force_prec_f32) {
ggml_tensor* im2col = ggml_im2col_3d(ctx, w, x, IC, s0, s1, s2, p0, p1, p2, d0, d1, d2, w->type);
int64_t OC = w->ne[3] / IC;
@@ -461,7 +488,7 @@ ggml_tensor* ggml_ext_conv_3d(ggml_context* ctx,
x = ggml_mul_mat(ctx,
ggml_reshape_2d(ctx, im2col, im2col->ne[0], im2col->ne[3] * im2col->ne[2] * im2col->ne[1]),
ggml_reshape_2d(ctx, w, w->ne[0] * w->ne[1] * w->ne[2] * IC, OC));
ggml_mul_mat_set_prec(x, GGML_PREC_F32);
ggml_prec_set_acc(x, GGML_PREC_F32);
int64_t OD = im2col->ne[3] / N;
x = ggml_reshape_4d(ctx, x, im2col->ne[1] * im2col->ne[2], OD, N, OC);
@@ -485,6 +512,9 @@ ggml_tensor* ggml_ext_conv_3d(ggml_context* ctx,
}
}
if (scale != 1.f) {
x = ggml_ext_scale(ctx, x, 1.f / scale);
}
if (b != nullptr) {
b = ggml_reshape_4d(ctx, b, 1, 1, 1, b->ne[0]); // [OC, 1, 1, 1]
x = ggml_add_inplace(ctx, x, b);
@@ -605,7 +635,12 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
ggml_tensor* mask,
bool skip_reshape,
bool flash_attn,
float kv_scale) { // avoid overflow
float kv_scale,
bool sage_attn,
bool* used_flash_attn) { // avoid overflow
if (used_flash_attn != nullptr) {
*used_flash_attn = false;
}
int64_t L_q;
int64_t L_k;
int64_t C;
@@ -643,6 +678,14 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
ggml_tensor* kqv = nullptr;
auto build_kqv = [&](ggml_tensor* q_in, ggml_tensor* k_in, ggml_tensor* v_in, ggml_tensor* mask_in) -> ggml_tensor* {
const bool pad_head = d_head > 0 && d_head < 64 && q_in->ne[0] == d_head && k_in->ne[0] == d_head &&
q_in->type == GGML_TYPE_F32 && k_in->type == GGML_TYPE_F32 &&
v_in->type == GGML_TYPE_F32 && sd_backend_supports_cuda_mma(backend);
if (pad_head) {
// CUDA FA MMA starts at 64 channels; keep the original head's attention scale.
q_in = ggml_pad(ctx, q_in, static_cast<int>(64 - d_head), 0, 0, 0);
k_in = ggml_pad(ctx, k_in, static_cast<int>(64 - d_head), 0, 0, 0);
}
if (kv_scale != 1.0f) {
k_in = ggml_ext_scale(ctx, k_in, kv_scale);
}
@@ -650,6 +693,9 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
v_in = ggml_ext_cont(ctx, ggml_permute(ctx, v_in, 0, 2, 1, 3));
v_in = ggml_reshape_3d(ctx, v_in, d_head, L_k, n_kv_head * N);
if (pad_head) {
v_in = ggml_pad(ctx, v_in, static_cast<int>(64 - d_head), 0, 0, 0);
}
if (kv_scale != 1.0f) {
v_in = ggml_ext_scale(ctx, v_in, kv_scale);
}
@@ -675,14 +721,47 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
if (!ggml_backend_supports_op(backend, out)) {
return nullptr;
}
ggml_flash_attn_ext_set_prec(out, GGML_PREC_F32);
ggml_prec_set_acc(out, GGML_PREC_F32);
if (kv_scale != 1.0f) {
out = ggml_ext_scale(ctx, out, 1.0f / kv_scale);
}
if (pad_head) {
out = ggml_ext_slice(ctx, out, 0, 0, d_head);
}
return out;
};
if (flash_attn) {
#ifndef SD_USE_UPSTREAM_GGML
if (sage_attn && mask == nullptr && d_head > 0 && d_head <= 128) {
auto q_in = ggml_reshape_4d(ctx, ggml_ext_cont(ctx, q->type == GGML_TYPE_F32 ? q : ggml_cast(ctx, q, GGML_TYPE_F32)), d_head, L_q, n_head, N);
auto k_in = ggml_reshape_4d(ctx, ggml_ext_cont(ctx, k->type == GGML_TYPE_F32 ? k : ggml_cast(ctx, k, GGML_TYPE_F32)), d_head, L_k, n_kv_head, N);
auto v_in = ggml_ext_cont(ctx, ggml_permute(ctx, v, 0, 2, 1, 3));
const int64_t padded_head = d_head <= 64 ? 64 : 128;
if ((padded_head != d_head || kv_scale != 1.0f) && v_in->type != GGML_TYPE_F32) {
v_in = ggml_cast(ctx, v_in, GGML_TYPE_F32);
}
if (padded_head != d_head) {
// Keep the original head's softmax scale when padding for the CUDA kernel.
q_in = ggml_pad(ctx, q_in, static_cast<int>(padded_head - d_head), 0, 0, 0);
k_in = ggml_pad(ctx, k_in, static_cast<int>(padded_head - d_head), 0, 0, 0);
v_in = ggml_pad(ctx, v_in, static_cast<int>(padded_head - d_head), 0, 0, 0);
}
if (kv_scale != 1.0f) {
k_in = ggml_ext_scale(ctx, k_in, kv_scale);
v_in = ggml_ext_scale(ctx, v_in, kv_scale);
}
v_in = ggml_cast(ctx, v_in, GGML_TYPE_F16);
auto out = ggml_sage_attn(ctx, q_in, k_in, v_in, scale / kv_scale, GGML_SAGE_ATTN_AUTO);
if (ggml_backend_supports_op(backend, out)) {
kqv = kv_scale != 1.0f ? ggml_ext_scale(ctx, out, 1.0f / kv_scale) : out;
if (padded_head != d_head) {
kqv = ggml_ext_slice(ctx, kqv, 0, 0, d_head);
}
}
}
#endif
if (kqv == nullptr && flash_attn) {
// LOG_VERBOSE("attention_ext L_q:%d L_k:%d n_head:%d C:%d d_head:%d N:%d", L_q, L_k, n_head, C, d_head, N);
bool can_use_flash_attn = true;
if (mask != nullptr) {
@@ -693,6 +772,9 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
if (can_use_flash_attn) {
kqv = build_kqv(q, k, v, mask);
if (kqv != nullptr) {
if (used_flash_attn != nullptr) {
*used_flash_attn = true;
}
kqv = ggml_view_4d(ctx,
kqv,
d_head,
@@ -715,7 +797,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
v = ggml_reshape_3d(ctx, v, L_k, d_head, n_kv_head * N); // [N * n_kv_head, d_head, L_k]
auto kq = ggml_mul_mat(ctx, k, q); // [N * n_head, L_q, L_k]
ggml_mul_mat_set_prec(kq, GGML_PREC_F32);
ggml_prec_set_acc(kq, GGML_PREC_F32);
kq = ggml_scale_inplace(ctx, kq, scale);
if (mask) {
kq = ggml_add_inplace(ctx, kq, mask);
+9 -5
View File
@@ -153,7 +153,9 @@ ggml_tensor* ggml_ext_conv_3d(ggml_context* ctx,
int d0 = 1,
int d1 = 1,
int d2 = 1,
bool force_prec_f32 = false);
bool force_prec_f32 = false,
bool direct = false,
float scale = 1.f);
// w: [OC,IC, KD, 1 * 1]
// x: [N, IC, ID, IH*IW]
@@ -216,10 +218,12 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
ggml_tensor* k,
ggml_tensor* v,
int64_t n_head,
ggml_tensor* mask = nullptr,
bool skip_reshape = false,
bool flash_attn = false,
float kv_scale = 1.0f);
ggml_tensor* mask = nullptr,
bool skip_reshape = false,
bool flash_attn = false,
float kv_scale = 1.0f,
bool sage_attn = false,
bool* used_flash_attn = nullptr);
ggml_tensor* ggml_ext_layer_norm(ggml_context* ctx,
ggml_tensor* x,
+189 -1
View File
@@ -6,10 +6,20 @@
#include <mutex>
#include <sstream>
#include <stdexcept>
#include <utility>
#include <vector>
#ifdef SD_USE_CUDA
#include <cuda.h>
#endif
#ifdef SD_USE_HIP
#include <hip/hip_runtime_api.h>
#endif
#include "core/util.h"
#include "ggml/src/ggml-impl.h"
#include "ggml-backend-impl.h"
#include "ggml-impl.h"
#include "stable-diffusion.h"
static std::string trim_copy(const std::string& value) {
@@ -429,6 +439,131 @@ bool sd_backend_is_cpu(ggml_backend_t backend) {
return dev != nullptr && ggml_backend_dev_type(dev) == GGML_BACKEND_DEVICE_TYPE_CPU;
}
ggml_backend_buffer_t sd_backend_dev_buffer_from_host_ptr(ggml_backend_dev_t device,
void* ptr,
size_t size,
size_t max_tensor_size) {
ggml_backend_buffer_t buffer = ggml_backend_dev_buffer_from_host_ptr(device, ptr, size, max_tensor_size);
if (buffer != nullptr && buffer->context == nullptr) {
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(device);
if (reg != nullptr && std::strcmp(ggml_backend_reg_name(reg), "Metal") == 0) {
// Metal can wrap a failed mapping in a non-null buffer. Its free callback also
// dereferences the missing context, so only release the outer buffer.
buffer->iface.free_buffer = nullptr;
ggml_backend_buffer_free(buffer);
return nullptr;
}
}
return buffer;
}
bool sd_backend_supports_cuda_mma(ggml_backend_t backend) {
#ifdef SD_USE_CUDA
if (!sd_backend_is(backend, "CUDA")) {
return false;
}
auto dev = ggml_backend_get_device(backend);
if (dev == nullptr) {
return false;
}
static std::mutex mutex;
static std::unordered_map<ggml_backend_dev_t, bool> cache;
std::lock_guard<std::mutex> lock(mutex);
auto it = cache.find(dev);
if (it != cache.end()) {
return it->second;
}
const bool supported = [&]() {
ggml_backend_dev_props props{};
ggml_backend_dev_get_props(dev, &props);
CUdevice device;
int major = 0, minor = 0;
if (props.device_id == nullptr || cuInit(0) != CUDA_SUCCESS ||
cuDeviceGetByPCIBusId(&device, props.device_id) != CUDA_SUCCESS ||
cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, device) != CUDA_SUCCESS ||
cuDeviceGetAttribute(&minor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, device) != CUDA_SUCCESS) {
return false;
}
auto reg = ggml_backend_dev_backend_reg(dev);
auto get_features = reinterpret_cast<ggml_backend_get_features_t>(
ggml_backend_reg_get_proc_address(reg, "ggml_backend_get_features"));
if (get_features == nullptr) {
return false;
}
// Match ggml's highest compiled architecture for this device, including PTX fallback.
const int cc = 100 * major + 10 * minor;
int compiled_arch = 0;
for (auto feature = get_features(reg); feature != nullptr && feature->name != nullptr; ++feature) {
if (std::strcmp(feature->name, "ARCHS") != 0 || feature->value == nullptr) {
continue;
}
const char* arch = feature->value;
while (*arch != '\0') {
char* end = nullptr;
const long value = std::strtol(arch, &end, 10);
if (end == arch) {
++arch;
continue;
}
if (value <= cc && value > compiled_arch) {
compiled_arch = static_cast<int>(value);
}
arch = end;
}
}
return compiled_arch == 700 || compiled_arch >= 750;
}();
cache.emplace(dev, supported);
return supported;
#else
(void)backend;
return false;
#endif
}
bool sd_backend_supports_hip_mma(ggml_backend_t backend) {
#ifdef SD_USE_HIP
if (!sd_backend_is(backend, "ROCm")) {
return false;
}
auto dev = ggml_backend_get_device(backend);
if (dev == nullptr) {
return false;
}
ggml_backend_dev_props props{};
ggml_backend_dev_get_props(dev, &props);
int device = 0;
hipDeviceProp_t hip_props{};
if (props.device_id == nullptr ||
hipDeviceGetByPCIBusId(&device, props.device_id) != hipSuccess ||
hipGetDeviceProperties(&hip_props, device) != hipSuccess) {
return false;
}
const char* arch = hip_props.gcnArchName;
if (std::strncmp(arch, "gfx", 3) != 0) {
return false;
}
char* end = nullptr;
const long value = std::strtol(arch + 3, &end, 16);
if (end == arch + 3 || (*end != '\0' && *end != ':')) {
return false;
}
// Match the HIP FA kernels; keep unknown architecture families on the probe path.
if (value >= 0x1100 && value < 0x1300) {
return true;
}
#ifdef SD_HIP_MFMA
return value == 0x908 || value == 0x90a ||
value == 0x940 || value == 0x941 || value == 0x942 || value == 0x950;
#else
return false;
#endif
#else
(void)backend;
return false;
#endif
}
ggml_backend_t sd_backend_cpu_init() {
ggml_backend_load_all_once();
return ggml_backend_init_by_type(GGML_BACKEND_DEVICE_TYPE_CPU, nullptr);
@@ -647,6 +782,59 @@ bool sd_parse_backend_assignment(const std::string& spec, SDBackendAssignment* a
return true;
}
bool sd_parse_flash_attention_assignment(const std::string& spec, SDBackendAssignment* assignment, std::string* error) {
if (assignment == nullptr) {
return false;
}
SDBackendAssignment result;
result.set_default("auto");
std::istringstream stream(spec);
std::string part;
while (std::getline(stream, part, ',')) {
SDBackendAssignment entry;
if (!sd_parse_backend_assignment(part, &entry, error)) {
return false;
}
auto valid_mode = [&](const std::string& mode) {
if (mode == "on" || mode == "off" || mode == "auto") {
return true;
}
if (error != nullptr) {
*error = "invalid flash attention mode '" + mode + "': expected on, off, or auto";
}
return false;
};
if (!entry.default_name.empty()) {
if (!valid_mode(entry.default_name)) {
return false;
}
result.set_default(entry.default_name);
}
for (const auto& setting : entry.module_names) {
switch (setting.first) {
case SDBackendModule::DIFFUSION:
case SDBackendModule::TE:
case SDBackendModule::CLIP_VISION:
case SDBackendModule::VAE:
case SDBackendModule::CONTROL_NET:
case SDBackendModule::AUDIO_ENCODER:
break;
default:
if (error != nullptr) {
*error = std::string("flash attention is not configurable for module '") + sd_backend_module_name(setting.first) + "'";
}
return false;
}
if (!valid_mode(setting.second)) {
return false;
}
result.set_module(setting.first, setting.second);
}
}
*assignment = std::move(result);
return true;
}
bool SDBackendAssignment::empty() const {
return default_name.empty() && module_names.empty();
}
+7
View File
@@ -87,6 +87,12 @@ private:
bool sd_backend_is(ggml_backend_t backend, const std::string& name);
bool sd_backend_is_cpu(ggml_backend_t backend);
bool sd_backend_supports_cuda_mma(ggml_backend_t backend);
bool sd_backend_supports_hip_mma(ggml_backend_t backend);
ggml_backend_buffer_t sd_backend_dev_buffer_from_host_ptr(ggml_backend_dev_t device,
void* ptr,
size_t size,
size_t max_tensor_size);
ggml_backend_t sd_backend_cpu_init();
bool sd_backend_cpu_set_n_threads(ggml_backend_t backend_cpu, int n_threads);
ggml_status sd_backend_graph_compute_with_eval_callback(ggml_backend_t backend,
@@ -95,6 +101,7 @@ ggml_status sd_backend_graph_compute_with_eval_callback(ggml_backend_t backend,
void* callback_eval_user_data);
std::string sd_backend_resolve_name(const std::string& name);
bool sd_parse_backend_assignment(const std::string& spec, SDBackendAssignment* assignment, std::string* error);
bool sd_parse_flash_attention_assignment(const std::string& spec, SDBackendAssignment* assignment, std::string* error);
const char* sd_backend_module_name(SDBackendModule module);
void ggml_ext_im_set_f32_1d(const struct ggml_tensor* tensor, int i, float value);
bool add_rpc_devices(const std::string& servers);
+86 -29
View File
@@ -16,7 +16,7 @@
#include "ggml-alloc.h"
#include "ggml-backend.h"
#include "ggml/src/ggml-impl.h"
#include "ggml-impl.h"
namespace sd::ggml_graph_cut {
@@ -426,8 +426,8 @@ namespace sd::ggml_graph_cut {
if (tensor == nullptr || tensor->name[0] == '\0') {
return false;
}
return starts_with(tensor->name, GGML_RUNNER_CUT_PREFIX) &&
ends_with(tensor->name, GGML_RUNNER_CUT_SUFFIX);
return std::strncmp(tensor->name, GGML_RUNNER_CUT_PREFIX, std::strlen(GGML_RUNNER_CUT_PREFIX)) == 0 &&
tensor->name[std::strlen(tensor->name) - 1] == GGML_RUNNER_CUT_SUFFIX[0];
}
std::string make_graph_cut_name(const std::string& group, const std::string& output) {
@@ -492,35 +492,88 @@ namespace sd::ggml_graph_cut {
}
}
std::vector<uint64_t> graph_layout(ggml_cgraph* graph, bool include_bindings) {
std::vector<const ggml_tensor*> tensors;
std::unordered_map<const ggml_tensor*, size_t> indices;
auto add = [&](const ggml_tensor* tensor) {
if (tensor != nullptr && indices.emplace(tensor, tensors.size() + 1).second) {
tensors.push_back(tensor);
}
struct GraphLayoutTensors {
struct Entry {
const ggml_tensor* tensor = nullptr;
size_t index = 0;
};
std::vector<ggml_tensor*> tensors;
std::vector<Entry> entries;
explicit GraphLayoutTensors(size_t graph_size) {
tensors.reserve(graph_size);
size_t capacity = 2;
while (capacity < 2 * graph_size) {
capacity *= 2;
}
entries.resize(capacity);
}
size_t find(const ggml_tensor* tensor) const {
size_t hash = reinterpret_cast<uintptr_t>(tensor) >> 4;
hash ^= hash >> 16;
const size_t mask = entries.size() - 1;
size_t slot = hash & mask;
while (entries[slot].tensor != nullptr && entries[slot].tensor != tensor) {
slot = (slot + 1) & mask;
}
return slot;
}
void add(ggml_tensor* tensor) {
if (tensor == nullptr) {
return;
}
size_t slot = find(tensor);
if (entries[slot].tensor != nullptr) {
return;
}
if (2 * (tensors.size() + 1) > entries.size()) {
// Segment graphs can reference tensors outside their node and leaf arrays.
std::vector<Entry> next(2 * entries.size());
entries.swap(next);
for (size_t i = 0; i < tensors.size(); ++i) {
entries[find(tensors[i])] = {tensors[i], i + 1};
}
slot = find(tensor);
}
entries[slot] = {tensor, tensors.size() + 1};
tensors.push_back(tensor);
}
size_t index(const ggml_tensor* tensor) const {
return tensor == nullptr ? 0 : entries[find(tensor)].index;
}
};
std::vector<uint64_t> graph_layout(ggml_cgraph* graph, bool include_bindings) {
const size_t graph_size = static_cast<size_t>(graph->n_leafs) + graph->n_nodes;
GraphLayoutTensors layout_tensors(graph_size);
const auto& tensors = layout_tensors.tensors;
for (int i = 0; i < graph->n_leafs; ++i) {
add(graph->leafs[i]);
layout_tensors.add(graph->leafs[i]);
}
for (int i = 0; i < graph->n_nodes; ++i) {
add(graph->nodes[i]);
layout_tensors.add(graph->nodes[i]);
}
for (size_t i = 0; i < tensors.size(); ++i) {
add(tensors[i]->view_src);
layout_tensors.add(tensors[i]->view_src);
for (auto source : tensors[i]->src) {
add(source);
layout_tensors.add(source);
}
}
std::vector<uint64_t> signature;
signature.reserve(tensors.size() * 24);
const size_t tensor_fields = 5 + 2 * GGML_MAX_DIMS + GGML_MAX_SRC +
GGML_MAX_OP_PARAMS / sizeof(int32_t) + (include_bindings ? 2 : 0);
signature.reserve(2 + graph_size + tensors.size() * tensor_fields);
signature.push_back(graph->n_nodes);
signature.push_back(graph->n_leafs);
for (int i = 0; i < graph->n_leafs; ++i) {
signature.push_back(indices.at(graph->leafs[i]));
signature.push_back(layout_tensors.index(graph->leafs[i]));
}
for (int i = 0; i < graph->n_nodes; ++i) {
signature.push_back(indices.at(graph->nodes[i]));
signature.push_back(layout_tensors.index(graph->nodes[i]));
}
for (auto tensor : tensors) {
signature.push_back(tensor->op);
@@ -536,9 +589,9 @@ namespace sd::ggml_graph_cut {
signature.push_back(tensor->ne[d]);
signature.push_back(tensor->nb[d]);
}
signature.push_back(tensor->view_src == nullptr ? 0 : indices.at(tensor->view_src));
signature.push_back(layout_tensors.index(tensor->view_src));
for (auto source : tensor->src) {
signature.push_back(source == nullptr ? 0 : indices.at(source));
signature.push_back(layout_tensors.index(source));
}
if (!can_ignore_op_params(tensor->op)) {
for (int value : tensor->op_params) {
@@ -562,14 +615,18 @@ namespace sd::ggml_graph_cut {
return false;
}
}
std::vector<std::pair<int, std::string>> cut_markers;
for (int i = 0; i < ggml_graph_n_nodes(gf); ++i) {
auto node = ggml_graph_node(gf, i);
size_t cut_index = 0;
for (int i = 0; i < gf->n_nodes; ++i) {
auto node = gf->nodes[i];
if (is_graph_cut_tensor(node)) {
cut_markers.emplace_back(i, node->name);
if (cut_index >= plan.cut_markers.size() ||
plan.cut_markers[cut_index].first != i || plan.cut_markers[cut_index].second != node->name) {
return false;
}
++cut_index;
}
}
return cut_markers == plan.cut_markers;
return cut_index == plan.cut_markers.size();
}
bool plan_matches_graph(ggml_cgraph* gf, const Plan& plan) {
@@ -948,11 +1005,11 @@ namespace sd::ggml_graph_cut {
return plan;
}
Plan resolve_plan(ggml_backend_t backend,
ggml_cgraph* gf,
PlanCache* cache,
const std::unordered_set<const ggml_tensor*>& params_tensor_set,
const char* log_desc) {
const Plan& resolve_plan(ggml_backend_t backend,
ggml_cgraph* gf,
PlanCache* cache,
const std::unordered_set<const ggml_tensor*>& params_tensor_set,
const char* log_desc) {
GGML_ASSERT(backend != nullptr);
GGML_ASSERT(gf != nullptr);
GGML_ASSERT(cache != nullptr);
+6 -5
View File
@@ -94,11 +94,12 @@ namespace sd::ggml_graph_cut {
ggml_cgraph* gf,
const std::unordered_set<const ggml_tensor*>& params_tensor_set,
const char* log_desc);
Plan resolve_plan(ggml_backend_t backend,
ggml_cgraph* gf,
PlanCache* cache,
const std::unordered_set<const ggml_tensor*>& params_tensor_set,
const char* log_desc);
// The returned reference is valid until its cache entry is evicted or the cache is destroyed.
const Plan& resolve_plan(ggml_backend_t backend,
ggml_cgraph* gf,
PlanCache* cache,
const std::unordered_set<const ggml_tensor*>& params_tensor_set,
const char* log_desc);
} // namespace sd::ggml_graph_cut

Some files were not shown because too many files have changed in this diff Show More