Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e41fbcf455 | ||
|
|
012c7f8866 | ||
|
|
a5e48c66aa | ||
|
|
b29962db7a | ||
|
|
0f76bf5c1e | ||
|
|
bb6a0ccfa3 | ||
|
|
e6370bf093 | ||
|
|
2a5f81095d | ||
|
|
d234ac62fa | ||
|
|
0886f11b6b | ||
|
|
ea209dbfcf | ||
|
|
a85db96aa5 | ||
|
|
b29cad540a | ||
|
|
4c870cc60c | ||
|
|
50a64f075e | ||
|
|
95ff762308 | ||
|
|
e0ed724e28 | ||
|
|
40b1788476 | ||
|
|
cd7f986058 | ||
|
|
baf45b49e2 | ||
|
|
fd21ecfcee | ||
|
|
e779d4f149 | ||
|
|
deaeed6a2f | ||
|
|
39bc9d0b4b | ||
|
|
f18dbfced7 | ||
|
|
530fa39073 | ||
|
|
ddd6b3aa35 | ||
|
|
a0c8508e72 | ||
|
|
a77d362318 | ||
|
|
b15c8aac39 | ||
|
|
2e2ba3faec | ||
|
|
939895f00e | ||
|
|
845646b034 | ||
|
|
0518b34374 | ||
|
|
9b7da27374 | ||
|
|
269045e92c | ||
|
|
20050815ac | ||
|
|
a068716fab | ||
|
|
b13a48219d | ||
|
|
d55cc0f75f | ||
|
|
95b3090664 | ||
|
|
ab8d5ea8c3 | ||
|
|
f0aac524d3 | ||
|
|
68285df319 | ||
|
|
af0fc72c96 | ||
|
|
2f016e8e30 | ||
|
|
8c442fb0ad | ||
|
|
8cd731e7b4 | ||
|
|
e47426c044 | ||
|
|
2891378bb1 | ||
|
|
120e3e875c | ||
|
|
1d0a58a350 | ||
|
|
46362fc4c0 | ||
|
|
d763dae3b8 | ||
|
|
60423ab5f6 | ||
|
|
7e3e20a50b | ||
|
|
d637cfb935 | ||
|
|
792bcd344c | ||
|
|
2988060a43 | ||
|
|
1b0ba10893 | ||
|
|
b7a74e68d7 | ||
|
|
05845fde41 | ||
|
|
0e1d733b7a | ||
|
|
ce95c09ce1 | ||
|
|
f89d9b13d7 | ||
|
|
f2f168fa70 | ||
|
|
7867f6da4a | ||
|
|
228c707fde | ||
|
|
e16d26a96a | ||
|
|
a4a966990d | ||
|
|
a1ded76da5 | ||
|
|
8cc5ad8f1c | ||
|
|
4a892293ac | ||
|
|
fd43b542a6 | ||
|
|
0963357214 | ||
|
|
1da55ebde8 | ||
|
|
fb8ee71f55 | ||
|
|
96f67fe631 | ||
|
|
203a882a28 | ||
|
|
019bcfc0ae | ||
|
|
c150a6bafe | ||
|
|
f4b5326da7 | ||
|
|
bbdcad48dd | ||
|
|
b6947b8bdc | ||
|
|
9db0db6570 | ||
|
|
d4bfcd5d78 | ||
|
|
3f8527a46c | ||
|
|
42ab1c12f1 | ||
|
|
27f7c430c0 | ||
|
|
ede32a621e | ||
|
|
47e83d7136 | ||
|
|
f8890b9db3 | ||
|
|
61a83b85cc | ||
|
|
9947eebec8 | ||
|
|
168f7b88b9 | ||
|
|
2f886889e6 | ||
|
|
19bbbca1c7 | ||
|
|
39ada0863b | ||
|
|
4c3cf7543d | ||
|
|
510bccf330 | ||
|
|
5e7b29131e | ||
|
|
0a9340c599 | ||
|
|
b167b942f7 | ||
|
|
1a2330de68 | ||
|
|
740c7ae193 | ||
|
|
4dfe8f5d45 | ||
|
|
caa111adf3 | ||
|
|
88411ef1e0 | ||
|
|
500ef5fa7c | ||
|
|
2a4ebba818 | ||
|
|
70c1dbc01e | ||
|
|
36746936c0 | ||
|
|
2dc7f5408a |
No files matched your search
@@ -0,0 +1,2 @@
|
||||
/start-server.sh text eol=lf
|
||||
/start-server.bat text eol=crlf
|
||||
@@ -26,6 +26,9 @@ on:
|
||||
"ggml",
|
||||
"examples/server/frontend",
|
||||
"examples/server/frontend/**",
|
||||
"examples/server/recipes/*.json",
|
||||
"start-server.sh",
|
||||
"start-server.bat",
|
||||
]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
@@ -44,6 +47,9 @@ on:
|
||||
"ggml",
|
||||
"examples/server/frontend",
|
||||
"examples/server/frontend/**",
|
||||
"examples/server/recipes/*.json",
|
||||
"start-server.sh",
|
||||
"start-server.bat",
|
||||
]
|
||||
|
||||
env:
|
||||
@@ -107,7 +113,8 @@ jobs:
|
||||
run: |
|
||||
cp ggml/LICENSE ./build/bin/ggml.txt
|
||||
cp LICENSE ./build/bin/stable-diffusion.cpp.txt
|
||||
zip -j sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}.zip ./build/bin/*
|
||||
chmod +x start-server.sh
|
||||
zip -j sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}.zip ./build/bin/* ./start-server.sh ./start-server.bat
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
@@ -170,7 +177,8 @@ jobs:
|
||||
run: |
|
||||
cp ggml/LICENSE ./build/bin/ggml.txt
|
||||
cp LICENSE ./build/bin/stable-diffusion.cpp.txt
|
||||
zip -j sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}-vulkan.zip ./build/bin/*
|
||||
chmod +x start-server.sh
|
||||
zip -j sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}-vulkan.zip ./build/bin/* ./start-server.sh ./start-server.bat
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
@@ -325,7 +333,8 @@ jobs:
|
||||
run: |
|
||||
cp ggml/LICENSE ./build/bin/ggml.txt
|
||||
cp LICENSE ./build/bin/stable-diffusion.cpp.txt
|
||||
zip -j sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}.zip ./build/bin/*
|
||||
chmod +x start-server.sh
|
||||
zip -j sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}.zip ./build/bin/* ./start-server.sh ./start-server.bat
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
@@ -420,7 +429,7 @@ jobs:
|
||||
ls .\build\bin
|
||||
throw "Can't find stable-diffusion.dll"
|
||||
}
|
||||
7z a sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-${{ matrix.build }}-x64.zip $filePath
|
||||
7z a sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-${{ matrix.build }}-x64.zip $filePath .\start-server.sh .\start-server.bat
|
||||
|
||||
- name: Copy and pack Cuda runtime
|
||||
id: pack_cuda_runtime
|
||||
@@ -451,8 +460,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 +507,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,10 +569,22 @@ 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: |
|
||||
7z a sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-${{ env.ROCM_VERSION }}-x64.zip .\build\bin\*
|
||||
7z a sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-${{ env.ROCM_VERSION }}-x64.zip .\build\bin\* .\start-server.sh .\start-server.bat
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
@@ -582,9 +603,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 +619,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 +665,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)
|
||||
@@ -715,7 +746,8 @@ jobs:
|
||||
run: |
|
||||
cp ggml/LICENSE ./build/bin/ggml.txt
|
||||
cp LICENSE ./build/bin/stable-diffusion.cpp.txt
|
||||
zip -y -r sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-Ubuntu-${{ env.UBUNTU_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}-rocm-${{ matrix.ROCM_VERSION }}.zip ./build/bin
|
||||
chmod +x start-server.sh
|
||||
zip -y -r sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-Ubuntu-${{ env.UBUNTU_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}-rocm-${{ matrix.ROCM_VERSION }}.zip ./build/bin ./start-server.sh ./start-server.bat
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
@@ -7,6 +7,10 @@ tests/
|
||||
.cache/
|
||||
*.swp
|
||||
*.bat
|
||||
!/start-server.bat
|
||||
/start-server.local.sh
|
||||
/start-server.local.bat
|
||||
/start-server.local.ps1
|
||||
*.bin
|
||||
*.exe
|
||||
*.gguf
|
||||
|
||||
@@ -83,6 +83,8 @@ documentation.
|
||||
and debugging.
|
||||
* `examples/common/` - shared example support code.
|
||||
* `examples/server/` - server application built on top of the library.
|
||||
Before modifying built-in recipes, read and follow
|
||||
[the built-in recipe policy](examples/server/recipes/README.md).
|
||||
* `examples/server/frontend/` - git submodule containing independent frontend
|
||||
code. Avoid modifying it unless explicitly requested.
|
||||
|
||||
|
||||
@@ -225,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"
|
||||
@@ -342,9 +344,17 @@ if (SD_CUDA)
|
||||
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()
|
||||
target_include_directories(${SD_LIB} PUBLIC . src include)
|
||||
target_include_directories(${SD_LIB} PRIVATE src/core)
|
||||
target_include_directories(${SD_LIB} PUBLIC . thirdparty)
|
||||
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 include)
|
||||
target_include_directories(${SD_LIB} PRIVATE src src/core)
|
||||
target_include_directories(${SD_LIB} PUBLIC thirdparty)
|
||||
target_compile_features(${SD_LIB} PUBLIC c_std_11 cxx_std_17)
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ 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.
|
||||
@@ -25,6 +29,21 @@ 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.
|
||||
|
||||
## Server Recipes
|
||||
|
||||
Before adding or changing a built-in server recipe, read and follow the
|
||||
[built-in recipe policy](examples/server/recipes/README.md). The policy governs
|
||||
native editing, community editing LoRAs, distinct task checkpoints, external
|
||||
adapters, native components and layer-count variants. Do not add recipes solely
|
||||
for style or acceleration LoRAs, separately stored native components, or added
|
||||
or removed model layers. Explain the task, component compatibility or capability
|
||||
difference that justifies a new recipe, and verify its required components,
|
||||
feature declarations and embedded API representation.
|
||||
|
||||
## Code Style
|
||||
|
||||
Format code according to the repository style before submitting changes.
|
||||
|
||||
@@ -15,6 +15,8 @@ API and command-line option may change frequently.***
|
||||
|
||||
## 🔥Important News
|
||||
|
||||
* **2026/10/16** 🚀 stable-diffusion.cpp adds **Day-0 support for MOSS-Image-Base / MOSS-Image-SFT**
|
||||
* **2026/10/16** 🚀 stable-diffusion.cpp adds **Day-0 support for FLUX.3 Image Klein / Klein Base**
|
||||
* **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**
|
||||
@@ -44,6 +46,9 @@ API and command-line option may change frequently.***
|
||||
- [SD3/SD3.5](./docs/sd3.md)
|
||||
- [FLUX.1-dev/FLUX.1-schnell](./docs/flux.md)
|
||||
- [FLUX.2-dev/FLUX.2-klein](./docs/flux2.md)
|
||||
- [FLUX.3 Image Klein / Klein Base](./docs/flux3.md)
|
||||
- [MOSS-Image-Base / MOSS-Image-SFT](./docs/moss_image.md)
|
||||
- [Fibo 2.0 Dev](./docs/fibo2.md)
|
||||
- [Lens](./docs/lens.md)
|
||||
- [Chroma](./docs/chroma.md)
|
||||
- [Chroma1-Radiance](./docs/chroma_radiance.md)
|
||||
@@ -52,6 +57,7 @@ API and command-line option may change frequently.***
|
||||
- [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)
|
||||
@@ -64,6 +70,9 @@ API and command-line option may change frequently.***
|
||||
- [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)
|
||||
@@ -71,6 +80,7 @@ API and command-line option may change frequently.***
|
||||
- [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)
|
||||
- [Fibo 2.0 Dev / Edit Turbo](./docs/fibo2.md#editing-and-turbo)
|
||||
- Video Models
|
||||
- [Wan2.1/Wan2.2](./docs/wan.md)
|
||||
- [MiniMax-H3](./docs/minimax_h3.md)
|
||||
@@ -179,7 +189,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)
|
||||
@@ -187,6 +196,7 @@ These projects use `stable-diffusion.cpp` as a backend for their image generatio
|
||||
- [LocalAI](https://github.com/mudler/LocalAI)
|
||||
- [Neural-Pixel](https://github.com/Luiz-Alcantara/Neural-Pixel)
|
||||
- [KoboldCpp](https://github.com/LostRuins/koboldcpp)
|
||||
- [Kokon](https://github.com/GabrieleLozupone/kokon)
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 500 KiB |
|
After Width: | Height: | Size: 501 KiB |
|
After Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 489 KiB |
|
After Width: | Height: | Size: 611 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.1 MiB |
@@ -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" ]
|
||||
@@ -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" ]
|
||||
@@ -1,7 +1,6 @@
|
||||
ARG MUSA_VERSION=rc4.2.0
|
||||
ARG UBUNTU_VERSION=22.04
|
||||
ARG MUSA_IMAGE=registry.mthreads.com/mcconline/inference/pytorch:2.9.1.post1-py3.10-musa5.2.0-mp31-devel-ubuntu22.04-amd64
|
||||
|
||||
FROM mthreads/musa:${MUSA_VERSION}-devel-ubuntu${UBUNTU_VERSION}-amd64 as build
|
||||
FROM ${MUSA_IMAGE} 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 ccache cmake git ca-certificates curl gnupg && \
|
||||
@@ -22,9 +21,8 @@ COPY . .
|
||||
|
||||
RUN mkdir build && cd build && \
|
||||
cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_C_FLAGS="${CMAKE_C_FLAGS} -fopenmp -I/usr/lib/llvm-14/lib/clang/14.0.0/include -L/usr/lib/llvm-14/lib" \
|
||||
-DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -fopenmp -I/usr/lib/llvm-14/lib/clang/14.0.0/include -L/usr/lib/llvm-14/lib" \
|
||||
-DSD_MUSA=ON \
|
||||
-DMUSA_ARCHITECTURES=31 \
|
||||
-DSD_BUILD_SHARED_LIBS=ON \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DSD_BUILD_SHARED_GGML_LIB=ON \
|
||||
@@ -35,11 +33,14 @@ RUN mkdir build && cd build && \
|
||||
-DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build . --config Release
|
||||
|
||||
FROM mthreads/musa:${MUSA_VERSION}-runtime-ubuntu${UBUNTU_VERSION}-amd64 as runtime
|
||||
FROM ${MUSA_IMAGE} AS runtime
|
||||
|
||||
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
|
||||
|
||||
WORKDIR /sd.cpp
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
CMD []
|
||||
@@ -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" ]
|
||||
@@ -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" ]
|
||||
@@ -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,9 +1,10 @@
|
||||
# ADetailer
|
||||
|
||||
`sd-cli` can run a YOLOv8 object detector on an existing or newly generated
|
||||
image and perform a cropped inpaint pass for every detected object. The first
|
||||
implementation supports YOLOv8 detection checkpoints. YOLOv8 segmentation and
|
||||
MediaPipe models are not supported yet.
|
||||
image, and `sd-server` can run it after image generation. Both perform a cropped
|
||||
inpaint pass for every detected object. The implementation supports YOLOv8
|
||||
detection checkpoints. YOLOv8 segmentation and MediaPipe models are not
|
||||
supported yet.
|
||||
|
||||
## Convert a detector
|
||||
|
||||
@@ -68,6 +69,36 @@ ADetailer can also run automatically after normal image generation:
|
||||
--extra-ad-args "confidence=0.3,denoising_strength=0.4,inpaint_width=512,inpaint_height=512"
|
||||
```
|
||||
|
||||
The same options can be supplied when starting `sd-server`. When `--ad-model` is set, its detector runs after OpenAI-compatible and native asynchronous image generation. SDAPI requests enable ADetailer through `alwayson_scripts`, as described below. If no object is detected, the original image is returned unchanged. RGBA output keeps its original alpha channel, including when detected regions are repaired. Detector initialization or detail-pass failures are reported as request failures.
|
||||
|
||||
Server image-generation requests limit base, hires, and detail-pass dimensions to 4096 pixels per side. ADetailer step overrides are limited to 1–100; `steps=0` in `extra_ad_args` keeps inheriting the main generation steps. These limits also apply to inherited dimensions and `extra_ad_args` overrides. Requests exceeding the limits return a parameter error before generation. CLI limits are unchanged.
|
||||
|
||||
### SDAPI requests
|
||||
|
||||
`/sdapi/v1/txt2img` and `/sdapi/v1/img2img` accept the WebUI ADetailer argument format:
|
||||
|
||||
```json
|
||||
{
|
||||
"prompt": "portrait photo",
|
||||
"alwayson_scripts": {
|
||||
"ADetailer": {
|
||||
"args": [true, false, {
|
||||
"ad_model": "face_yolov8n.pt",
|
||||
"ad_prompt": "[PROMPT], detailed face",
|
||||
"ad_use_steps": true,
|
||||
"ad_steps": 20
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The detector must be configured at server startup with `--ad-model`. `ad_model` must match that file's basename; the original `.pt` basename is also accepted for a converted detector. Requests cannot load arbitrary detector paths. Omitting the script, setting its first argument to `false`, selecting `"None"`, or setting `ad_tab_enable` to `false` disables detailing. Dictionary-only argument arrays are also accepted. Multiple enabled dictionaries run in order using the configured detector.
|
||||
|
||||
Supported settings include prompts, confidence, mask filtering by area or confidence, mask count and area ratios, dilation/erosion, offsets, mask merging/inversion, blur, denoising strength, masked or full-image inpainting, padding, and optional overrides for inpaint dimensions, steps, CFG, sampler/scheduler, and CLIP skip. The corresponding `ad_use_*` flags control whether overrides apply. Otherwise dimensions, steps, CFG, sampler, scheduler, and CLIP skip inherit the main request. Denoising strength defaults to `0.4`. Pixel mask settings are limited to the larger output dimension, with a minimum limit of 32 and a maximum of 16384 pixels.
|
||||
|
||||
Model/VAE switching, non-default noise multipliers, face restoration, ControlNet, and skipping the initial img2img pass are not supported; active requests for these features return an error. Unknown settings and invalid active values also return errors. During detailing, `/sdapi/v1/progress` remains active with an `adetailer` job; its percentage and preview stay fixed until detailing finishes.
|
||||
|
||||
An empty ADetailer prompt inherits the main prompt. `[PROMPT]` inserts the main
|
||||
prompt, `[SEP]` assigns different prompts to consecutive masks, and `[SKIP]`
|
||||
skips the corresponding mask.
|
||||
@@ -82,6 +113,7 @@ All settings other than the detector path and prompts are passed through
|
||||
| `nms` | `0.45` | NMS IoU threshold |
|
||||
| `max_detections` | `100` | Maximum detections retained after NMS |
|
||||
| `mask_k_largest` | `0` | Keep only the largest K masks; zero keeps all |
|
||||
| `mask_filter_method` | `area` | Rank retained masks by `area` or `confidence` |
|
||||
| `mask_min_ratio` | `0` | Minimum bbox area relative to the image |
|
||||
| `mask_max_ratio` | `1` | Maximum bbox area relative to the image |
|
||||
| `dilate_erode` | `4` | Positive values dilate; negative values erode |
|
||||
@@ -90,6 +122,7 @@ All settings other than the detector path and prompts are passed through
|
||||
| `merge_masks`, `invert_mask` | `false` | Boolean alternatives to `mask_mode` |
|
||||
| `mask_blur` | `4` | Final composite feather radius |
|
||||
| `inpaint_padding` | `32` | Padding around the detected region |
|
||||
| `inpaint_only_masked` | `true` | Crop to the detected region; `false` uses the full image with the detection mask |
|
||||
| `inpaint_width`, `inpaint_height` | mode-specific | `512x512` after generation; input/output size in `adetailer` mode |
|
||||
| `denoising_strength` | mode-specific | `0.4` after generation; inherits `--strength` in `adetailer` mode |
|
||||
| `steps` | `0` | Detail steps; zero inherits the main generation |
|
||||
|
||||
@@ -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" />
|
||||
@@ -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 \
|
||||
|
||||
@@ -161,6 +161,9 @@ 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
|
||||
@@ -212,6 +215,123 @@ 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.
|
||||
|
||||
## Direct convolution (`--conv-direct on|off|auto`)
|
||||
|
||||
`--conv-direct` defaults to `auto` and accepts module assignments for
|
||||
`diffusion`, `vae` (including preview VAE), and `controlnet`, using the same
|
||||
aliases and precedence rules as `--flash-attn`:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --conv-direct auto,vae=off
|
||||
sd-cli -m model.safetensors -p "a cat" --conv-direct off,diffusion=on
|
||||
```
|
||||
|
||||
- `on` requests direct 2D and 3D convolution.
|
||||
- `off` uses the existing im2col-based convolution paths.
|
||||
- `auto` first checks support for a representative F16-weight/F32-input direct
|
||||
convolution graph. Vulkan then benchmarks direct convolution against im2col
|
||||
using two small shapes per dimension, with warmup, output validation, and five
|
||||
interleaved measurements per shape. Direct convolution must improve the sum of
|
||||
median times by at least 5%; a failed probe defaults to `off`.
|
||||
Other backends retain their implicit GEMM checks without benchmarking:
|
||||
CUDA 2D requires a compiled Turing-or-newer MMA architecture; HIP 2D requires
|
||||
the supported CDNA MFMA or RDNA WMMA architectures described below for flash
|
||||
attention. MUSA 2D has a SIMT implementation. CUDA, HIP, and MUSA 3D also have
|
||||
a SIMT implementation, so they do not require MMA. Other backends default to
|
||||
`off`. Results and decision logs are cached per compute backend and dimension
|
||||
in the context, as with flash attention. Each dimension is selected independently,
|
||||
and all compute devices in a module must qualify. Parameter-storage devices do
|
||||
not affect this choice.
|
||||
|
||||
These are backend-level defaults. GGML still chooses the actual kernel based on
|
||||
weight type, shape, and hardware; enabling direct convolution does not guarantee
|
||||
that every operation uses implicit GEMM. The CUDA-family implicit GEMM paths
|
||||
require F16 weights and can use ordinary direct kernels for other cases.
|
||||
This option does not change the separately created image upscaler context.
|
||||
|
||||
Repeating `--conv-direct` replaces the previous assignment string. Existing
|
||||
`--diffusion-conv-direct` (diffusion and ControlNet) and `--vae-conv-direct`
|
||||
flags are deprecated and emit a warning, but remain supported when
|
||||
`--conv-direct` is omitted. Use `--conv-direct auto,diffusion=on,controlnet=on`
|
||||
and `--conv-direct auto,vae=on`, respectively. An explicit
|
||||
`--conv-direct` takes precedence over both legacy flags regardless of order.
|
||||
Library callers can set the appended `sd_ctx_params_t::conv_direct_mode` field
|
||||
to the same string; null or empty selects `auto` while honoring legacy true
|
||||
flags. The context copies the string.
|
||||
|
||||
## 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 |
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# 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. `src/core/probe/conv_direct.cpp` uses metadata-only probe graphs
|
||||
to check direct 2D/3D convolution support, then enables known implicit GEMM
|
||||
paths on non-Vulkan backends. Vulkan compares direct convolution with im2col
|
||||
using the shared measurement machinery.
|
||||
|
||||
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.
|
||||
|
||||
Convolution results are cached separately for 2D and 3D, once per compute backend
|
||||
in the context, just like attention results. Each probe logs its backend, decision,
|
||||
and reason; mixed-device modules log when their compute devices disagree.
|
||||
|
||||
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.
|
||||
@@ -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" />
|
||||
@@ -119,14 +119,16 @@ cmake --build . --config Release
|
||||
|
||||
## Build with MUSA
|
||||
|
||||
This provides GPU acceleration using Moore Threads GPU. Make sure to have the MUSA toolkit installed.
|
||||
This provides GPU acceleration using Moore Threads GPUs. The configuration below follows upstream validation with MUSA SDK 5.2.0 and MTT S5000 (PH1, `mp_31`). Install that toolkit before building. Older SDKs such as 4.2 may lack the CUB headers required by the current backend.
|
||||
|
||||
```shell
|
||||
mkdir build && cd build
|
||||
cmake .. -DCMAKE_C_COMPILER=/usr/local/musa/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/musa/bin/clang++ -DSD_MUSA=ON -DCMAKE_BUILD_TYPE=Release
|
||||
cmake .. -DCMAKE_C_COMPILER=/usr/local/musa/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/musa/bin/clang++ -DSD_MUSA=ON -DMUSA_ARCHITECTURES=31 -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build . --config Release
|
||||
```
|
||||
|
||||
The MUSA Docker image uses the same SDK 5.2.0 development image as upstream for both build and runtime stages, and targets `mp_31` only. It includes the development tools and PyTorch from that base image, so it is larger than a minimal runtime image. Other GPU architectures require separate validation.
|
||||
|
||||
## Build with Metal
|
||||
|
||||
Using Metal makes the computation run on the GPU. Currently, there are some issues with Metal when performing operations on very large matrices, making it highly inefficient at the moment. Performance improvements are expected in the near future.
|
||||
|
||||
@@ -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"
|
||||
```
|
||||
@@ -14,10 +14,12 @@ Depending on the architecture, different models handle reference images differen
|
||||
| [**Qwen Image Edit**](./qwen_image_edit.md) | `qwen` |
|
||||
| **Qwen Image LAYERED** | `qwen_layered` |
|
||||
| [**Flux.2 [Dev] / Flux.2 [Klein]**](./flux2.md) | `flux2` |
|
||||
| [**FLUX.3 Image Klein / Klein Base**](./flux3.md) | `flux2` |
|
||||
| [**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` |
|
||||
| [**Fibo 2.0 Dev / Edit Turbo**](./fibo2.md#examples) | `fibo2` |
|
||||
| **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.
|
||||
@@ -49,6 +51,7 @@ The `--ref-image-args` argument accepts a comma-separated list of key-value pair
|
||||
| `flux_kontext` | FLUX.1 Kontext |
|
||||
| `longcat` | LongCat Image Edit |
|
||||
| `flux2` | FLUX.2 models |
|
||||
| `fibo2` | Fibo 2.0 Dev / Edit Turbo |
|
||||
| `qwen` | Qwen Image Edit |
|
||||
| `qwen_layered` | Qwen Image Layered |
|
||||
| `z_image_omni` | Boogu, Z-Image Omni |
|
||||
@@ -72,11 +75,19 @@ If presets are insufficient, you can manually configure the following parameters
|
||||
| `ref_index_mode` | string | Behavior of the RoPE index. | `fixed`, `increase`, `decrease` |
|
||||
| `force_ref_timestep_zero` | bool | Forces timestep=0 for reference tokens. | `true`, `false` (Krea2 only) |
|
||||
| `resize_before_vae` | bool | Whether reference images are resized before VAE encoding. | `true`, `false` |
|
||||
| `vae_input_max_pixels` | int | Maximum pixel area for VAE reference inputs. | Integer |
|
||||
| `resize_vae_to_target` | bool | Use output dimensions for every reference, overriding area-based sizing. | `true`, `false` |
|
||||
| `resize_single_ref_to_target` | bool | Use output dimensions when exactly one reference is supplied. | `true`, `false` |
|
||||
| `vae_size_buckets` | bool | Choose aligned dimensions within ±10% of the requested area, minimizing aspect-ratio cropping. Ignored when using output dimensions. | `true`, `false` |
|
||||
| `vae_center_crop` | bool | Scale to cover the selected dimensions with Lanczos, then center-crop. Otherwise resize directly. | `true`, `false` |
|
||||
| `vae_input_max_pixels` | int | VAE area budget, also capped by output area in ordinary resizing. With size buckets, this is the target area and output area does not cap it. | Integer |
|
||||
| `vlm_use_vae_input` | bool | Reuse the resized/cropped VAE input for the VLM, before the VLM's own resize. Has no effect when `resize_before_vae=false`. | `true`, `false` |
|
||||
| `vlm_resize_mode` | string | How to resize VLM reference inputs. | `longest_side`, `area`, `none` |
|
||||
| `vlm_max_size` | int | Maximum VLM input size; interpreted according to `vlm_resize_mode`. | Integer |
|
||||
| `vlm_min_size` | int | Minimum VLM input size; interpreted according to `vlm_resize_mode`. | Integer |
|
||||
| `vlm_size` | int | Shortcut to set both VLM min and max size to the same value. | Integer |
|
||||
| `ref_image_size` | string | MiniMax-H3 reference image sizing before VAE encoding. `match` scales each reference down to the generation's pixel area; `max` uses a 2048 px short edge. Only references above the limit are downscaled; each axis is then snapped to the nearest 32 px multiple (an axis of at least 16 px grows by at most 16 px, and references are never narrower than 32 px). | `match`, `max` |
|
||||
|
||||
`ref_image_size` applies to `vid_gen` with MiniMax-H3 Ref2VA. Reference latents are attended to at every sampling step, so `max` can be several times slower.
|
||||
|
||||
### Preset Default Values
|
||||
|
||||
@@ -87,6 +98,7 @@ For a technical overview of how each preset is configured, see the table below.
|
||||
| `flux_kontext` | No | `fixed` | `none` | |
|
||||
| `longcat` | Yes | `fixed` | `area` | |
|
||||
| `flux2` | No | `increase` | `none` | |
|
||||
| `fibo2` | Yes | `increase` | `longest` | Shared reference crops; clean reference timesteps; see [Fibo 2.0](./fibo2.md) |
|
||||
| `qwen` | Yes | `increase` | `area` | |
|
||||
| `qwen_layered` | Yes | `decrease` | `area` | |
|
||||
| `mage_flow` | Yes | `increase` | `longest` | `vlm_max_size = 384`, VAE input resized to target |
|
||||
@@ -98,3 +110,5 @@ For a technical overview of how each preset is configured, see the table below.
|
||||
**Additional Default Notes:**
|
||||
- **VLM Input Sizes:** For most presets, `vlm_max_size` and `vlm_min_size` are set to `-1`, meaning the values are model-dependent and handled automatically. In `area` mode they represent pixel area; in `longest_side` mode they represent a side length in pixels.
|
||||
- **VAE Input Size:** `vae_input_max_pixels` defaults to $1024 \times 1024$ pixels (`1048576`).
|
||||
- **VAE Geometry:** `resize_vae_to_target` defaults to `true` for Mage-Flow and LLaDA-Image. Fibo enables `resize_single_ref_to_target`, `vae_size_buckets`, and `vae_center_crop`. These options are otherwise `false`. Bucket dimensions use the model's alignment (normally 16, Qwen Image 32), with a minimum side of four alignment units; if no bucket fits, output dimensions are used.
|
||||
- **Shared Encoder Input:** `vlm_use_vae_input` defaults to `true` for Fibo and Qwen Image 2.1, otherwise `false`. All these options can be overridden individually after selecting a preset.
|
||||
@@ -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" />
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
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.
|
||||
|
||||
RGBA images, including Qwen Image 2.1 output, keep their alpha channel during model upscaling and hires fix. ESRGAN processes the RGB channels; the alpha channel is resized with bilinear interpolation and recombined with the upscaled image.
|
||||
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:
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# How to Use
|
||||
|
||||
Fibo 2.0 uses a Fibo diffusion transformer, the standard FLUX.2 VAE, and Qwen3-VL 4B as the LLM text encoder. The Qwen tokenizer is built in.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download Fibo 2.0 Dev
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Fibo-2.0/tree/main/diffusion_models
|
||||
- Download Fibo 2.0 Edit Turbo
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Fibo-2.0/tree/main/diffusion_models
|
||||
- Download vae
|
||||
- safetensors: https://huggingface.co/black-forest-labs/FLUX.2-dev/tree/main
|
||||
- Download Qwen3-VL 4B
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Fibo-2.0/tree/main/text_encoders
|
||||
- gguf: https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct-GGUF/tree/main
|
||||
|
||||
The original Diffusers weights from [briaai/Fibo-2.0-dev](https://huggingface.co/briaai/Fibo-2.0-dev) and [briaai/Fibo-2.0-edit-turbo](https://huggingface.co/briaai/Fibo-2.0-edit-turbo) are also supported. Pass the transformer's `diffusion_pytorch_model.safetensors.index.json` to `--diffusion-model` and keep its shards alongside it.
|
||||
|
||||
INT8 convrot weights load directly; see [INT8 convrot](./int8_convrot.md) for backend requirements.
|
||||
|
||||
## Examples
|
||||
|
||||
### Fibo 2.0 Dev
|
||||
|
||||
Dev supports both text-to-image generation and reference-image editing.
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\fibo2_dev_int8_convrot.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\flux2_ae.safetensors -p "a lovely cat holding a sign says 'fibo2.cpp'" --cfg-scale 5.0 --sampling-method euler -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img src="../assets/fibo2/t2i.png" />
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\fibo2_dev_int8_convrot.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\flux2_ae.safetensors -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'fibo2.cpp'" --cfg-scale 5.0 --sampling-method euler -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img src="../assets/fibo2/dev_edit.png" />
|
||||
|
||||
### Fibo 2.0 Edit Turbo
|
||||
|
||||
Edit Turbo is distilled for reference-image editing in 4 steps with CFG 1.
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\fibo2_edit_turbo_int8_convrot.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\flux2_ae.safetensors -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'fibo2.cpp'" --cfg-scale 1 --steps 4 --sampling-method euler -v --offload-to-cpu
|
||||
```
|
||||
|
||||
The BF16 files accept the same options. Use dimensions divisible by 16. Repeat `-r` to use multiple reference images; instructions can refer to `<image_1>`, `<image_2>`, etc. See [Image Editing](./edit.md) for reference-image preprocessing options. Separate GGUF text encoders need their vision projector passed with `--llm_vision` for editing.
|
||||
|
||||
The automatic `fibo2` reference preset combines `resize_single_ref_to_target=true`, `vae_size_buckets=true`, `vae_center_crop=true`, and `vlm_use_vae_input=true`. A single reference is scaled to cover the output dimensions and center-cropped. Multiple references use dimensions divisible by 16, chosen within ±10% of one megapixel to minimize cropping for each image's aspect ratio; `vae_input_max_pixels` overrides that target area. The VAE and vision encoder receive the same crops, with smaller copies used by the vision encoder. These are generic reference-image options and can be overridden individually: for example, `--ref-image-args vae_center_crop=false` uses direct resizing, while `resize_before_vae=false` skips this automatic crop and resize step entirely.
|
||||
|
||||
The vision encoder defaults to `vlm_resize_mode=longest_side,vlm_max_size=768`. Override these with `--ref-image-args`, for example `vlm_max_size=512`, or use `vlm_resize_mode=none` to skip size-based scaling. Vision inputs are still aligned to the encoder's patch grid.
|
||||
|
||||
Plain prompts and structured JSON captions are accepted. This integration uses the caption-only text-rendering path (`render_text_images=False`); it does not generate auxiliary glyph images or expand prompts with Bria VGL. Native Fibo mask preprocessing is not connected to `--mask`.
|
||||
@@ -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" />
|
||||
@@ -0,0 +1,50 @@
|
||||
# How to Use
|
||||
|
||||
FLUX.3 Image Klein uses a FLUX.3 diffusion transformer, the FLUX.2 VAE, and Qwen3-VL 4B as the LLM text encoder.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download FLUX.3 Image Klein
|
||||
- safetensors: https://huggingface.co/black-forest-labs/flux-3-image-klein/tree/main
|
||||
- gguf: https://huggingface.co/leejet/flux-3-image-klein-GGUF/tree/main
|
||||
- Download FLUX.3 Image Klein Base
|
||||
- safetensors: https://huggingface.co/black-forest-labs/flux-3-image-klein-base/tree/main
|
||||
- gguf: https://huggingface.co/leejet/flux-3-image-klein-base-GGUF/tree/main
|
||||
- Download vae
|
||||
- safetensors: https://huggingface.co/black-forest-labs/FLUX.2-dev/tree/main
|
||||
- The `ae.safetensors` included with FLUX.3 can also be used.
|
||||
- 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
|
||||
|
||||
Use the root `model.safetensors` for the diffusion model.
|
||||
|
||||
## Examples
|
||||
|
||||
### FLUX.3 Image Klein
|
||||
|
||||
The distilled model uses 16 steps and CFG 1.0.
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-3-image-klein.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors -p "a lovely cat holding a sign says 'flux3.cpp'" --cfg-scale 1.0 --steps 16 --sampling-method euler -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img src="../assets/flux3/flux3-image-klein.png" />
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-3-image-klein.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'flux3-image-klein.cpp'" --cfg-scale 1.0 --sampling-method euler -v --offload-to-cpu --steps 16
|
||||
```
|
||||
|
||||
<img src="../assets/flux3/flux3-image-klein-edit.png" />
|
||||
|
||||
Repeat `-r` to use multiple reference images, up to 10. See [Image Editing](./edit.md) for reference-image preprocessing options.
|
||||
|
||||
### FLUX.3 Image Klein Base
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-3-image-klein-base.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors -p "a lovely cat holding a sign says 'flux3.cpp'" --cfg-scale 3.5 --steps 20 --sampling-method euler -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img src="../assets/flux3/flux3-image-klein-base.png" />
|
||||
|
||||
The default discrete scheduler uses the upstream time shift of 6.93 for both models. Use `--flow-shift` to override it.
|
||||
@@ -0,0 +1,165 @@
|
||||
# Generation results and metadata
|
||||
|
||||
`generate_image()`, `generate_video()`, `adetail_image()` and `upscale()` return
|
||||
owned results containing the media and its generation record. This is a breaking
|
||||
C API change: applications and bindings must update their calls and rebuild.
|
||||
|
||||
```c
|
||||
sd_img_gen_params_t params;
|
||||
sd_img_gen_params_init(¶ms);
|
||||
params.prompt = "a small house";
|
||||
|
||||
sd_image_generation_result_t* result = NULL;
|
||||
if (generate_image(ctx, ¶ms, &result)) {
|
||||
for (int i = 0; i < result->count; ++i) {
|
||||
const sd_image_result_t* item = &result->items[i];
|
||||
/* Consume item->image and item->generation_info together. */
|
||||
}
|
||||
free_sd_image_generation_result(result);
|
||||
}
|
||||
```
|
||||
|
||||
`result_out` is required and is set to `NULL` on failure. A successful result
|
||||
owns its pixels and UTF-8 JSON strings. The video result additionally owns its
|
||||
audio and contains the effective `fps` and `frame_count`. Results remain valid
|
||||
after freeing the generation context. Use the matching library result destructor;
|
||||
do not separately free fields unless transferring ownership and clearing them.
|
||||
Both result destructors accept `NULL`.
|
||||
|
||||
## Reproduction parameters
|
||||
|
||||
The `sdcpp.generation/v1` JSON is a snapshot of generation parameters for the
|
||||
same loaded context and the same input images, masks, reference media and other
|
||||
assets. It is created after `GenerationRequest` resolves the request, before
|
||||
preprocessing or sampling. A failed call returns no result or metadata.
|
||||
|
||||
Fields follow the C generation parameter structs: `prompt`, `negative_prompt`,
|
||||
`width`, `height`, `seed`, `sample_params`, `hires`, `cache`,
|
||||
`vae_tiling_params`, `image_preprocess`, and the image/video-specific options.
|
||||
`kind` distinguishes image and video parameters. There is no inferred `task`.
|
||||
Enums use their API string names. Seeds are decimal strings to preserve their
|
||||
full integer range. Omitted optional fields retain the parameter initializer's
|
||||
defaults, including automatic values represented by infinity in the C API.
|
||||
|
||||
Disabled optional features are omitted entirely: `cache`, `hires`, VAE tiling,
|
||||
zero/nonpositive `shifted_timestep`, inactive SLG, false circular axes, and empty
|
||||
preprocessing/reference arguments. SLG layers remain present when `slg_uncond`
|
||||
is enabled even with zero SLG scale. Enabled caches store their mode's parameters;
|
||||
TaylorSeer retains the shared DBCache thresholds and limits used by the runtime.
|
||||
Image CFG at its neutral value of 1 is omitted and resolves back to 1 by default.
|
||||
|
||||
Image strength, ControlNet/IP-Adapter strength and PhotoMaker settings are stored
|
||||
when their corresponding input images are supplied. PuLID requires an embedding
|
||||
path. Layer count is stored for layered generation; video VACE and high-noise
|
||||
settings require those models. High-noise settings omit scheduler, custom sigmas,
|
||||
flow shift and timestep shifting because that pass shares the base configuration.
|
||||
Explicit nonempty argument strings are retained without rewriting their contents.
|
||||
Core sampling values such as seed, sampler, scheduler, steps and guidance remain
|
||||
explicit even when equal to their defaults. ADetailer always retains its inpaint
|
||||
strength because its input crops are created later.
|
||||
Model-required VAE tiling is resolved before serialization, so forced tiling
|
||||
(such as MiniMax-H3) remains present even if the caller did not enable it.
|
||||
|
||||
The request fixes the seed, dimensions, clip skip, guidance, sampler, scheduler
|
||||
and eta before taking the snapshot. `custom_sigmas` is stored only when supplied
|
||||
as an input. Automatically computed schedules, preprocessing transforms,
|
||||
conditioning/cache decisions, detector regions and output statistics are not
|
||||
stored: they are recomputed from the same parameters and inputs.
|
||||
|
||||
Each image records the seed used to sample its latent. `batch_count` is omitted;
|
||||
replay image parameters with `batch_count = 1`. Layers or frames decoded from
|
||||
the same latent share its seed, so layered generation reproduces that group of
|
||||
layers. Video frames share the video request. Partial batch cancellation returns
|
||||
metadata only for completed outputs. Full cancellation returns no result.
|
||||
|
||||
The current image pipeline prepares input latents once using the starting seed
|
||||
and shares them across a batch. With a stochastic VAE encoder, replaying a later
|
||||
image alone using its sampling seed can change those input latents. Per-image
|
||||
seeds therefore do not guarantee independent reproduction for these paths.
|
||||
|
||||
To replay a record, initialize the matching C parameter struct, populate its
|
||||
fields from the JSON, restore the same input buffers, and call generation with
|
||||
the same context. Keep custom-sigma/SLG arrays and string storage alive for the
|
||||
call. Paths supplied as generation parameters (LoRAs, hires models and identity
|
||||
embeddings) are retained. `models` identifies successfully loaded context model
|
||||
files by component (`model`, `diffusion_model`, `clip_l`, `vae`, etc.), using
|
||||
filenames with extensions and omitting directories. Optional components disabled
|
||||
for the request and preview-only TAE are omitted. WebUI text includes `Model`
|
||||
(the diffusion model when supplied separately, otherwise the checkpoint, without
|
||||
the extension) and `VAE` for a separate active VAE. These names are identifiers,
|
||||
not hashes or sufficient configuration to rebuild a context; replay still requires
|
||||
the same model files and context settings. The JSON does not embed media buffers
|
||||
or model weights.
|
||||
|
||||
When `--lora-model-dir` is configured, CLI/server output records express LoRA
|
||||
paths relative to that directory, preserving subdirectories and file extensions
|
||||
and using `/` separators. This applies to file metadata, API responses and
|
||||
ADetailer records. Resolve these paths against the same LoRA directory before
|
||||
passing them back to the C API. Core results retain the supplied loading paths.
|
||||
Files outside the directory retain `..` components; paths on another volume,
|
||||
or records without a configured LoRA directory, retain their original paths.
|
||||
|
||||
## Postprocessing
|
||||
|
||||
Upscale records `upscale.upscale_factor`. ADetailer records `adetailer` and
|
||||
`inpaint_params`, with a concrete starting seed chosen before detection. These
|
||||
operations require the same upscaler/detector context and source image on replay.
|
||||
They do not store detection results or per-region generation traces.
|
||||
|
||||
The CLI/server append operation parameter snapshots to `postprocess` in call
|
||||
order. The source generation parameters remain unchanged. Frame postprocessing
|
||||
uses the same operation parameters for each video frame. Standalone
|
||||
postprocessing does not invent source diffusion parameters for an input image.
|
||||
|
||||
## File metadata
|
||||
|
||||
Image encoders accept the native record and derive WebUI infotext from it.
|
||||
Images use the existing metadata locations and combined text layout:
|
||||
`<WebUI infotext>, SDCPP: <native JSON>`. Standalone postprocessing without
|
||||
source diffusion parameters stores only native JSON. Video metadata stores
|
||||
only native JSON.
|
||||
|
||||
| Container | Metadata location |
|
||||
| --- | --- |
|
||||
| PNG | `tEXt`, keyword `parameters` |
|
||||
| JPEG | COM segment, `parameters` label followed by a NUL byte |
|
||||
| WebP image / animated WebP | XMP property `sdcpp:parameters` |
|
||||
| AVI | RIFF INFO `ICMT` |
|
||||
| WebM | `COMMENT` tag |
|
||||
|
||||
Inspect images or videos with `sd-cli -M metadata --image <file>`; the same
|
||||
command supports all containers above, with `--metadata-format json` for a
|
||||
structured report. Video metadata inspection does not require a model or video
|
||||
decoder.
|
||||
Matroska containers are also supported, including `.webm` outputs whose audio
|
||||
codec causes the muxer to select the Matroska document type.
|
||||
|
||||
The XMP namespace is `https://github.com/leejet/stable-diffusion.cpp/ns/1.0/`.
|
||||
PNG and JPEG retain the existing UTF-8 text payload convention. Memory encoding
|
||||
and file saving use the same implementation. The core result and API
|
||||
`generation_info` fields remain pure JSON; the combined layout applies only
|
||||
to embedded image metadata.
|
||||
|
||||
WebUI conversion is lossy: known samplers and schedules have explicit name
|
||||
mappings; unsupported ones use `SDCPP Sampler`/`SDCPP Schedule` fields. Hires
|
||||
keeps the base request size and separate configured hires size/steps. The native
|
||||
record retains the full generation parameters. Standalone postprocessing has no WebUI
|
||||
infotext because its original diffusion settings are unknown.
|
||||
|
||||
WebUI prompt text appends ordinary LoRAs as `<lora:filename_without_extension:weight>`.
|
||||
Directory components are omitted to match WebUI's filename lookup; the native
|
||||
`loras` array retains relative paths and weights. High-noise-only LoRAs and names
|
||||
that cannot be represented by this tag syntax remain in the native record only.
|
||||
The native prompt is not modified, and postprocessing LoRAs are not added to the
|
||||
source generation prompt.
|
||||
|
||||
JPEG requires the COM payload, including the label and terminating NUL bytes,
|
||||
to fit within 65,533 bytes. Larger records fail encoding with an error rather
|
||||
than being truncated. Use PNG or WebP for larger records.
|
||||
|
||||
Disabling file metadata omits both image representations, but does not disable
|
||||
the core generation record or its API response. Native async image results and
|
||||
OpenAI image response items contain `generation_info`; video results contain a
|
||||
sequence-level `generation_info`. The SD API `info` object contains per-output
|
||||
`generation_info` alongside derived `infotexts` and each image's seed. Its outer
|
||||
`parameters` field continues to echo the request.
|
||||
@@ -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>
|
||||
@@ -49,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" />
|
||||
@@ -100,10 +100,13 @@ for individual references, identity images, or video control frames.
|
||||
--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.
|
||||
If init and mask source dimensions differ, the mask is first center-cropped to
|
||||
the init source aspect ratio and resized to the init source dimensions using
|
||||
nearest-exact interpolation. The mask then inherits the init crop, resize, and
|
||||
padding coordinates, while retaining its own filter, padding value, and Canny
|
||||
setting for that transform. Explicit mask geometry is evaluated after this
|
||||
alignment; conflicting geometry is rejected. An omitted mask remains absent
|
||||
until the original pipeline creates its default mask.
|
||||
|
||||
## Downstream behavior
|
||||
|
||||
@@ -128,7 +131,8 @@ 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.
|
||||
still apply; MiniMax-H3 requires 32-pixel-aligned references and warns instead
|
||||
of honoring the switch.
|
||||
|
||||
## Server requests
|
||||
|
||||
@@ -148,12 +152,12 @@ an array of rule strings:
|
||||
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.
|
||||
user rules for replay with the same input images; 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:
|
||||
Generation returns media and actual parameters together; see [generation metadata](generation_metadata.md):
|
||||
|
||||
```c
|
||||
sd_img_gen_params_t params;
|
||||
@@ -161,7 +165,10 @@ sd_img_gen_params_init(¶ms);
|
||||
/* 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, ¶ms, &images, &count);
|
||||
sd_image_generation_result_t* result = NULL;
|
||||
bool ok = generate_image(ctx, ¶ms, &result);
|
||||
/* Consume result->items and their generation_info on success. */
|
||||
free_sd_image_generation_result(result);
|
||||
```
|
||||
|
||||
Both generation parameter initializers set `image_preprocess.rules` to `NULL`,
|
||||
|
||||
@@ -84,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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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" />
|
||||
@@ -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" />
|
||||
@@ -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
|
||||
```
|
||||
@@ -122,7 +122,6 @@ save_file(merged, "llada_connectors.safetensors")
|
||||
--height 1024 \
|
||||
--steps 4 \
|
||||
--cfg-scale 1.0 \
|
||||
--diffusion-fa \
|
||||
--output output.png
|
||||
```
|
||||
|
||||
@@ -145,7 +144,7 @@ only in the positive branch. Editing requires connectors that include the SigVQ
|
||||
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
|
||||
`--diffusion-fa`; without it the diffusion graph does not fit.
|
||||
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.
|
||||
|
||||
@@ -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" />
|
||||
@@ -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,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" />
|
||||
@@ -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
|
||||
```
|
||||
@@ -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>
|
||||
@@ -80,8 +80,20 @@ latents condition the diffusion transformer. Paired video and audio references
|
||||
share the same timeline. Ref2VA cannot be combined with `--init-img` or
|
||||
`--end-img` in one request.
|
||||
|
||||
Reference images keep their aspect ratio and are only downscaled when their
|
||||
pixel area exceeds the requested generation canvas.
|
||||
Reference images keep their aspect ratio and are only downscaled when they
|
||||
exceed the configured limit; neither mode scales a reference up. The limit is
|
||||
selected by `ref_image_size`. The default, `match`, uses the pixel area of the
|
||||
requested generation canvas. Pass `--ref-image-args "ref_image_size=max"` (or
|
||||
`ref_image_size` in `sd_vid_gen_params_t.ref_image_args`) to use a 2048 px short
|
||||
edge instead, which preserves more identity detail at a higher sampling cost:
|
||||
reference latents are attended at every sampling step.
|
||||
|
||||
Whichever limit applies, each axis is then snapped to the nearest 32-pixel
|
||||
multiple. An axis of at least 16 pixels can therefore grow by up to 16 pixels
|
||||
past the scaled size (a 48-pixel axis becomes 64), and references are never
|
||||
narrower than 32 pixels.
|
||||
`resize_before_vae=false` is not supported here; MiniMax-H3 keeps references on
|
||||
the 32-pixel grid and reports a warning.
|
||||
|
||||
The C API exposes the same inputs through `ref_images`, `ref_videos`, and
|
||||
`ref_audios` in `sd_vid_gen_params_t`. Each `sd_ref_video_t` supplies its own
|
||||
|
||||
@@ -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,7 +1,7 @@
|
||||
# Model Configuration Conventions
|
||||
|
||||
This document describes the conventions for model configuration structs and
|
||||
weight-based configuration detection.
|
||||
This document describes model configuration structs, weight-based configuration
|
||||
detection, and the definitions used to integrate models into the runtime.
|
||||
|
||||
## Config Types
|
||||
|
||||
@@ -116,3 +116,113 @@ struct.
|
||||
If a model has no weight-derived config today, it may still provide
|
||||
`detect_from_weights` for API consistency, but it should not print a config
|
||||
detection log unless it actually derives values from weights.
|
||||
|
||||
## Model Integration Definitions
|
||||
|
||||
Every `SDVersion` has an explicit `ModelDefinition` in
|
||||
`src/model/integrations/`. Related variants share an integration file and runner
|
||||
factory. Network blocks and their weight-derived configuration remain under
|
||||
`src/model/diffusion/`; reusable text encoder implementations remain under
|
||||
`src/model/te/`. Weight-name conversion remains in `src/name_conversion.cpp`.
|
||||
|
||||
To add a model:
|
||||
|
||||
1. Add its identity to `SDVersion` without renumbering existing values.
|
||||
2. Implement the network and `*Config::detect_from_weights` following the
|
||||
conventions above, or reuse an existing runner.
|
||||
3. Add a definition in its integration file, including its component
|
||||
factory, traits, geometry, and sampling policy.
|
||||
4. Add the family's explicit registration in `src/model_registry.cpp`. For a new
|
||||
variant of a registered family, add its definition to that family's list.
|
||||
5. Add version detection signatures and any required weight-name mappings in
|
||||
`src/model_loader.cpp` and `src/name_conversion.cpp` respectively. Update shared
|
||||
conditioning or runtime code only when existing behavior cannot serve the model.
|
||||
|
||||
Use the existing family integration as the starting point for a variant. For a
|
||||
new family, `src/model/integrations/moss_image.cpp` is a compact example of a
|
||||
conditioner/diffusion factory and its definition. Definitions collect model
|
||||
construction and defaults; they do not replace version detection, name conversion,
|
||||
or model-specific inference algorithms.
|
||||
|
||||
The registry checks for missing, duplicate, and invalid version registrations.
|
||||
Explicit references keep integrations reachable in static-library builds. The
|
||||
existing CMake glob includes integration sources automatically. No legacy
|
||||
builder, display-name table, or prediction-default fallback needs updating.
|
||||
|
||||
### Detection And Weight Conversion
|
||||
|
||||
Version detection remains in `ModelLoader::get_sd_version()` in
|
||||
`src/model_loader.cpp`. Add model signatures there and preserve the order of
|
||||
overlapping checks. Detection uses tensor names and metadata, and can run before
|
||||
and after name conversion, so signatures must work at both stages. Do not load
|
||||
tensor data to identify an architecture. The registry looks up definitions by
|
||||
the detected version; it does not perform detection.
|
||||
|
||||
Weight-name mappings and their version-based dispatch remain centralized in
|
||||
`src/name_conversion.cpp`. Add new mappings there when needed. Inference, GGUF
|
||||
export, quantization, and LoRA conversion share the `convert_tensor_name` entry
|
||||
point; preserve its prefix normalization and component conversion order.
|
||||
|
||||
### Components And Conditioning
|
||||
|
||||
The core factory receives `model_builders::Context`, the tokenizer configuration,
|
||||
and the output runners. Backend checks, optional IP-Adapter setup, scale
|
||||
overrides, and tokenizer usage validation remain in the common builder. A
|
||||
factory returns failure if any required additional backend cannot initialize.
|
||||
|
||||
LLM architecture selection, tokenizer requirements, prompt preparation, and
|
||||
encoding remain centralized in `src/conditioning/conditioner.hpp`. Integration
|
||||
factories instantiate the conditioner with the model version; models requiring
|
||||
new LLM conditioning behavior still need changes in that shared implementation.
|
||||
|
||||
`vae_kind` is the single source of truth for the VAE family; compatibility
|
||||
helpers derive their results from it. `KL`, `FLUX`, and `FLUX2` share the
|
||||
`AutoEncoderKL` implementation while retaining their distinct latent formats.
|
||||
The related traits have separate responsibilities:
|
||||
|
||||
- `supports_video_generation` describes the model's video generation capability.
|
||||
- `video_tae` selects `TinyVideoAutoEncoder` instead of `TinyImageAutoEncoder`.
|
||||
Image models can use a video TAE, so this is independent of video generation.
|
||||
- `fake_vae` selects FakeVAE for pixel-space models. It is separate from
|
||||
`vae_kind`, which also controls latent-format compatibility helpers.
|
||||
- `vae_format_override` allows PiD's VAE format override.
|
||||
|
||||
`configure_vae` handles model-specific KL adjustments such as
|
||||
PixArt's scale and SDXL's convolution scale. VAE compatibility traits also drive
|
||||
latent transformations and previews through the existing helper functions.
|
||||
|
||||
### Sampling And Geometry
|
||||
|
||||
`ModelSamplingPolicy` groups the defaults first: `default_prediction`,
|
||||
`default_flow_shift`, `default_sample_method`, and `default_scheduler`. Set only
|
||||
the values that differ from the struct defaults. `prediction_from_weights` and
|
||||
`flow_shift_from_weights` preserve checkpoint-dependent defaults during automatic
|
||||
prediction selection; `probe_v_prediction` enables SD2's runtime v-prediction
|
||||
probe. Explicit prediction settings take precedence over automatic selection.
|
||||
MOSS's default unconditional skip layer stays
|
||||
in the diffusion engine, with active unconditional SLG taking precedence.
|
||||
|
||||
The resolved `prediction_t` selects the denoiser directly. Flux3, MiniMax H3 AV,
|
||||
and Ming-Image use `FLUX3_FLOW_PRED`, `H3_AV_FLOW_PRED`, and
|
||||
`MING_IMAGE_FLOW_PRED`; their CLI overrides are `flux3_flow`, `h3_av_flow`, and
|
||||
`ming_image_flow`. Explicit `sd3_flow` always selects `DiscreteFlowDenoiser`,
|
||||
and `flux_flow` always selects `FluxFlowDenoiser`, regardless of model version.
|
||||
Automatic selection retains each model's default implementation. New prediction
|
||||
values are appended before `PREDICTION_COUNT`, preserving existing valid values;
|
||||
clients using the automatic-selection sentinel must rebuild with the new header.
|
||||
|
||||
`ModelGeometry` declares `diffusion_down_factor`, `latent_channels`, and
|
||||
`image_channels`. `ref_image_preset` defaults to `"default"`; set it only for models
|
||||
that need another reference-image preset.
|
||||
|
||||
The common pipeline executes these policies; specialized video, multimodal,
|
||||
cache, and extension algorithms remain in their existing runtime components.
|
||||
Model-specific diffusion extra parameters are assembled in the diffusion engine.
|
||||
Video frame conversion and alignment also remain in the diffusion engine.
|
||||
`get_vae_scale_factor()` retains PiD's pixel-space special case and otherwise
|
||||
uses the VAE's scale factor.
|
||||
|
||||
Validate migrations against the previous implementation using synthetic metadata,
|
||||
name conversions, prompt preparation, default/override selection, and focused
|
||||
builds. Real generation checks are still needed to validate numerical behavior
|
||||
when changing network or encoding algorithms.
|
||||
@@ -0,0 +1,37 @@
|
||||
# How to Use
|
||||
|
||||
MOSS-Image uses a 5B diffusion transformer, the FLUX.2 VAE, and Qwen3.5 9B as the LLM text encoder. Both Base and SFT are supported. Prompt enhancement is not included.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download MOSS-Image Base or SFT
|
||||
- safetensors: https://huggingface.co/Comfy-Org/MOSS-Image/tree/main/diffusion_models
|
||||
- gguf:
|
||||
- Base: https://huggingface.co/leejet/MOSS-Image-Base-GGUF/tree/main
|
||||
- SFT: https://huggingface.co/leejet/MOSS-Image-SFT-GGUF/tree/main
|
||||
- Download vae
|
||||
- safetensors: https://huggingface.co/Comfy-Org/MOSS-Image/tree/main/vae
|
||||
- Download Qwen3.5 9B
|
||||
- gguf: https://huggingface.co/unsloth/Qwen3.5-9B-GGUF/tree/main
|
||||
- Download `tokenizer.json`
|
||||
- https://huggingface.co/OpenMOSS-Team/MOSS-Image-Base/tree/main/tokenizer
|
||||
|
||||
Pass the external `tokenizer.json` with `--tokenizer`; Qwen3.5 uses a different vocabulary from the embedded Qwen tokenizer.
|
||||
|
||||
## Examples
|
||||
|
||||
### MOSS-Image Base
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\moss_image_base_int8_convrot_attn.safetensors --llm ..\models\text_encoders\Qwen3.5-9B-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors --tokenizer ..\models\tokenizers\tokenizer_qwen3.5.json --cfg-scale 3.0 -p "a lovely cat holding a sign says 'moss-image.cpp'" -v --offload-to-cpu -H 1024 -W 1024
|
||||
```
|
||||
|
||||
### MOSS-Image SFT
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\moss_image_sft_int8_convrot_attn.safetensors --llm ..\models\text_encoders\Qwen3.5-9B-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors --tokenizer ..\models\tokenizers\tokenizer_qwen3.5.json --cfg-scale 3.0 -p "a lovely cat holding a sign says 'moss-image.cpp'" -v --offload-to-cpu -H 1024 -W 1024
|
||||
```
|
||||
|
||||
<img src="../assets/moss/sft.png" />
|
||||
|
||||
Use dimensions divisible by 16. The default discrete scheduler uses the upstream time shift of 5. Use `--flow-shift` to override it. The negative CFG branch skips DiT layer 22 (zero-based), matching the upstream pipeline.
|
||||
@@ -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" />
|
||||
@@ -0,0 +1,60 @@
|
||||
# Position embedding cache
|
||||
|
||||
`sd::PECache` stores CPU-side `sd::Tensor<float>` data. Each model runner owns
|
||||
its cache and calls the existing `make_input()` on every graph build. Cache
|
||||
hits skip PE generation; allocation and upload remain ordinary graph-input
|
||||
operations. `GGMLRunner` has no PE-specific code or resource hooks.
|
||||
|
||||
The cache keeps the most recent key and tensor for each named PE input. A
|
||||
matching key returns the existing tensor by reference. Changing its parameters
|
||||
replaces that input's cached result. Different named inputs have independent
|
||||
storage. Alternating two layouts under the same name recomputes their PE;
|
||||
there is no LRU, device-buffer cache, memory budget, or graph lifecycle callback.
|
||||
CPU data lives with the model runner, including across `runner_end()` calls,
|
||||
and is released when its named entry is replaced or the cache is destroyed.
|
||||
|
||||
## Adding a model
|
||||
|
||||
Include `core/pe_cache.h` and declare a member:
|
||||
|
||||
```cpp
|
||||
sd::PECache pe_cache_;
|
||||
```
|
||||
|
||||
Construct the key before generating PE and place the generation code in the
|
||||
lazy callback:
|
||||
|
||||
```cpp
|
||||
sd::PECacheKey key("model.image_pe");
|
||||
key.add(height).add(width).add(head_dim).add(theta)
|
||||
.add(circular_x_enabled).add(circular_y_enabled);
|
||||
auto pe = make_input(pe_cache_.get_or_create(key, [&]() {
|
||||
auto values = generate_position_embedding(/* existing arguments */);
|
||||
return sd::Tensor<float>({2, 2, head_dim / 2, token_count}, std::move(values));
|
||||
}));
|
||||
```
|
||||
|
||||
Keys must cover every variable affecting PE values or shape: geometry, text
|
||||
length, frequency settings, reference-image ordering and offsets, circular
|
||||
settings, and video/audio positions or frame rates as applicable.
|
||||
`add_shape()` records all four ggml dimensions, including trailing singleton
|
||||
dimensions. `add_shapes()` records an ordered list of reference shapes.
|
||||
`add(sd::Tensor<T>)` records both shape and values for explicit position inputs.
|
||||
Keys compare their complete encoded fields rather than a data pointer or hash.
|
||||
|
||||
Use distinct names for PE inputs that coexist in a graph, such as Anima's
|
||||
adapter query and key PE. `make_input()` defers reading the CPU data until graph
|
||||
execution: do not replace a named entry while an unfinished graph references
|
||||
it. Adding or updating other named entries does not invalidate its storage.
|
||||
Return tensor shapes with the contiguous dimension first, and preserve each
|
||||
model's existing F32 values, layout, and final transformations.
|
||||
|
||||
## Current integrations
|
||||
|
||||
The cache is used by MiniT2I, the Flux runner and its variants, Flux3, Qwen Image,
|
||||
Qwen Image 2.1, Z-Image and L2P, Anima, Boogu, ERNIE Image, Fibo2, Ideogram4,
|
||||
Iris, Krea2, Lens, LLaDA Image, MAGE Flow, Ming Image, MOSS Image, PiD, PixArt,
|
||||
Wan, Hunyuan Video, LingBot Video, and LTX audio/video.
|
||||
|
||||
Position embeddings stored as model weights and graph-side RoPE operations
|
||||
remain on their existing paths, including MiniMax H3 and SenseNova U1.
|
||||
@@ -5,10 +5,15 @@ eg.:
|
||||
- flux 768x768 ~600mb
|
||||
- SD2 768x768 ~1400mb
|
||||
|
||||
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).
|
||||
`--flash-attn on|off|auto` controls flash attention. Its effect on speed depends
|
||||
on GPU architecture, backend, driver, model, and tensor shapes. Automatic
|
||||
selection uses backend rules and, for some paths, small probes; it does not
|
||||
guarantee the fastest end-to-end generation. Compare `on` and `off` with the
|
||||
same model and generation settings over repeated runs, separating startup/probe
|
||||
time from steady-state generation time. See [backend selection](backend.md)
|
||||
for automatic selection rules and per-module overrides.
|
||||
|
||||
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 +26,77 @@ 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.
|
||||
|
||||
## Tune direct convolution.
|
||||
|
||||
`--conv-direct on|off|auto` selects direct convolution or the im2col-based
|
||||
path. Performance depends on GPU architecture, backend, driver, model, and
|
||||
tensor shapes, so enabling direct convolution can either improve or reduce speed.
|
||||
|
||||
Automatic selection uses small benchmarks on Vulkan and implicit GEMM checks
|
||||
on other backends. It does not guarantee the fastest end-to-end generation.
|
||||
Compare `on` and `off` with the same model and generation settings over
|
||||
repeated runs, keeping flash attention unchanged and separating startup/probe
|
||||
time from steady-state generation time. See [backend selection](backend.md)
|
||||
for automatic selection rules and per-module overrides.
|
||||
|
||||
## 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 +139,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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
@@ -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" />
|
||||
|
||||
@@ -16,6 +16,8 @@ Qwen Image 2.1 supports text-to-image generation and image editing, using Qwen3-
|
||||
|
||||
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.
|
||||
@@ -30,6 +32,16 @@ Run the following commands from the build directory. Use image dimensions divisi
|
||||
|
||||
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`.
|
||||
@@ -40,6 +52,34 @@ Pass the reference image with `-r` and describe the edit in `-p`. Vision weights
|
||||
|
||||
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:
|
||||
|
||||
@@ -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" />
|
||||
@@ -131,7 +131,7 @@ If you want to allow connections from other machines on the network:
|
||||
|
||||
> **Drivers & Hardware:** Ensure the Server machine has the necessary drivers installed and functional (e.g., Nvidia Drivers for CUDA, Vulkan SDK, or Metal). If no devices are found, the server will simply fallback to CPU usage.
|
||||
|
||||
<!-- ### Step B: Check if the client is able to connect to the server and see the available devices
|
||||
### Step B: Check if the client is able to connect to the server and see the available devices
|
||||
|
||||
We're assuming the server is running on your local machine, and listening on the default port `50052`. If it's running on a different machine, you can replace `localhost` with the IP address of the server.
|
||||
|
||||
@@ -147,15 +147,15 @@ Example output:
|
||||
(Client built without GPU acceleration, two GPUs available on the server)
|
||||
|
||||
```
|
||||
List of available GGML devices:
|
||||
Name Description
|
||||
-------------------
|
||||
CPU AMD Ryzen 9 5900X 12-Core Processor
|
||||
RPC0 localhost:50052
|
||||
RPC1 localhost:50052
|
||||
``` -->
|
||||
```
|
||||
|
||||
### Step B: Run with RPC device
|
||||
The options can be given in either order; no model is required. The client exits
|
||||
with an error if RPC support is unavailable or a server cannot provide devices.
|
||||
|
||||
### Step C: Run with RPC device
|
||||
|
||||
If everything is working correctly, you can now run the client while offloading some or all of the work to the RPC server.
|
||||
|
||||
@@ -217,4 +217,4 @@ The client will map these servers to sequential device IDs (e.g., RPC0 from the
|
||||
|
||||
## 6. Performance Considerations
|
||||
|
||||
RPC performance is heavily dependent on network bandwidth, as large weights and activations must be transferred back and forth over the network, especially for large models, or when using high resolutions. For best results, ensure your network connection is stable and has sufficient bandwidth (>1Gbps recommended). This shoumd not be a concern if you are running the server and client on the same machine, as the data transfer will happen over the loopback interface.
|
||||
RPC performance is heavily dependent on network bandwidth, as large weights and activations must be transferred back and forth over the network, especially for large models, or when using high resolutions. For best results, ensure your network connection is stable and has sufficient bandwidth (>1Gbps recommended). This shoumd not be a concern if you are running the server and client on the same machine, as the data transfer will happen over the loopback interface.
|
||||
@@ -37,12 +37,12 @@ rebuild with CUDA 12.8 or newer to use SageAttention2++.
|
||||
|
||||
## Use
|
||||
|
||||
Replace `--diffusion-fa` with `--sage-attn` in an existing command. For example,
|
||||
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 --diffusion-fa --offload-to-cpu --video-frames 33 --sage-attn
|
||||
形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --offload-to-cpu --video-frames 33 --sage-attn
|
||||
```
|
||||
|
||||
SageAttention currently handles unmasked attention with head dimensions from
|
||||
@@ -51,9 +51,7 @@ 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 in diffusion
|
||||
when combined with `--fa` or `--diffusion-fa`; `--fa` continues to control other
|
||||
modules. Existing attention scaling overrides remain effective.
|
||||
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
|
||||
|
||||
@@ -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" />
|
||||
@@ -28,7 +28,6 @@ and the tokenizer files.
|
||||
--seed 42 \
|
||||
--sampling-method euler \
|
||||
--rng cuda \
|
||||
--fa \
|
||||
--output output.png
|
||||
```
|
||||
|
||||
|
||||
@@ -37,3 +37,17 @@ 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`.
|
||||
|
||||
`sd-server` accepts the same `--taesd` and `--taesd-preview-only` options; the preview method is selected per request instead of via `--preview`.
|
||||
@@ -40,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
|
||||
|
||||
@@ -40,6 +40,9 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/tree/main/split_files/diffusion_models
|
||||
- gguf: https://huggingface.co/QuantStack/Wan2.2-S2V-14B-GGUF/tree/main
|
||||
- int8_convrot safetensors: https://huggingface.co/noctrex/Wan2.2-S2V-14B-int8_convrot
|
||||
- Wan2.2 VACE-Fun A14B
|
||||
- safetensors: https://huggingface.co/alibaba-pai/Wan2.2-VACE-Fun-A14B
|
||||
- gguf: https://huggingface.co/QuantStack/Wan2.2-VACE-Fun-A14B-GGUF/tree/main
|
||||
- Download vae
|
||||
- wan_2.1_vae (for all the wan model except Wan2.2 TI2V 5B)
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/blob/main/split_files/vae/wan_2.1_vae.safetensors
|
||||
@@ -64,7 +67,7 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
|
||||
### 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>
|
||||
@@ -72,7 +75,7 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
|
||||
### 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>
|
||||
@@ -82,7 +85,7 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
|
||||
### 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>
|
||||
@@ -90,7 +93,7 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
|
||||
### 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>
|
||||
@@ -98,7 +101,7 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
|
||||
### 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>
|
||||
@@ -111,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:
|
||||
@@ -148,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" />
|
||||
@@ -156,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>
|
||||
@@ -168,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>
|
||||
@@ -176,15 +179,17 @@ 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>
|
||||
|
||||
TI2V 5B can decode multiple latent frames together when temporal tiling is enabled. For example, `--temporal-tiling --vae-tiling --vae-tile-size 256x256 --extra-tiling-args "temporal_tile_frames=4,temporal_decode_frames=2"` keeps up to four latent frames in each temporal tile and decodes up to two at once, with spatial tiles of 256×256 pixels. `temporal_decode_frames` defaults to `1`, preserving frame-by-frame decoding; other model versions ignore it. Increasing it requires more memory. Adjust the spatial tile size to fit the available memory.
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
|
||||
@@ -193,7 +198,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>
|
||||
@@ -203,7 +208,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>
|
||||
@@ -212,7 +217,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>
|
||||
@@ -223,7 +228,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>
|
||||
@@ -233,7 +238,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>
|
||||
@@ -242,7 +247,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>
|
||||
@@ -252,7 +257,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.
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -2,8 +2,10 @@ set(TARGET sd-cli)
|
||||
|
||||
add_executable(${TARGET}
|
||||
../common/common.cpp
|
||||
../common/generation_metadata.cpp
|
||||
../common/log.cpp
|
||||
../common/media_io.cpp
|
||||
../common/media_metadata.cpp
|
||||
image_metadata.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
@@ -11,6 +11,17 @@ 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.
|
||||
|
||||
`--conv-direct on|off|auto` also defaults to `auto`, enabling direct convolution
|
||||
on backends with implicit GEMM implementations. Use `--conv-direct auto,vae=off`
|
||||
for module overrides; supported modules are `diffusion`, `vae`, and `controlnet`.
|
||||
See [Direct convolution](../../docs/backend.md#direct-convolution---conv-direct-onoffauto).
|
||||
|
||||
For direct image repair or automatic post-generation YOLOv8 detection followed by cropped inpainting, see
|
||||
[ADetailer](../../docs/adetailer.md).
|
||||
|
||||
@@ -20,14 +31,21 @@ 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:
|
||||
Metadata mode inspects PNG, JPEG, WebP (including animated WebP), AVI, WebM, and Matroska
|
||||
container metadata without loading any model. Use `--image` for both images and videos:
|
||||
|
||||
```bash
|
||||
./bin/sd-cli -M metadata --image ./output.png
|
||||
./bin/sd-cli -M metadata --image ./output.jpg --metadata-format json
|
||||
./bin/sd-cli -M metadata --image ./output.webm
|
||||
./bin/sd-cli -M metadata --image ./output.avi --metadata-format json
|
||||
./bin/sd-cli -M metadata --image ./output.png --metadata-raw
|
||||
./bin/sd-cli -M metadata --image ./output.png --metadata-all
|
||||
```
|
||||
|
||||
For completely black or white images or videos, NaNs, and the `--linear-scale` /
|
||||
`--attn-scale` workaround, see [Troubleshooting](../../docs/troubleshooting.md).
|
||||
|
||||
Generation outputs embed the core [generation record](../../docs/generation_metadata.md).
|
||||
Images retain the native JSON and an additional WebUI-compatible infotext.
|
||||
`--disable-image-metadata` omits both from files.
|
||||
@@ -2,11 +2,14 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <codecvt>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <locale>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
@@ -276,7 +279,7 @@ namespace {
|
||||
}
|
||||
|
||||
bool read_file(const std::string& path, std::vector<uint8_t>& data, std::string& error) {
|
||||
std::ifstream fin(path, std::ios::binary);
|
||||
std::ifstream fin(std::filesystem::u8path(path), std::ios::binary);
|
||||
if (!fin) {
|
||||
error = "failed to open file: " + path;
|
||||
return false;
|
||||
@@ -390,8 +393,39 @@ namespace {
|
||||
json& report,
|
||||
std::string& error);
|
||||
|
||||
json parse_exif_user_comment(const uint8_t* data, size_t size) {
|
||||
if (size >= 8 && memcmp(data, "ASCII\0\0\0", 8) == 0) {
|
||||
return trim_trailing_nuls(bytes_to_string(data + 8, data + size));
|
||||
}
|
||||
if (size < 8 || memcmp(data, "UNICODE\0", 8) != 0) {
|
||||
return json{{"raw_hex_preview", hex_preview(data, size)}};
|
||||
}
|
||||
size_t offset = 8;
|
||||
// WebUI's piexif writer uses big-endian UTF-16 without a BOM.
|
||||
bool little_endian = false;
|
||||
if (size >= 10 && ((data[8] == 0xfe && data[9] == 0xff) || (data[8] == 0xff && data[9] == 0xfe))) {
|
||||
little_endian = data[8] == 0xff;
|
||||
offset = 10;
|
||||
}
|
||||
if ((size - offset) % 2 != 0) {
|
||||
return json{{"error", "truncated UTF-16 UserComment"}, {"raw_hex_preview", hex_preview(data, size)}};
|
||||
}
|
||||
std::u16string text;
|
||||
for (; offset < size; offset += 2) {
|
||||
text.push_back(static_cast<char16_t>(little_endian ? data[offset] | (uint16_t(data[offset + 1]) << 8)
|
||||
: (uint16_t(data[offset]) << 8) | data[offset + 1]));
|
||||
}
|
||||
try {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;
|
||||
return trim_trailing_nuls(converter.to_bytes(text));
|
||||
} catch (const std::range_error&) {
|
||||
return json{{"error", "invalid UTF-16 UserComment"}, {"raw_hex_preview", hex_preview(data, size)}};
|
||||
}
|
||||
}
|
||||
|
||||
json parse_exif_value(const std::vector<uint8_t>& data,
|
||||
size_t value_offset,
|
||||
uint16_t tag,
|
||||
uint16_t type,
|
||||
uint32_t count,
|
||||
bool little_endian,
|
||||
@@ -465,8 +499,9 @@ namespace {
|
||||
break;
|
||||
}
|
||||
case 7:
|
||||
value = bytes_to_string(data.data() + value_offset,
|
||||
data.data() + value_offset + count);
|
||||
value = tag == 0x9286 ? parse_exif_user_comment(data.data() + value_offset, count)
|
||||
: json(bytes_to_string(data.data() + value_offset,
|
||||
data.data() + value_offset + count));
|
||||
break;
|
||||
case 9: {
|
||||
if (count == 1) {
|
||||
@@ -590,6 +625,7 @@ namespace {
|
||||
|
||||
tag_json["value"] = parse_exif_value(data,
|
||||
value_offset,
|
||||
tag,
|
||||
type,
|
||||
value_count,
|
||||
little_endian,
|
||||
@@ -1097,6 +1133,178 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_avi_chunks(const std::vector<uint8_t>& data, size_t offset, size_t end, bool info, bool include_raw, json& result, std::string& error) {
|
||||
while (offset < end) {
|
||||
if (end - offset < 8) {
|
||||
error = "truncated AVI chunk header";
|
||||
return false;
|
||||
}
|
||||
const auto name = bytes_to_string(data.data() + offset, data.data() + offset + 4);
|
||||
const size_t size = read_u32_le(data, offset + 4);
|
||||
offset += 8;
|
||||
if (size > end - offset || (size & 1u) > end - offset - size) {
|
||||
error = "AVI chunk exceeds its container";
|
||||
return false;
|
||||
}
|
||||
json entry = {{"entry_type", "chunk"}, {"name", name}, {"length", size}, {"metadata_like", info}};
|
||||
if (name == "LIST") {
|
||||
if (size < 4) {
|
||||
error = "truncated AVI LIST type";
|
||||
return false;
|
||||
}
|
||||
const auto type = bytes_to_string(data.data() + offset, data.data() + offset + 4);
|
||||
entry["data"]["list_type"] = type;
|
||||
if (type == "INFO" && !info &&
|
||||
!parse_avi_chunks(data, offset + 4, offset + size, true, include_raw, result, error))
|
||||
return false;
|
||||
} else if (info) {
|
||||
entry["data"]["text"] = trim_trailing_nuls(bytes_to_string(data.data() + offset, data.data() + offset + size));
|
||||
} else {
|
||||
append_raw_preview(entry, data.data() + offset, size, include_raw);
|
||||
}
|
||||
result["entries"].push_back(std::move(entry));
|
||||
offset += size + (size & 1u);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_avi(const std::vector<uint8_t>& data, bool include_raw, json& result, std::string& error) {
|
||||
result = {{"format", "AVI"}, {"entries", json::array()}};
|
||||
size_t offset = 0;
|
||||
while (offset < data.size()) {
|
||||
if (data.size() - offset < 12 || memcmp(data.data() + offset, "RIFF", 4) != 0 ||
|
||||
memcmp(data.data() + offset + 8, offset == 0 ? "AVI " : "AVIX", 4) != 0) {
|
||||
error = "invalid AVI RIFF header";
|
||||
return false;
|
||||
}
|
||||
const size_t size = read_u32_le(data, offset + 4);
|
||||
if (size < 4 || size > data.size() - offset - 8 ||
|
||||
(size & 1u) > data.size() - offset - 8 - size) {
|
||||
error = "AVI RIFF size exceeds file size";
|
||||
return false;
|
||||
}
|
||||
if (!parse_avi_chunks(data, offset + 12, offset + 8 + size, false, include_raw, result, error))
|
||||
return false;
|
||||
offset += 8 + size + (size & 1u);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool read_ebml_uint(const std::vector<uint8_t>& data, size_t& offset, size_t end, bool id, uint64_t& value, bool& unknown) {
|
||||
if (offset == end || data[offset] == 0)
|
||||
return false;
|
||||
uint8_t mask = 0x80;
|
||||
size_t length = 1;
|
||||
while (!(data[offset] & mask)) {
|
||||
mask >>= 1;
|
||||
++length;
|
||||
}
|
||||
if (length > (id ? 4u : 8u) || length > end - offset)
|
||||
return false;
|
||||
value = data[offset++] & (id ? 0xff : mask - 1);
|
||||
for (size_t i = 1; i < length; ++i)
|
||||
value = (value << 8) | data[offset++];
|
||||
unknown = !id && value == (uint64_t(1) << (7 * length)) - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_webm_segment_child(uint64_t id) {
|
||||
return id == 0x114D9B74 || id == 0x1549A966 || id == 0x1654AE6B ||
|
||||
id == 0x1F43B675 || id == 0x1C53BB6B || id == 0x1941A469 ||
|
||||
id == 0x1043A770 || id == 0x1254C367;
|
||||
}
|
||||
|
||||
static bool parse_webm_elements(const std::vector<uint8_t>& data, size_t& offset, size_t end, uint64_t parent, bool unknown_cluster, int depth, bool include_raw, json& result, std::string& error) {
|
||||
if (depth > 16) {
|
||||
error = "WebM tag nesting exceeds limit";
|
||||
return false;
|
||||
}
|
||||
std::string tag_name;
|
||||
std::string tag_text;
|
||||
bool has_text = false;
|
||||
while (offset < end) {
|
||||
const auto start = offset;
|
||||
uint64_t id, size;
|
||||
bool unknown;
|
||||
if (!read_ebml_uint(data, offset, end, true, id, unknown)) {
|
||||
error = "invalid or truncated WebM element ID";
|
||||
return false;
|
||||
}
|
||||
// An unknown-size Cluster ends at the next Segment-level element.
|
||||
if (unknown_cluster && is_webm_segment_child(id)) {
|
||||
offset = start;
|
||||
return true;
|
||||
}
|
||||
if (!read_ebml_uint(data, offset, end, false, size, unknown) ||
|
||||
(!unknown && size > end - offset)) {
|
||||
error = "invalid or truncated WebM element size";
|
||||
return false;
|
||||
}
|
||||
if (unknown && id != 0x18538067 && id != 0x1F43B675) {
|
||||
error = "unsupported unknown-size WebM element";
|
||||
return false;
|
||||
}
|
||||
const size_t stop = unknown ? end : offset + static_cast<size_t>(size);
|
||||
const bool container = (parent == 0 && (id == 0x1A45DFA3 || id == 0x18538067)) ||
|
||||
(parent == 0x18538067 && (id == 0x1254C367 || (id == 0x1F43B675 && unknown))) ||
|
||||
(parent == 0x1254C367 && id == 0x7373) ||
|
||||
((parent == 0x7373 || parent == 0x67C8) && id == 0x67C8);
|
||||
if (parent == 0 && id == 0x18538067)
|
||||
result["has_segment"] = true;
|
||||
if (parent == 0x1A45DFA3 && id == 0x4282) {
|
||||
const auto type = bytes_to_string(data.data() + offset, data.data() + stop);
|
||||
if (type != "webm" && type != "matroska") {
|
||||
error = "EBML document is not WebM or Matroska";
|
||||
return false;
|
||||
}
|
||||
result["doc_type"] = type;
|
||||
result["format"] = type == "webm" ? "WEBM" : "MATROSKA";
|
||||
}
|
||||
if (parent == 0x67C8 && (id == 0x45A3 || id == 0x4487)) {
|
||||
auto value = bytes_to_string(data.data() + offset, data.data() + stop);
|
||||
if (id == 0x45A3)
|
||||
tag_name = std::move(value);
|
||||
else {
|
||||
tag_text = std::move(value);
|
||||
has_text = true;
|
||||
}
|
||||
}
|
||||
if (parent == 0 || parent == 0x18538067) {
|
||||
std::ostringstream name;
|
||||
name << "0x" << std::hex << std::uppercase << id;
|
||||
json entry = {{"entry_type", "element"}, {"name", name.str()}, {"length", stop - offset}, {"metadata_like", false}};
|
||||
if (unknown)
|
||||
entry["unknown_size"] = true;
|
||||
append_raw_preview(entry, data.data() + offset, stop - offset, include_raw);
|
||||
result["entries"].push_back(std::move(entry));
|
||||
}
|
||||
if (container) {
|
||||
if (!parse_webm_elements(data, offset, stop, id, id == 0x1F43B675 && unknown,
|
||||
depth + 1, include_raw, result, error))
|
||||
return false;
|
||||
} else {
|
||||
offset = stop;
|
||||
}
|
||||
}
|
||||
if (parent == 0x67C8 && has_text) {
|
||||
result["entries"].push_back({{"entry_type", "tag"}, {"name", tag_name}, {"metadata_like", true}, {"data", {{"text", tag_text}}}});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_webm(const std::vector<uint8_t>& data, bool include_raw, json& result, std::string& error) {
|
||||
result = {{"format", "WEBM"}, {"entries", json::array()}};
|
||||
size_t offset = 0;
|
||||
if (!parse_webm_elements(data, offset, data.size(), 0, false, 0, include_raw, result, error))
|
||||
return false;
|
||||
if (!result.contains("doc_type") || !result.value("has_segment", false)) {
|
||||
error = "missing WebM document type or Segment";
|
||||
return false;
|
||||
}
|
||||
result.erase("has_segment");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string abbreviate(const std::string& value, bool brief) {
|
||||
if (!brief || value.size() <= 240) {
|
||||
return value;
|
||||
@@ -1115,7 +1323,7 @@ namespace {
|
||||
return;
|
||||
}
|
||||
if (value.is_primitive()) {
|
||||
out << prefix << key << ": " << value.dump() << "\n";
|
||||
out << prefix << key << ": " << value.dump(-1, ' ', false, json::error_handler_t::replace) << "\n";
|
||||
return;
|
||||
}
|
||||
if (value.is_array()) {
|
||||
@@ -1124,7 +1332,7 @@ namespace {
|
||||
if (item.is_string()) {
|
||||
out << prefix << " - " << abbreviate(item.get<std::string>(), brief) << "\n";
|
||||
} else if (item.is_primitive()) {
|
||||
out << prefix << " - " << item.dump() << "\n";
|
||||
out << prefix << " - " << item.dump(-1, ' ', false, json::error_handler_t::replace) << "\n";
|
||||
} else {
|
||||
out << prefix << " -\n";
|
||||
for (auto it = item.begin(); it != item.end(); ++it) {
|
||||
@@ -1157,10 +1365,12 @@ namespace {
|
||||
}
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
const bool is_chunk = entry.value("entry_type", "") == "chunk";
|
||||
const std::string name = is_chunk ? entry.value("name", "unknown")
|
||||
: entry.value("marker", "unknown");
|
||||
out << (is_chunk ? "Chunk: " : "Segment: ") << name << "\n";
|
||||
const auto type = entry.value("entry_type", "");
|
||||
const auto name = entry.value("name", entry.value("marker", "unknown"));
|
||||
const char* label = type == "chunk" ? "Chunk" : type == "tag" ? "Tag"
|
||||
: type == "element" ? "Element"
|
||||
: "Segment";
|
||||
out << label << ": " << name << "\n";
|
||||
for (auto it = entry.begin(); it != entry.end(); ++it) {
|
||||
if (it.key() == "entry_type" || it.key() == "name" || it.key() == "marker" ||
|
||||
it.key() == "metadata_like") {
|
||||
@@ -1210,7 +1420,13 @@ namespace {
|
||||
return parse_webp(data, include_raw, report, error);
|
||||
}
|
||||
|
||||
error = "unsupported image format; only PNG, JPEG, and WebP are supported";
|
||||
if (data.size() >= 12 && memcmp(data.data(), "RIFF", 4) == 0 &&
|
||||
memcmp(data.data() + 8, "AVI ", 4) == 0)
|
||||
return parse_avi(data, include_raw, report, error);
|
||||
if (data.size() >= 4 && read_u32_be(data, 0) == 0x1A45DFA3)
|
||||
return parse_webm(data, include_raw, report, error);
|
||||
|
||||
error = "unsupported media format; only PNG, JPEG, WebP, AVI, WebM, and Matroska are supported";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1229,7 +1445,7 @@ bool print_image_metadata(const std::string& image_path,
|
||||
|
||||
if (options.output_format == MetadataOutputFormat::JSON) {
|
||||
visible_report["file"] = image_path;
|
||||
out << visible_report.dump(2) << "\n";
|
||||
out << visible_report.dump(2, ' ', false, json::error_handler_t::replace) << "\n";
|
||||
} else {
|
||||
print_text_report(image_path, visible_report, options.include_structural, options.brief, out);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ struct SDCliParams {
|
||||
int preview_interval = 1;
|
||||
std::string preview_path = "preview.png";
|
||||
int preview_fps = 16;
|
||||
bool taesd_preview = false;
|
||||
bool preview_noisy = false;
|
||||
bool color = false;
|
||||
bool metadata_raw = false;
|
||||
@@ -70,7 +69,7 @@ struct SDCliParams {
|
||||
&output_path},
|
||||
{"",
|
||||
"--image",
|
||||
"path to the image to inspect (for metadata mode)",
|
||||
"path to the image or video to inspect (for metadata mode)",
|
||||
0,
|
||||
&image_path},
|
||||
{"",
|
||||
@@ -114,10 +113,6 @@ struct SDCliParams {
|
||||
"--color",
|
||||
"colors the logging tags according to level",
|
||||
true, &color},
|
||||
{"",
|
||||
"--taesd-preview-only",
|
||||
std::string("prevents usage of taesd for decoding the final image. (for use with --preview ") + previews_str[PREVIEW_TAE] + ")",
|
||||
true, &taesd_preview},
|
||||
{"",
|
||||
"--preview-noisy",
|
||||
"enables previewing noisy inputs of the models rather than the denoised outputs",
|
||||
@@ -188,7 +183,7 @@ struct SDCliParams {
|
||||
if (++index >= argc) {
|
||||
return -1;
|
||||
}
|
||||
imatrix_in.push_back(argv[index]);
|
||||
imatrix_in.push_back(argv_to_utf8(index, argv));
|
||||
return 1;
|
||||
};
|
||||
|
||||
@@ -232,7 +227,7 @@ struct SDCliParams {
|
||||
}
|
||||
} else {
|
||||
if (image_path.empty()) {
|
||||
LOG_ERROR("error: metadata mode needs an image path (--image)");
|
||||
LOG_ERROR("error: metadata mode needs an image or video path (--image)");
|
||||
return false;
|
||||
}
|
||||
if (metadata_format != "text" && metadata_format != "json") {
|
||||
@@ -268,7 +263,6 @@ struct SDCliParams {
|
||||
<< " preview_interval: " << preview_interval << ",\n"
|
||||
<< " preview_path: \"" << preview_path << "\",\n"
|
||||
<< " preview_fps: " << preview_fps << ",\n"
|
||||
<< " taesd_preview: " << (taesd_preview ? "true" : "false") << ",\n"
|
||||
<< " preview_noisy: " << (preview_noisy ? "true" : "false") << ",\n"
|
||||
<< " imatrix_out: \"" << imatrix_out << "\",\n"
|
||||
<< " metadata_raw: " << (metadata_raw ? "true" : "false") << ",\n"
|
||||
@@ -301,6 +295,10 @@ void parse_args(int argc, const char** argv, SDCliParams& cli_params, SDContextP
|
||||
log_level = cli_params.log_level;
|
||||
log_color = cli_params.color;
|
||||
|
||||
if (ctx_params.list_devices) {
|
||||
exit(ctx_params.print_devices() ? 0 : 1);
|
||||
}
|
||||
|
||||
bool valid = cli_params.resolve_and_validate();
|
||||
if (valid && cli_params.mode != METADATA) {
|
||||
valid = ctx_params.resolve_and_validate(cli_params.mode) &&
|
||||
@@ -323,13 +321,14 @@ 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 max_image_num = 0) {
|
||||
if (!fs::exists(dir) || !fs::is_directory(dir)) {
|
||||
const fs::path dir_path = fs::u8path(dir);
|
||||
if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) {
|
||||
LOG_ERROR("'%s' is not a valid directory\n", dir.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<fs::directory_entry> entries;
|
||||
for (const auto& entry : fs::directory_iterator(dir)) {
|
||||
for (const auto& entry : fs::directory_iterator(dir_path)) {
|
||||
if (entry.is_regular_file()) {
|
||||
entries.push_back(entry);
|
||||
}
|
||||
@@ -337,12 +336,12 @@ bool load_images_from_dir(const std::string dir,
|
||||
|
||||
std::sort(entries.begin(), entries.end(),
|
||||
[](const fs::directory_entry& a, const fs::directory_entry& b) {
|
||||
return a.path().filename().string() < b.path().filename().string();
|
||||
return a.path().filename().u8string() < b.path().filename().u8string();
|
||||
});
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
std::string path = entry.path().string();
|
||||
std::string ext = entry.path().extension().string();
|
||||
std::string path = entry.path().u8string();
|
||||
std::string ext = entry.path().extension().u8string();
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
|
||||
|
||||
if (ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp" || ext == ".webp") {
|
||||
@@ -397,19 +396,19 @@ void step_callback(int step, int frame_count, sd_image_t* image, bool is_noisy,
|
||||
// is_noisy is set to true if the preview corresponds to noisy latents, false if it's denoised latents
|
||||
// unused in this app, it will either be always noisy or always denoised here
|
||||
if (frame_count == 1) {
|
||||
fs::path path = cli_params->preview_path;
|
||||
if (encoded_image_format_from_path(path.string()) == EncodedImageFormat::UNKNOWN)
|
||||
fs::path path = fs::u8path(cli_params->preview_path);
|
||||
if (encoded_image_format_from_path(path.u8string()) == EncodedImageFormat::UNKNOWN)
|
||||
path += ".png";
|
||||
if (std::regex_search(path.string(), format_specifier_regex))
|
||||
path = fs::path(format_frame_idx(path.string(), continuous_preview_counter++));
|
||||
if (!write_image_to_file(path.string(),
|
||||
if (std::regex_search(path.u8string(), format_specifier_regex))
|
||||
path = fs::u8path(format_frame_idx(path.u8string(), continuous_preview_counter++));
|
||||
if (!write_image_to_file(path.u8string(),
|
||||
image->data,
|
||||
image->width,
|
||||
image->height,
|
||||
image->channel,
|
||||
"",
|
||||
cli_params->compression_quality)) {
|
||||
LOG_ERROR("save preview image to '%s' failed", path.string().c_str());
|
||||
LOG_ERROR("save preview image to '%s' failed", path.u8string().c_str());
|
||||
}
|
||||
} else {
|
||||
int fps = cli_params->preview_method == PREVIEW_PROJ ? cli_params->preview_fps / 4 : cli_params->preview_fps;
|
||||
@@ -420,12 +419,12 @@ void step_callback(int step, int frame_count, sd_image_t* image, bool is_noisy,
|
||||
}
|
||||
|
||||
static fs::path get_video_audio_sidecar_path(const SDCliParams& cli_params) {
|
||||
fs::path out_path = cli_params.output_path;
|
||||
fs::path out_path = fs::u8path(cli_params.output_path);
|
||||
fs::path base_path = out_path;
|
||||
fs::path ext = out_path.has_extension() ? out_path.extension() : fs::path{};
|
||||
std::string ext_lower = ext.string();
|
||||
std::string ext_lower = ext.u8string();
|
||||
std::transform(ext_lower.begin(), ext_lower.end(), ext_lower.begin(), ::tolower);
|
||||
const EncodedImageFormat output_format = encoded_image_format_from_path(out_path.string());
|
||||
const EncodedImageFormat output_format = encoded_image_format_from_path(out_path.u8string());
|
||||
if (!ext.empty()) {
|
||||
if (output_format == EncodedImageFormat::JPEG ||
|
||||
output_format == EncodedImageFormat::PNG ||
|
||||
@@ -442,22 +441,23 @@ static fs::path get_video_audio_sidecar_path(const SDCliParams& cli_params) {
|
||||
bool save_results(const SDCliParams& cli_params,
|
||||
const SDContextParams& ctx_params,
|
||||
const SDGenerationParams& gen_params,
|
||||
sd_image_t* results,
|
||||
SDImageVec& results,
|
||||
int num_results,
|
||||
const sd_audio_t* generated_audio = nullptr) {
|
||||
if (results == nullptr || num_results <= 0) {
|
||||
const sd_audio_t* generated_audio,
|
||||
const std::string& video_info) {
|
||||
if (results.empty() || num_results <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
fs::path out_path = cli_params.output_path;
|
||||
fs::path out_path = fs::u8path(cli_params.output_path);
|
||||
|
||||
if (!out_path.parent_path().empty()) {
|
||||
std::error_code ec;
|
||||
fs::create_directories(out_path.parent_path(), ec);
|
||||
if (ec) {
|
||||
LOG_ERROR("failed to create directory '%s': %s",
|
||||
out_path.parent_path().string().c_str(), ec.message().c_str());
|
||||
out_path.parent_path().u8string().c_str(), ec.message().c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -465,9 +465,9 @@ bool save_results(const SDCliParams& cli_params,
|
||||
fs::path base_path = out_path;
|
||||
fs::path ext = out_path.has_extension() ? out_path.extension() : fs::path{};
|
||||
|
||||
std::string ext_lower = ext.string();
|
||||
std::string ext_lower = ext.u8string();
|
||||
std::transform(ext_lower.begin(), ext_lower.end(), ext_lower.begin(), ::tolower);
|
||||
const EncodedImageFormat output_format = encoded_image_format_from_path(out_path.string());
|
||||
const EncodedImageFormat output_format = encoded_image_format_from_path(out_path.u8string());
|
||||
if (!ext.empty()) {
|
||||
if (output_format == EncodedImageFormat::JPEG ||
|
||||
output_format == EncodedImageFormat::PNG ||
|
||||
@@ -488,13 +488,11 @@ bool save_results(const SDCliParams& cli_params,
|
||||
if (!img.data)
|
||||
return false;
|
||||
|
||||
int images_per_batch = gen_params.batch_count > 0 ? std::max(1, num_results / gen_params.batch_count) : 1;
|
||||
const int64_t metadata_seed = cli_params.mode == VID_GEN ? gen_params.seed : gen_params.seed + idx / images_per_batch;
|
||||
std::string params = gen_params.embed_image_metadata
|
||||
? get_image_params(ctx_params, gen_params, metadata_seed, cli_params.mode)
|
||||
: "";
|
||||
const bool ok = write_image_to_file(path.string(), img.data, img.width, img.height, img.channel, params, cli_params.compression_quality);
|
||||
LOG_INFO("save result image %d to '%s' (%s)", idx, path.string().c_str(), ok ? "success" : "failure");
|
||||
std::string params = gen_params.embed_image_metadata
|
||||
? results.generation_info(idx)
|
||||
: "";
|
||||
const bool ok = write_image_to_file(path.u8string(), img.data, img.width, img.height, img.channel, params, cli_params.compression_quality);
|
||||
LOG_INFO("save result image %d to '%s' (%s)", idx, path.u8string().c_str(), ok ? "success" : "failure");
|
||||
return ok;
|
||||
};
|
||||
|
||||
@@ -502,14 +500,14 @@ bool save_results(const SDCliParams& cli_params,
|
||||
if (generated_audio == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (write_wav_to_file(wav_path.string(),
|
||||
if (write_wav_to_file(wav_path.u8string(),
|
||||
generated_audio->data,
|
||||
generated_audio->sample_count,
|
||||
generated_audio->channels,
|
||||
generated_audio->sample_rate)) {
|
||||
LOG_INFO("save result audio to '%s'", wav_path.string().c_str());
|
||||
LOG_INFO("save result audio to '%s'", wav_path.u8string().c_str());
|
||||
} else {
|
||||
LOG_WARN("failed to save result audio to '%s'", wav_path.string().c_str());
|
||||
LOG_WARN("failed to save result audio to '%s'", wav_path.u8string().c_str());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -522,7 +520,7 @@ bool save_results(const SDCliParams& cli_params,
|
||||
pattern += ext;
|
||||
|
||||
for (int i = 0; i < num_results; ++i) {
|
||||
fs::path img_path = format_frame_idx(pattern.string(), output_begin_idx + i);
|
||||
fs::path img_path = fs::u8path(format_frame_idx(pattern.u8string(), output_begin_idx + i));
|
||||
if (write_image(img_path, i)) {
|
||||
sucessful_reults++;
|
||||
}
|
||||
@@ -535,16 +533,16 @@ bool save_results(const SDCliParams& cli_params,
|
||||
if (ext_lower != ".avi" && ext_lower != ".webp" && ext_lower != ".webm")
|
||||
ext = ".avi";
|
||||
std::string params = gen_params.embed_image_metadata
|
||||
? get_image_params(ctx_params, gen_params, gen_params.seed, cli_params.mode)
|
||||
? video_info
|
||||
: "";
|
||||
|
||||
fs::path video_path = base_path;
|
||||
video_path += ext;
|
||||
std::string final_ext_lower = ext.string();
|
||||
std::string final_ext_lower = ext.u8string();
|
||||
std::transform(final_ext_lower.begin(), final_ext_lower.end(), final_ext_lower.begin(), ::tolower);
|
||||
const bool mux_audio = generated_audio != nullptr && (final_ext_lower == ".avi" || final_ext_lower == ".webm");
|
||||
if (create_video_from_sd_images(video_path.string().c_str(), results, num_results, gen_params.fps, cli_params.compression_quality, mux_audio ? generated_audio : nullptr, params) == 0) {
|
||||
LOG_INFO("save result video to '%s'", video_path.string().c_str());
|
||||
if (create_video_from_sd_images(video_path.u8string().c_str(), results.data(), num_results, gen_params.fps, cli_params.compression_quality, mux_audio ? generated_audio : nullptr, params) == 0) {
|
||||
LOG_INFO("save result video to '%s'", video_path.u8string().c_str());
|
||||
if (generated_audio != nullptr && !mux_audio) {
|
||||
fs::path wav_path = video_path;
|
||||
wav_path.replace_extension(".wav");
|
||||
@@ -552,7 +550,7 @@ bool save_results(const SDCliParams& cli_params,
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
LOG_ERROR("Failed to save result video to '%s'", video_path.string().c_str());
|
||||
LOG_ERROR("Failed to save result video to '%s'", video_path.u8string().c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -608,30 +606,29 @@ static bool apply_adetailer(sd_ctx_t* sd_ctx,
|
||||
continue;
|
||||
}
|
||||
sd_img_gen_params_t ad_generation_params = img_gen_params;
|
||||
ad_generation_params.seed = img_gen_params.seed + i;
|
||||
ad_generation_params.seed = generation_seed(results.generation_info(i), img_gen_params.seed);
|
||||
if (mode == IMG_GEN) {
|
||||
ad_generation_params.width = 512;
|
||||
ad_generation_params.height = 512;
|
||||
ad_generation_params.strength = 0.4f;
|
||||
}
|
||||
sd_image_t* detailed_images = nullptr;
|
||||
int detailed_count = 0;
|
||||
sd_image_generation_result_t* detailed = nullptr;
|
||||
if (!adetail_image(ad_ctx.get(),
|
||||
sd_ctx,
|
||||
results[i],
|
||||
&ad_params,
|
||||
&ad_generation_params,
|
||||
&detailed_images,
|
||||
&detailed_count) ||
|
||||
detailed_count <= 0 || detailed_images == nullptr || detailed_images[0].data == nullptr) {
|
||||
free_sd_images(detailed_images, detailed_count);
|
||||
&detailed) ||
|
||||
detailed == nullptr || detailed->count <= 0 || detailed->items[0].image.data == nullptr) {
|
||||
free_sd_image_generation_result(detailed);
|
||||
LOG_ERROR("ADetailer failed for image %d", i + 1);
|
||||
return false;
|
||||
}
|
||||
results.append_generation_info(i, detailed->items[0].generation_info, ctx_params.lora_model_dir);
|
||||
free(results[i].data);
|
||||
results[i] = detailed_images[0];
|
||||
detailed_images[0] = {0, 0, 0, nullptr};
|
||||
free_sd_images(detailed_images, detailed_count);
|
||||
results[i] = detailed->items[0].image;
|
||||
detailed->items[0].image = {};
|
||||
free_sd_image_generation_result(detailed);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -696,12 +693,12 @@ int main(int argc, const char* argv[]) {
|
||||
|
||||
LOG_VERBOSE("version: %s", version_string().c_str());
|
||||
LOG_VERBOSE("%s", sd_get_system_info());
|
||||
LOG_VERBOSE("%s", cli_params.to_string().c_str());
|
||||
LOG_VERBOSE("%s", ctx_params.to_string().c_str());
|
||||
LOG_VERBOSE("%s", gen_params.to_string().c_str());
|
||||
LOG_DEBUG("%s", cli_params.to_string().c_str());
|
||||
LOG_DEBUG("%s", ctx_params.to_string().c_str());
|
||||
LOG_DEBUG("%s", gen_params.to_string().c_str());
|
||||
|
||||
if (!cli_params.imatrix_out.empty()) {
|
||||
if (fs::exists(cli_params.imatrix_out) &&
|
||||
if (fs::exists(fs::u8path(cli_params.imatrix_out)) &&
|
||||
std::find(cli_params.imatrix_in.begin(), cli_params.imatrix_in.end(), cli_params.imatrix_out) == cli_params.imatrix_in.end()) {
|
||||
LOG_WARN("imatrix file '%s' already exists and will be overwritten", cli_params.imatrix_out.c_str());
|
||||
}
|
||||
@@ -716,6 +713,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(),
|
||||
@@ -726,7 +732,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(),
|
||||
@@ -869,11 +877,12 @@ int main(int argc, const char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(cli_params.taesd_preview);
|
||||
sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t();
|
||||
|
||||
SDImageVec results;
|
||||
int num_results = 0;
|
||||
sd_audio_t* generated_audio = nullptr;
|
||||
std::string video_info;
|
||||
|
||||
if (cli_params.mode == UPSCALE) {
|
||||
num_results = 1;
|
||||
@@ -886,18 +895,6 @@ int main(int argc, const char* argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (gen_params.sample_params.sample_method == SAMPLE_METHOD_COUNT) {
|
||||
gen_params.sample_params.sample_method = sd_get_default_sample_method(sd_ctx.get());
|
||||
}
|
||||
|
||||
if (gen_params.high_noise_sample_params.sample_method == SAMPLE_METHOD_COUNT) {
|
||||
gen_params.high_noise_sample_params.sample_method = sd_get_default_sample_method(sd_ctx.get());
|
||||
}
|
||||
|
||||
if (gen_params.sample_params.scheduler == SCHEDULER_COUNT) {
|
||||
gen_params.sample_params.scheduler = sd_get_default_scheduler(sd_ctx.get(), gen_params.sample_params.sample_method);
|
||||
}
|
||||
|
||||
sd_img_gen_params_t img_gen_params{};
|
||||
const bool use_img_gen_params = cli_params.mode == IMG_GEN || cli_params.mode == ADETAILER;
|
||||
if (use_img_gen_params) {
|
||||
@@ -905,23 +902,24 @@ int main(int argc, const char* argv[]) {
|
||||
}
|
||||
|
||||
if (cli_params.mode == IMG_GEN) {
|
||||
sd_image_t* generated_images = nullptr;
|
||||
if (!generate_image(sd_ctx.get(), &img_gen_params, &generated_images, &num_results)) {
|
||||
generated_images = nullptr;
|
||||
num_results = 0;
|
||||
}
|
||||
results.adopt(generated_images, num_results);
|
||||
sd_image_generation_result_t* generated = nullptr;
|
||||
generate_image(sd_ctx.get(), &img_gen_params, &generated);
|
||||
results.adopt(generated, ctx_params.lora_model_dir);
|
||||
num_results = results.count();
|
||||
} else if (cli_params.mode == ADETAILER) {
|
||||
num_results = 1;
|
||||
results.push_back(gen_params.init_image.release());
|
||||
} else if (cli_params.mode == VID_GEN) {
|
||||
sd_vid_gen_params_t vid_gen_params = gen_params.to_sd_vid_gen_params_t();
|
||||
sd_image_t* generated_video = nullptr;
|
||||
if (!generate_video(sd_ctx.get(), &vid_gen_params, &generated_video, &num_results, &generated_audio, &cli_params.preview_fps)) {
|
||||
generated_video = nullptr;
|
||||
sd_vid_gen_params_t vid_gen_params = gen_params.to_sd_vid_gen_params_t();
|
||||
sd_video_generation_result_t* generated = nullptr;
|
||||
if (generate_video(sd_ctx.get(), &vid_gen_params, &generated)) {
|
||||
gen_params.fps = cli_params.preview_fps = generated->fps;
|
||||
generated_audio = generated->audio;
|
||||
generated->audio = nullptr;
|
||||
num_results = generated->frame_count;
|
||||
results.adopt_video(generated, ctx_params.lora_model_dir);
|
||||
video_info = results.generation_info(0);
|
||||
}
|
||||
gen_params.fps = cli_params.preview_fps;
|
||||
results.adopt(generated_video, num_results);
|
||||
}
|
||||
|
||||
if (!results) {
|
||||
@@ -961,21 +959,20 @@ int main(int argc, const char* argv[]) {
|
||||
SDImageOwner current_image(results[i]);
|
||||
results[i] = {0, 0, 0, nullptr};
|
||||
for (int u = 0; u < gen_params.upscale_repeats; ++u) {
|
||||
sd_image_t* upscaled_images = nullptr;
|
||||
int upscaled_count = 0;
|
||||
bool upscale_ok = upscale(upscaler_ctx.get(),
|
||||
current_image.get(),
|
||||
upscale_factor,
|
||||
&upscaled_images,
|
||||
&upscaled_count);
|
||||
if (!upscale_ok || upscaled_count <= 0 || upscaled_images[0].data == nullptr) {
|
||||
free_sd_images(upscaled_images, upscaled_count);
|
||||
sd_image_generation_result_t* upscaled = nullptr;
|
||||
bool upscale_ok = upscale(upscaler_ctx.get(),
|
||||
current_image.get(),
|
||||
upscale_factor,
|
||||
&upscaled);
|
||||
if (!upscale_ok || !upscaled || upscaled->count <= 0 || upscaled->items[0].image.data == nullptr) {
|
||||
free_sd_image_generation_result(upscaled);
|
||||
LOG_ERROR("upscale failed");
|
||||
break;
|
||||
}
|
||||
sd_image_t upscaled_image = upscaled_images[0];
|
||||
upscaled_images[0] = {0, 0, 0, nullptr};
|
||||
free_sd_images(upscaled_images, upscaled_count);
|
||||
results.append_generation_info(i, upscaled->items[0].generation_info);
|
||||
sd_image_t upscaled_image = upscaled->items[0].image;
|
||||
upscaled->items[0].image = {};
|
||||
free_sd_image_generation_result(upscaled);
|
||||
current_image.reset(upscaled_image);
|
||||
}
|
||||
results[i] = current_image.release(); // Set the final upscaled image as the result
|
||||
@@ -983,7 +980,9 @@ int main(int argc, const char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!save_results(cli_params, ctx_params, gen_params, results.data(), num_results, generated_audio)) {
|
||||
if (!video_info.empty() && results.count() > 0)
|
||||
video_info = results.generation_info(0);
|
||||
if (!save_results(cli_params, ctx_params, gen_params, results, num_results, generated_audio, video_info)) {
|
||||
free_sd_audio(generated_audio);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef __EXAMPLES_COMMON_COMMON_H__
|
||||
#define __EXAMPLES_COMMON_COMMON_H__
|
||||
|
||||
#include "generation_metadata.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
@@ -12,6 +14,10 @@
|
||||
#include "resource_owners.hpp"
|
||||
#include "stable-diffusion.h"
|
||||
|
||||
// The command-line argument at `index` as UTF-8 (on Windows, read again from
|
||||
// the UTF-16 command line: argv itself is in the ANSI code page).
|
||||
std::string argv_to_utf8(int index, const char** argv);
|
||||
|
||||
#define SAFE_STR(s) ((s) ? (s) : "")
|
||||
#define BOOL_STR(b) ((b) ? "true" : "false")
|
||||
|
||||
@@ -157,23 +163,26 @@ 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;
|
||||
std::string model_args;
|
||||
bool auto_fit = true;
|
||||
std::string rpc_servers;
|
||||
bool list_devices = false;
|
||||
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 enable_mmap = false;
|
||||
bool control_net_cpu = false;
|
||||
bool clip_on_cpu = false;
|
||||
bool vae_on_cpu = false;
|
||||
std::string flash_attn_mode = "auto";
|
||||
std::string conv_direct_mode; // Empty selects auto while honoring legacy convolution flags.
|
||||
bool sage_attn = false;
|
||||
bool diffusion_conv_direct = false;
|
||||
bool vae_conv_direct = false;
|
||||
bool taesd_preview_only = false;
|
||||
|
||||
prediction_t prediction = PREDICTION_COUNT;
|
||||
lora_apply_mode_t lora_apply_mode = LORA_APPLY_AUTO;
|
||||
@@ -184,13 +193,14 @@ struct SDContextParams {
|
||||
|
||||
float flow_shift = INFINITY;
|
||||
ArgOptions get_options();
|
||||
bool print_devices() const;
|
||||
void build_embedding_map();
|
||||
void prepare_backend_assignments();
|
||||
bool resolve(SDMode mode);
|
||||
bool validate(SDMode mode);
|
||||
bool resolve_and_validate(SDMode mode);
|
||||
std::string to_string() const;
|
||||
sd_ctx_params_t to_sd_ctx_params_t(bool taesd_preview);
|
||||
sd_ctx_params_t to_sd_ctx_params_t();
|
||||
};
|
||||
|
||||
struct SDGenerationParams {
|
||||
@@ -313,12 +323,12 @@ struct SDGenerationParams {
|
||||
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,
|
||||
@@ -330,13 +340,4 @@ struct SDGenerationParams {
|
||||
};
|
||||
|
||||
std::string version_string();
|
||||
std::string build_sdcpp_image_metadata_json(const SDContextParams& ctx_params,
|
||||
const SDGenerationParams& gen_params,
|
||||
int64_t seed,
|
||||
SDMode mode = IMG_GEN);
|
||||
std::string get_image_params(const SDContextParams& ctx_params,
|
||||
const SDGenerationParams& gen_params,
|
||||
int64_t seed,
|
||||
SDMode mode = IMG_GEN);
|
||||
|
||||
#endif // __EXAMPLES_COMMON_COMMON_H__
|
||||
@@ -0,0 +1,155 @@
|
||||
#include "generation_metadata.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
static void relativize_lora_paths(nlohmann::json& info, const std::filesystem::path& root) {
|
||||
if (info.contains("loras")) {
|
||||
for (auto& lora : info["loras"]) {
|
||||
const auto path = lora.value("path", "");
|
||||
if (path.empty())
|
||||
continue;
|
||||
std::error_code error;
|
||||
const auto absolute = std::filesystem::absolute(std::filesystem::u8path(path), error);
|
||||
if (error)
|
||||
continue;
|
||||
const auto relative = absolute.lexically_normal().lexically_relative(root);
|
||||
if (!relative.empty())
|
||||
lora["path"] = relative.generic_u8string();
|
||||
}
|
||||
}
|
||||
if (info.contains("inpaint_params"))
|
||||
relativize_lora_paths(info["inpaint_params"], root);
|
||||
if (info.contains("postprocess")) {
|
||||
for (auto& operation : info["postprocess"])
|
||||
relativize_lora_paths(operation, root);
|
||||
}
|
||||
}
|
||||
|
||||
std::string generation_info_with_relative_lora_paths(const std::string& info, const std::string& lora_model_dir) {
|
||||
if (info.empty() || lora_model_dir.empty())
|
||||
return info;
|
||||
std::error_code error;
|
||||
const auto root = std::filesystem::absolute(std::filesystem::u8path(lora_model_dir), error);
|
||||
if (error)
|
||||
return info;
|
||||
auto result = nlohmann::json::parse(info);
|
||||
relativize_lora_paths(result, root.lexically_normal());
|
||||
return result.dump();
|
||||
}
|
||||
|
||||
std::string merge_generation_info(const std::string& source, const std::string& operation) {
|
||||
if (source.empty())
|
||||
return operation;
|
||||
auto result = nlohmann::json::parse(source);
|
||||
const auto next = nlohmann::json::parse(operation);
|
||||
result["postprocess"].push_back(next);
|
||||
return result.dump();
|
||||
}
|
||||
|
||||
int64_t generation_seed(const std::string& info, int64_t fallback) {
|
||||
if (info.empty())
|
||||
return fallback;
|
||||
const auto value = nlohmann::json::parse(info);
|
||||
const auto& params = value.contains("inpaint_params") ? value.at("inpaint_params") : value;
|
||||
return params.contains("seed") ? std::stoll(params.at("seed").get<std::string>()) : fallback;
|
||||
}
|
||||
|
||||
std::string generation_info_to_webui(const std::string& text) {
|
||||
using nlohmann::json;
|
||||
if (text.empty())
|
||||
return "";
|
||||
const auto info = json::parse(text);
|
||||
if (info.value("schema", "") != "sdcpp.generation/v1")
|
||||
return "";
|
||||
// Standalone postprocessing has no source diffusion settings to import.
|
||||
if (!info.contains("sample_params"))
|
||||
return "";
|
||||
const auto& sampling = info.at("sample_params");
|
||||
std::string result = info.value("prompt", "");
|
||||
if (info.contains("loras")) {
|
||||
for (const auto& lora : info.at("loras")) {
|
||||
// WebUI tags cannot restrict a LoRA to the high-noise model.
|
||||
if (lora.value("is_high_noise", false))
|
||||
continue;
|
||||
auto name = lora.at("path").get<std::string>();
|
||||
const auto separator = name.find_last_of("/\\");
|
||||
if (separator != std::string::npos)
|
||||
name.erase(0, separator + 1);
|
||||
const auto extension = name.find_last_of('.');
|
||||
if (extension != std::string::npos && extension > 0)
|
||||
name.erase(extension);
|
||||
if (name.empty() || name.find_first_of(":<>\r\n") != std::string::npos)
|
||||
continue;
|
||||
if (!result.empty())
|
||||
result += " ";
|
||||
result += "<lora:" + name + ":" + lora.at("multiplier").dump() + ">";
|
||||
}
|
||||
}
|
||||
const auto negative = info.value("negative_prompt", "");
|
||||
if (!negative.empty())
|
||||
result += "\nNegative prompt: " + negative;
|
||||
result += "\n";
|
||||
bool first = true;
|
||||
const auto field = [&](const char* key, const json& value) {
|
||||
std::string v = value.is_string() ? value.get<std::string>() : value.dump();
|
||||
if (v.find_first_of(",\n\r:\"") != std::string::npos)
|
||||
v = json(v).dump(-1, ' ', false);
|
||||
if (!first)
|
||||
result += ", ";
|
||||
first = false;
|
||||
result += std::string(key) + ": " + v;
|
||||
};
|
||||
field("Steps", sampling.contains("custom_sigmas") ? json(sampling.at("custom_sigmas").size() - 1) : sampling.at("sample_steps"));
|
||||
static const std::map<std::string, std::string> samplers = {
|
||||
{"euler", "Euler"}, {"euler_a", "Euler a"}, {"heun", "Heun"}, {"dpm2", "DPM2"}, {"dpm++2s_a", "DPM++ 2S a"}, {"dpm++2m", "DPM++ 2M"}, {"dpm++2m_sde", "DPM++ 2M SDE"}, {"lcm", "LCM"}};
|
||||
const auto method = sampling.at("sample_method").get<std::string>();
|
||||
auto mapped = samplers.find(method);
|
||||
field(mapped != samplers.end() ? "Sampler" : "SDCPP Sampler", mapped != samplers.end() ? mapped->second : method);
|
||||
static const std::map<std::string, std::string> schedules = {
|
||||
{"discrete", "Normal"}, {"karras", "Karras"}, {"exponential", "Exponential"}, {"sgm_uniform", "SGM Uniform"}, {"simple", "Simple"}, {"ddim_uniform", "DDIM"}, {"beta", "Beta"}};
|
||||
if (sampling.contains("scheduler")) {
|
||||
const auto scheduler = sampling.at("scheduler").get<std::string>();
|
||||
auto mapped_schedule = schedules.find(scheduler);
|
||||
field(mapped_schedule != schedules.end() ? "Schedule type" : "SDCPP Schedule", mapped_schedule != schedules.end() ? mapped_schedule->second : scheduler);
|
||||
}
|
||||
field("CFG scale", sampling.at("guidance").at("txt_cfg"));
|
||||
field("Seed", info.at("seed"));
|
||||
field("Size", std::to_string(info.at("width").get<int>()) + "x" + std::to_string(info.at("height").get<int>()));
|
||||
if (info.contains("models")) {
|
||||
const auto& models = info.at("models");
|
||||
auto model = models.value("diffusion_model", models.value("model", ""));
|
||||
const auto extension = model.find_last_of('.');
|
||||
if (extension != std::string::npos && extension > 0)
|
||||
model.erase(extension);
|
||||
if (!model.empty())
|
||||
field("Model", model);
|
||||
if (models.contains("vae"))
|
||||
field("VAE", models.at("vae"));
|
||||
}
|
||||
if (sampling.contains("eta"))
|
||||
field("Eta", sampling.at("eta"));
|
||||
if (info.value("clip_skip", 0) > 0)
|
||||
field("Clip skip", info.at("clip_skip"));
|
||||
if (info.contains("strength"))
|
||||
field("Denoising strength", info.at("strength"));
|
||||
if (info.contains("hires") && info.at("hires").value("enabled", false)) {
|
||||
const auto& hires = info.at("hires");
|
||||
field("Hires resize", std::to_string(hires.at("target_width").get<int>()) + "x" + std::to_string(hires.at("target_height").get<int>()));
|
||||
if (hires.value("steps", 0) > 0)
|
||||
field("Hires steps", hires.at("steps"));
|
||||
field("Denoising strength", hires.at("denoising_strength"));
|
||||
field("Hires upscaler", hires.at("upscaler"));
|
||||
}
|
||||
field("Version", "stable-diffusion.cpp");
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string generation_info_to_image_metadata(const std::string& info) {
|
||||
if (info.empty())
|
||||
return "";
|
||||
const auto parameters = generation_info_to_webui(info);
|
||||
return parameters.empty() ? info : parameters + ", SDCPP: " + info;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef __EXAMPLES_COMMON_GENERATION_METADATA_H__
|
||||
#define __EXAMPLES_COMMON_GENERATION_METADATA_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
std::string merge_generation_info(const std::string& source, const std::string& operation);
|
||||
std::string generation_info_with_relative_lora_paths(const std::string& info, const std::string& lora_model_dir);
|
||||
int64_t generation_seed(const std::string& info, int64_t fallback = -1);
|
||||
std::string generation_info_to_webui(const std::string& text);
|
||||
std::string generation_info_to_image_metadata(const std::string& info);
|
||||
|
||||
#endif // __EXAMPLES_COMMON_GENERATION_METADATA_H__
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "media_io.h"
|
||||
#include "generation_metadata.h"
|
||||
#include "log.h"
|
||||
#include "media_metadata.h"
|
||||
#include "resource_owners.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -15,10 +17,12 @@
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STB_IMAGE_STATIC
|
||||
#define STBI_WINDOWS_UTF8
|
||||
#include "stb_image.h"
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_STATIC
|
||||
#define STBIW_WINDOWS_UTF8
|
||||
#include "stb_image_write.h"
|
||||
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
@@ -147,7 +151,7 @@ private:
|
||||
#endif
|
||||
|
||||
bool read_binary_file_bytes(const char* path, std::vector<uint8_t>& data) {
|
||||
std::ifstream fin(fs::path(path), std::ios::binary);
|
||||
std::ifstream fin(fs::u8path(path), std::ios::binary);
|
||||
if (!fin) {
|
||||
return false;
|
||||
}
|
||||
@@ -170,7 +174,7 @@ bool read_binary_file_bytes(const char* path, std::vector<uint8_t>& data) {
|
||||
}
|
||||
|
||||
bool write_binary_file_bytes(const std::string& path, const std::vector<uint8_t>& data) {
|
||||
std::ofstream fout(fs::path(path), std::ios::binary);
|
||||
std::ofstream fout(fs::u8path(path), std::ios::binary);
|
||||
if (!fout) {
|
||||
return false;
|
||||
}
|
||||
@@ -314,23 +318,22 @@ uint8_t* decode_webp_image_to_buffer(const uint8_t* data,
|
||||
return output.release();
|
||||
}
|
||||
|
||||
std::string build_webp_xmp_packet(const std::string& parameters) {
|
||||
if (parameters.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::string escaped_parameters = xml_escape(parameters);
|
||||
return "<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n"
|
||||
"<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n"
|
||||
" <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
|
||||
" <rdf:Description xmlns:sdcpp=\"https://github.com/leejet/stable-diffusion.cpp/ns/1.0/\">\n"
|
||||
" <sdcpp:parameters>" +
|
||||
escaped_parameters +
|
||||
"</sdcpp:parameters>\n"
|
||||
" </rdf:Description>\n"
|
||||
" </rdf:RDF>\n"
|
||||
"</x:xmpmeta>\n"
|
||||
"<?xpacket end=\"w\"?>";
|
||||
static bool embed_webp_metadata(std::vector<uint8_t>& image, const std::string& parameters) {
|
||||
if (parameters.empty())
|
||||
return true;
|
||||
WebPData data{image.data(), image.size()};
|
||||
WebPMuxPtr mux(WebPMuxCreate(&data, 1));
|
||||
if (!mux)
|
||||
return false;
|
||||
const auto xmp = generation_xmp(parameters);
|
||||
WebPData xmp_data{reinterpret_cast<const uint8_t*>(xmp.data()), xmp.size()};
|
||||
if (WebPMuxSetChunk(mux.get(), "XMP ", &xmp_data, 1) != WEBP_MUX_OK)
|
||||
return false;
|
||||
WebPDataGuard assembled;
|
||||
if (WebPMuxAssemble(mux.get(), &assembled.data) != WEBP_MUX_OK)
|
||||
return false;
|
||||
image.assign(assembled.data.bytes, assembled.data.bytes + assembled.data.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool encode_webp_image_to_vector(const uint8_t* image,
|
||||
@@ -374,37 +377,7 @@ bool encode_webp_image_to_vector(const uint8_t* image,
|
||||
|
||||
out.assign(encoded.get(), encoded.get() + encoded_size);
|
||||
|
||||
if (parameters.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
WebPData image_data;
|
||||
WebPDataInit(&image_data);
|
||||
WebPDataGuard assembled_data;
|
||||
|
||||
image_data.bytes = out.data();
|
||||
image_data.size = out.size();
|
||||
|
||||
WebPMuxPtr mux(WebPMuxNew());
|
||||
if (mux == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string xmp_packet = build_webp_xmp_packet(parameters);
|
||||
WebPData xmp_data;
|
||||
WebPDataInit(&xmp_data);
|
||||
xmp_data.bytes = reinterpret_cast<const uint8_t*>(xmp_packet.data());
|
||||
xmp_data.size = xmp_packet.size();
|
||||
|
||||
const bool ok = WebPMuxSetImage(mux.get(), &image_data, 1) == WEBP_MUX_OK &&
|
||||
WebPMuxSetChunk(mux.get(), "XMP ", &xmp_data, 1) == WEBP_MUX_OK &&
|
||||
WebPMuxAssemble(mux.get(), &assembled_data.data) == WEBP_MUX_OK;
|
||||
|
||||
if (ok) {
|
||||
out.assign(assembled_data.data.bytes, assembled_data.data.bytes + assembled_data.data.size);
|
||||
}
|
||||
|
||||
return ok;
|
||||
return embed_webp_metadata(out, generation_info_to_image_metadata(parameters));
|
||||
}
|
||||
|
||||
#ifdef SD_USE_WEBM
|
||||
@@ -714,7 +687,7 @@ static std::pair<uint64_t, uint64_t> audio_sample_range_for_video_frame(const sd
|
||||
}
|
||||
|
||||
EncodedImageFormat encoded_image_format_from_path(const std::string& path) {
|
||||
std::string ext = fs::path(path).extension().string();
|
||||
std::string ext = fs::u8path(path).extension().u8string();
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
|
||||
|
||||
if (ext == ".jpg" || ext == ".jpeg" || ext == ".jpe") {
|
||||
@@ -737,6 +710,7 @@ std::vector<uint8_t> encode_image_to_vector(EncodedImageFormat format,
|
||||
const std::string& parameters,
|
||||
int quality) {
|
||||
std::vector<uint8_t> buffer;
|
||||
const auto metadata = format == EncodedImageFormat::WEBP ? std::string() : generation_info_to_image_metadata(parameters);
|
||||
|
||||
auto write_func = [&buffer](void* context, void* data, int size) {
|
||||
(void)context;
|
||||
@@ -759,7 +733,7 @@ std::vector<uint8_t> encode_image_to_vector(EncodedImageFormat format,
|
||||
result = stbi_write_jpg_to_func(c_func, &ctx, width, height, channels, image, quality);
|
||||
break;
|
||||
case EncodedImageFormat::PNG:
|
||||
result = stbi_ext_write_png_to_func(c_func, &ctx, width, height, channels, image, width * channels, parameters.empty() ? nullptr : parameters.c_str());
|
||||
result = stbi_ext_write_png_to_func(c_func, &ctx, width, height, channels, image, width * channels, metadata.empty() ? nullptr : metadata.c_str());
|
||||
break;
|
||||
case EncodedImageFormat::WEBP:
|
||||
#ifdef SD_USE_WEBP
|
||||
@@ -777,9 +751,12 @@ std::vector<uint8_t> encode_image_to_vector(EncodedImageFormat format,
|
||||
break;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
buffer.clear();
|
||||
if (result && format == EncodedImageFormat::JPEG && !embed_jpeg_metadata(buffer, metadata)) {
|
||||
LOG_ERROR("generation metadata exceeds the container capacity or the encoded image is invalid");
|
||||
result = 0;
|
||||
}
|
||||
if (!result)
|
||||
buffer.clear();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@@ -792,18 +769,8 @@ bool write_image_to_file(const std::string& path,
|
||||
int quality) {
|
||||
const EncodedImageFormat format = encoded_image_format_from_path(path);
|
||||
|
||||
switch (format) {
|
||||
case EncodedImageFormat::JPEG:
|
||||
return stbi_write_jpg(path.c_str(), width, height, channels, image, quality, parameters.empty() ? nullptr : parameters.c_str()) != 0;
|
||||
case EncodedImageFormat::PNG:
|
||||
return stbi_write_png(path.c_str(), width, height, channels, image, 0, parameters.empty() ? nullptr : parameters.c_str()) != 0;
|
||||
case EncodedImageFormat::WEBP: {
|
||||
const std::vector<uint8_t> encoded = encode_image_to_vector(format, image, width, height, channels, parameters, quality);
|
||||
return !encoded.empty() && write_binary_file_bytes(path, encoded);
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
const auto encoded = encode_image_to_vector(format, image, width, height, channels, parameters, quality);
|
||||
return !encoded.empty() && write_binary_file_bytes(path, encoded);
|
||||
}
|
||||
|
||||
uint8_t* load_image_from_file(const char* image_path,
|
||||
@@ -1382,7 +1349,10 @@ std::vector<uint8_t> create_video_from_sd_images_to_vector(const std::string& ou
|
||||
|
||||
#ifdef SD_USE_WEBP
|
||||
if (format == "webp") {
|
||||
return create_animated_webp_from_sd_images_to_vector(images, num_images, fps, quality);
|
||||
auto data = create_animated_webp_from_sd_images_to_vector(images, num_images, fps, quality);
|
||||
if (!data.empty() && !embed_webp_metadata(data, parameters))
|
||||
return {};
|
||||
return data;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1413,7 +1383,7 @@ bool write_wav_to_file(const std::string& path,
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream file(path, std::ios::binary);
|
||||
std::ofstream file(fs::u8path(path), std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
@@ -1467,70 +1437,94 @@ static uint32_t read_le32(const uint8_t* data) {
|
||||
(static_cast<uint32_t>(data[3]) << 24);
|
||||
}
|
||||
|
||||
/** Loads a WAV file from disk and delegates decoding to load_wav_from_memory(). */
|
||||
bool load_wav_from_file(const std::string& path,
|
||||
std::vector<float>& interleaved_samples,
|
||||
uint32_t& sample_rate,
|
||||
uint32_t& channels) {
|
||||
std::ifstream file(fs::u8path(path), std::ios::binary);
|
||||
if (!file) {
|
||||
interleaved_samples.clear();
|
||||
sample_rate = 0;
|
||||
channels = 0;
|
||||
return false;
|
||||
}
|
||||
file.seekg(0, std::ios::end);
|
||||
const std::streamoff file_size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
if (file_size < 0) {
|
||||
interleaved_samples.clear();
|
||||
sample_rate = 0;
|
||||
channels = 0;
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> buffer(static_cast<size_t>(file_size));
|
||||
if (file_size > 0 && !file.read(reinterpret_cast<char*>(buffer.data()), file_size)) {
|
||||
interleaved_samples.clear();
|
||||
sample_rate = 0;
|
||||
channels = 0;
|
||||
return false;
|
||||
}
|
||||
return load_wav_from_memory(buffer.data(), buffer.size(), interleaved_samples, sample_rate, channels);
|
||||
}
|
||||
|
||||
/** Parses a RIFF/WAVE buffer in place; compares chunk sizes against the remaining
|
||||
* bytes so payload-controlled sizes cannot wrap size_t on 32-bit builds. */
|
||||
bool load_wav_from_memory(const uint8_t* data,
|
||||
size_t size,
|
||||
std::vector<float>& interleaved_samples,
|
||||
uint32_t& sample_rate,
|
||||
uint32_t& channels) {
|
||||
interleaved_samples.clear();
|
||||
sample_rate = 0;
|
||||
channels = 0;
|
||||
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
uint8_t riff_header[12];
|
||||
if (!file.read(reinterpret_cast<char*>(riff_header), sizeof(riff_header)) ||
|
||||
std::memcmp(riff_header, "RIFF", 4) != 0 ||
|
||||
std::memcmp(riff_header + 8, "WAVE", 4) != 0) {
|
||||
if (data == nullptr ||
|
||||
size < 12 ||
|
||||
std::memcmp(data, "RIFF", 4) != 0 ||
|
||||
std::memcmp(data + 8, "WAVE", 4) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t audio_format = 0;
|
||||
uint16_t bits_per_sample = 0;
|
||||
uint16_t block_align = 0;
|
||||
std::streampos data_pos = std::streampos(-1);
|
||||
size_t data_pos = SIZE_MAX;
|
||||
uint32_t data_size = 0;
|
||||
|
||||
while (file.good()) {
|
||||
uint8_t chunk_header[8];
|
||||
if (!file.read(reinterpret_cast<char*>(chunk_header), sizeof(chunk_header))) {
|
||||
break;
|
||||
}
|
||||
uint32_t chunk_size = read_le32(chunk_header + 4);
|
||||
std::streampos chunk_data_pos = file.tellg();
|
||||
size_t pos = 12;
|
||||
while (pos + 8 <= size) {
|
||||
uint32_t chunk_size = read_le32(data + pos + 4);
|
||||
size_t chunk_data_pos = pos + 8;
|
||||
const size_t remaining = size - chunk_data_pos; // safe: chunk_data_pos <= size
|
||||
// Chunk sizes come from the payload, so compare against the remaining bytes
|
||||
// instead of adding them to offsets (avoids size_t wrap on 32-bit builds).
|
||||
const bool data_fits = static_cast<size_t>(chunk_size) <= remaining;
|
||||
|
||||
if (std::memcmp(chunk_header, "fmt ", 4) == 0) {
|
||||
if (chunk_size < 16) {
|
||||
if (std::memcmp(data + pos, "fmt ", 4) == 0) {
|
||||
if (chunk_size < 16 || !data_fits) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> fmt(chunk_size);
|
||||
if (!file.read(reinterpret_cast<char*>(fmt.data()), chunk_size)) {
|
||||
return false;
|
||||
}
|
||||
audio_format = read_le16(fmt.data());
|
||||
channels = read_le16(fmt.data() + 2);
|
||||
sample_rate = read_le32(fmt.data() + 4);
|
||||
block_align = read_le16(fmt.data() + 12);
|
||||
bits_per_sample = read_le16(fmt.data() + 14);
|
||||
audio_format = read_le16(data + chunk_data_pos);
|
||||
channels = read_le16(data + chunk_data_pos + 2);
|
||||
sample_rate = read_le32(data + chunk_data_pos + 4);
|
||||
block_align = read_le16(data + chunk_data_pos + 12);
|
||||
bits_per_sample = read_le16(data + chunk_data_pos + 14);
|
||||
if (audio_format == 0xfffe && chunk_size >= 40) {
|
||||
audio_format = read_le16(fmt.data() + 24);
|
||||
audio_format = read_le16(data + chunk_data_pos + 24);
|
||||
}
|
||||
} else if (std::memcmp(chunk_header, "data", 4) == 0) {
|
||||
} else if (std::memcmp(data + pos, "data", 4) == 0) {
|
||||
data_pos = chunk_data_pos;
|
||||
data_size = chunk_size;
|
||||
file.seekg(chunk_size, std::ios::cur);
|
||||
} else {
|
||||
file.seekg(chunk_size, std::ios::cur);
|
||||
}
|
||||
|
||||
if (!file.good()) {
|
||||
break;
|
||||
}
|
||||
if ((chunk_size & 1) != 0) {
|
||||
file.seekg(1, std::ios::cur);
|
||||
}
|
||||
// Only advance past a chunk whose size and pad byte fully fit (keeps the sum
|
||||
// overflow-free); a chunk truncated at the buffer end terminates the walk.
|
||||
const bool chunk_fits = data_fits && ((chunk_size & 1) == 0 || static_cast<size_t>(chunk_size) < remaining);
|
||||
pos = chunk_fits ? chunk_data_pos + static_cast<size_t>(chunk_size) + (chunk_size & 1) : size;
|
||||
}
|
||||
|
||||
const uint32_t bytes_per_sample = (bits_per_sample + 7) / 8;
|
||||
if (data_pos == std::streampos(-1) || data_size == 0 || channels == 0 || sample_rate == 0 ||
|
||||
if (data_pos == SIZE_MAX || data_size == 0 || channels == 0 || sample_rate == 0 ||
|
||||
block_align == 0 || bytes_per_sample == 0 || block_align < channels * bytes_per_sample ||
|
||||
(audio_format != 1 && audio_format != 3)) {
|
||||
return false;
|
||||
@@ -1540,16 +1534,14 @@ bool load_wav_from_file(const std::string& path,
|
||||
if (frame_count == 0 || frame_count > SIZE_MAX / channels) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> pcm(data_size);
|
||||
file.clear();
|
||||
file.seekg(data_pos);
|
||||
if (!file.read(reinterpret_cast<char*>(pcm.data()), data_size)) {
|
||||
if (static_cast<size_t>(data_size) > size - data_pos) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t* pcm = data + data_pos;
|
||||
|
||||
interleaved_samples.resize(static_cast<size_t>(frame_count * channels));
|
||||
for (uint64_t frame = 0; frame < frame_count; ++frame) {
|
||||
const uint8_t* frame_data = pcm.data() + frame * block_align;
|
||||
const uint8_t* frame_data = pcm + frame * block_align;
|
||||
for (uint32_t channel = 0; channel < channels; ++channel) {
|
||||
const uint8_t* sample_data = frame_data + channel * bytes_per_sample;
|
||||
float sample = 0.0f;
|
||||
|
||||
@@ -16,6 +16,7 @@ enum class EncodedImageFormat {
|
||||
|
||||
EncodedImageFormat encoded_image_format_from_path(const std::string& path);
|
||||
|
||||
// parameters is the core sdcpp.generation/v1 JSON; empty disables both metadata formats.
|
||||
std::vector<uint8_t> encode_image_to_vector(EncodedImageFormat format,
|
||||
const uint8_t* image,
|
||||
int width,
|
||||
@@ -125,4 +126,11 @@ bool load_wav_from_file(const std::string& path,
|
||||
uint32_t& sample_rate,
|
||||
uint32_t& channels);
|
||||
|
||||
/** Decodes a WAV buffer (PCM 8/16/24/32 or IEEE float 32/64) into interleaved samples. */
|
||||
bool load_wav_from_memory(const uint8_t* data,
|
||||
size_t size,
|
||||
std::vector<float>& interleaved_samples,
|
||||
uint32_t& sample_rate,
|
||||
uint32_t& channels);
|
||||
|
||||
#endif // __MEDIA_IO_H__
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "media_metadata.h"
|
||||
|
||||
std::string generation_xmp(const std::string& info) {
|
||||
std::string escaped;
|
||||
for (char c : info) {
|
||||
switch (c) {
|
||||
case '&':
|
||||
escaped += "&";
|
||||
break;
|
||||
case '<':
|
||||
escaped += "<";
|
||||
break;
|
||||
case '>':
|
||||
escaped += ">";
|
||||
break;
|
||||
case '"':
|
||||
escaped += """;
|
||||
break;
|
||||
case '\'':
|
||||
escaped += "'";
|
||||
break;
|
||||
default:
|
||||
escaped += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\"><rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">"
|
||||
"<rdf:Description rdf:about=\"\" xmlns:sdcpp=\"https://github.com/leejet/stable-diffusion.cpp/ns/1.0/\">"
|
||||
"<sdcpp:parameters>" +
|
||||
escaped + "</sdcpp:parameters></rdf:Description></rdf:RDF></x:xmpmeta>";
|
||||
}
|
||||
|
||||
bool embed_jpeg_metadata(std::vector<uint8_t>& image, const std::string& parameters) {
|
||||
if (parameters.empty())
|
||||
return true;
|
||||
const char label[] = "parameters";
|
||||
const size_t overhead = sizeof(label) + 1;
|
||||
if (image.size() < 2 || parameters.size() > 65533 - overhead)
|
||||
return false;
|
||||
const size_t length = overhead + parameters.size() + 2;
|
||||
std::vector<uint8_t> segment = {0xff, 0xfe, static_cast<uint8_t>(length >> 8), static_cast<uint8_t>(length)};
|
||||
segment.insert(segment.end(), label, label + sizeof(label));
|
||||
segment.insert(segment.end(), parameters.begin(), parameters.end());
|
||||
segment.push_back(0);
|
||||
image.insert(image.begin() + 2, segment.begin(), segment.end());
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef __EXAMPLES_COMMON_MEDIA_METADATA_H__
|
||||
#define __EXAMPLES_COMMON_MEDIA_METADATA_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
std::string generation_xmp(const std::string& parameters);
|
||||
bool embed_jpeg_metadata(std::vector<uint8_t>& image, const std::string& parameters);
|
||||
|
||||
#endif // __EXAMPLES_COMMON_MEDIA_METADATA_H__
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "generation_metadata.h"
|
||||
#include "stable-diffusion.h"
|
||||
|
||||
struct FreeDeleter {
|
||||
@@ -175,6 +176,7 @@ public:
|
||||
class SDImageVec {
|
||||
private:
|
||||
std::vector<sd_image_t> images_;
|
||||
std::vector<std::string> generation_infos_;
|
||||
|
||||
public:
|
||||
SDImageVec() = default;
|
||||
@@ -183,13 +185,14 @@ public:
|
||||
SDImageVec& operator=(const SDImageVec&) = delete;
|
||||
|
||||
SDImageVec(SDImageVec&& other) noexcept
|
||||
: images_(std::move(other.images_)) {
|
||||
: images_(std::move(other.images_)), generation_infos_(std::move(other.generation_infos_)) {
|
||||
}
|
||||
|
||||
SDImageVec& operator=(SDImageVec&& other) noexcept {
|
||||
if (this != &other) {
|
||||
clear();
|
||||
images_ = std::move(other.images_);
|
||||
images_ = std::move(other.images_);
|
||||
generation_infos_ = std::move(other.generation_infos_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -210,6 +213,41 @@ public:
|
||||
images_.reserve(count);
|
||||
}
|
||||
|
||||
const std::string& generation_info(size_t index) const {
|
||||
static const std::string empty;
|
||||
return index < generation_infos_.size() ? generation_infos_[index] : empty;
|
||||
}
|
||||
|
||||
void append_generation_info(size_t index, const std::string& info, const std::string& lora_model_dir = {}) {
|
||||
generation_infos_.resize(images_.size());
|
||||
generation_infos_.at(index) = merge_generation_info(generation_infos_.at(index), generation_info_with_relative_lora_paths(info, lora_model_dir));
|
||||
}
|
||||
|
||||
void adopt(sd_image_generation_result_t* result, const std::string& lora_model_dir = {}) {
|
||||
clear();
|
||||
if (!result)
|
||||
return;
|
||||
for (int i = 0; i < result->count; ++i) {
|
||||
images_.push_back(result->items[i].image);
|
||||
result->items[i].image = {};
|
||||
generation_infos_.push_back(generation_info_with_relative_lora_paths(result->items[i].generation_info, lora_model_dir));
|
||||
}
|
||||
free_sd_image_generation_result(result);
|
||||
}
|
||||
|
||||
void adopt_video(sd_video_generation_result_t* result, const std::string& lora_model_dir = {}) {
|
||||
clear();
|
||||
if (!result)
|
||||
return;
|
||||
const auto info = generation_info_with_relative_lora_paths(result->generation_info, lora_model_dir);
|
||||
for (int i = 0; i < result->frame_count; ++i) {
|
||||
images_.push_back(result->frames[i]);
|
||||
result->frames[i] = {};
|
||||
generation_infos_.push_back(info);
|
||||
}
|
||||
free_sd_video_generation_result(result);
|
||||
}
|
||||
|
||||
void adopt(sd_image_t* images, int count) {
|
||||
clear();
|
||||
if (images == nullptr || count <= 0) {
|
||||
@@ -270,6 +308,7 @@ public:
|
||||
image.data = nullptr;
|
||||
}
|
||||
images_.clear();
|
||||
generation_infos_.clear();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -64,10 +64,14 @@ endif()
|
||||
|
||||
add_executable(${TARGET}
|
||||
../common/common.cpp
|
||||
../common/generation_metadata.cpp
|
||||
../common/log.cpp
|
||||
../common/media_io.cpp
|
||||
../common/media_metadata.cpp
|
||||
main.cpp
|
||||
runtime.cpp
|
||||
model_catalog.cpp
|
||||
routes_models.cpp
|
||||
async_jobs.cpp
|
||||
routes_index.cpp
|
||||
routes_openai.cpp
|
||||
@@ -88,6 +92,17 @@ else()
|
||||
message(STATUS "HAVE_INDEX_HTML disabled")
|
||||
endif()
|
||||
|
||||
file(GLOB BUILTIN_RECIPE_FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/recipes/*.json")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${BUILTIN_RECIPE_FILES})
|
||||
# Keep recipes in separate literals to stay within MSVC's string size limit.
|
||||
set(SD_BUILTIN_RECIPES "")
|
||||
foreach(RECIPE_FILE IN LISTS BUILTIN_RECIPE_FILES)
|
||||
file(READ "${RECIPE_FILE}" RECIPE_JSON)
|
||||
string(APPEND SD_BUILTIN_RECIPES "R\"recipes(${RECIPE_JSON})recipes\",\n")
|
||||
endforeach()
|
||||
configure_file(recipes_builtin.h.in "${CMAKE_CURRENT_BINARY_DIR}/recipes_builtin.h" @ONLY)
|
||||
target_include_directories(${TARGET} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
install(TARGETS ${TARGET} RUNTIME)
|
||||
target_link_libraries(${TARGET} PRIVATE stable-diffusion ${CMAKE_THREAD_LIBS_INIT})
|
||||
if(SD_WEBP)
|
||||
|
||||
@@ -1,9 +1,254 @@
|
||||
# Model selection
|
||||
|
||||
Start without model arguments to select models in the web UI:
|
||||
|
||||
```sh
|
||||
sd-server --model-dir ./models
|
||||
```
|
||||
|
||||
From a source checkout, you can also use `sh ./start-server.sh` on Linux/macOS
|
||||
or `.\start-server.bat` on Windows. The launchers look for the server relative
|
||||
to the script: beside the script, then in `bin/Release/`, `bin/`,
|
||||
`build/bin/Release/`, and `build/bin/`, in that order.
|
||||
For a different build directory, set `SD_SERVER_PATH` to the executable path:
|
||||
|
||||
```sh
|
||||
SD_SERVER_PATH="./build_vulkan/bin/sd-server" sh ./start-server.sh --listen-port 8080
|
||||
```
|
||||
|
||||
```powershell
|
||||
$env:SD_SERVER_PATH = ".\build_vulkan\bin\Release\sd-server.exe"
|
||||
.\start-server.bat --listen-port 8080
|
||||
```
|
||||
|
||||
Binary release archives include both launchers. Keep the launchers under version
|
||||
control and put personal settings in a separate file beside them:
|
||||
|
||||
- Windows batch: `start-server.local.bat`
|
||||
- Linux/macOS: `start-server.local.sh`
|
||||
|
||||
These local files are ignored by Git and excluded from release archives. They
|
||||
are loaded automatically after the launcher's defaults. They can override
|
||||
`SD_SERVER_PATH`, `MODEL_DIR`, and `COMMANDLINE_ARGS`; there is no need to edit
|
||||
the tracked launchers. For example:
|
||||
|
||||
```bat
|
||||
@echo off
|
||||
set "MODEL_DIR=D:\stable-diffusion-webui\models"
|
||||
set "COMMANDLINE_ARGS=--listen-port 8080 --offload-to-cpu"
|
||||
```
|
||||
|
||||
```sh
|
||||
MODEL_DIR="/data/stable-diffusion-webui/models"
|
||||
COMMANDLINE_ARGS="--listen-port 8080 --offload-to-cpu"
|
||||
```
|
||||
|
||||
Each launcher prints the resolved command before execution and forwards the
|
||||
server's exit code.
|
||||
|
||||
`SD_SERVER_PATH` inherits the environment value by default; leave it empty for
|
||||
automatic discovery, or set it in the local file to select another executable.
|
||||
Settings are applied in this order: launcher defaults, local configuration,
|
||||
then invocation arguments. Repeated `--model-dir` arguments add roots, so they
|
||||
do not replace the `MODEL_DIR` root. Local configuration files are shell scripts
|
||||
and use the corresponding shell's syntax.
|
||||
|
||||
The launchers preserve the current working directory, so the default
|
||||
`MODEL_DIR="./models"` is relative to where you start the launcher. Arguments
|
||||
passed when invoking the launcher follow these defaults. Repeated `--model-dir`
|
||||
arguments add search roots rather than replacing `MODEL_DIR`; set `MODEL_DIR` in
|
||||
the local configuration to change the first root. Other single-value options can override the defaults.
|
||||
In the shell script, `COMMANDLINE_ARGS` is a
|
||||
whitespace-separated list; pass values containing spaces as quoted arguments
|
||||
when invoking the script. `MODEL_DIR` itself supports spaces in both scripts.
|
||||
A leading `--version` bypasses the configured defaults. An explicit
|
||||
`SD_SERVER_PATH` is used without falling back to another build if it is invalid.
|
||||
|
||||
Choose a recipe, select the required component files, and click **Load models**.
|
||||
Recipe and component selectors support searching by name or relative file ID.
|
||||
Use the arrow keys and Enter to select, or Escape to cancel a search. With
|
||||
**Apply recipe defaults** enabled, selecting a recipe immediately applies its
|
||||
declared generation defaults while preserving prompts, input images and fields
|
||||
not specified by the recipe. Disable it to keep your current parameters when
|
||||
switching recipes. This preference defaults to enabled and is saved locally.
|
||||
Model loading and server-info refreshes do not reapply defaults or reset the form.
|
||||
Recipes declaring `video_frames` also switch the form to Video Generation;
|
||||
other recipes switch to Image Generation. Generation is enabled after the
|
||||
selected recipe is loaded. Changing a selection does not reload the model
|
||||
until you click Load.
|
||||
The server can also start with the existing model arguments shown below;
|
||||
existing API clients then work without selecting a recipe. The UI automatically
|
||||
selects **Command-line model (all features)** and uses the startup generation
|
||||
defaults. This runtime-only recipe exposes all controls for the supported
|
||||
image/video mode. Selecting another recipe previews a model switch; selecting
|
||||
the command-line entry again restores its generation defaults. The entry is
|
||||
removed when the command-line context is unloaded or replaced.
|
||||
|
||||
Built-in recipes cover SD 1/2/3/3.5, SDXL and distilled checkpoints;
|
||||
FLUX 1/2/3, Chroma, Qwen Image and editing variants; Z-Image, Anima, ERNIE,
|
||||
LENS, PiD and other supported image families; AnimateDiff, Wan, LTX,
|
||||
HunyuanVideo, LingBot Video and MiniMax-H3. Model-specific text encoder
|
||||
labels identify the expected encoder family and size. Loading a video model enables **Video Generation**; I2V also requires a
|
||||
start image. The model button below the prompt opens model and recipe selection.
|
||||
Its status summary shows elapsed loading time from the status API and keeps the final
|
||||
time after success or failure, including after a page refresh.
|
||||
Built-ins are embedded in the
|
||||
binary from [recipes/](recipes/), with one JSON file per recipe. Adding or
|
||||
editing a built-in requires rebuilding the server. User recipes are read from
|
||||
`<model-dir>/recipes/*.json`. A valid user recipe with the same ID replaces
|
||||
the entire built-in recipe. Across roots, the first valid user recipe wins;
|
||||
overrides are logged. Duplicate user IDs within one root are rejected; invalid
|
||||
recipes are reported without removing a valid recipe from another root or the
|
||||
built-in version. Slots need only `id` and
|
||||
`required`; the server supplies labels and search directories. Use `label`
|
||||
only when a recipe needs a more specific display name.
|
||||
|
||||
Recipes use `features` to enable specialized controls and `disabled_features`
|
||||
to turn off shared defaults. Disabled features take precedence. The UI hides
|
||||
unsupported fields, empty sections and irrelevant sampler/cache options, and
|
||||
omits hidden values from generation requests. See the
|
||||
[feature declarations](api.md#recipe-feature-declarations) for the defaults
|
||||
and complete list of supported names.
|
||||
|
||||
Built-in additions and changes must follow the [recipe policy](recipes/README.md).
|
||||
Split recipes by task weights or added capabilities, not by style or acceleration
|
||||
LoRAs. Quantizations, file formats and compatible layer-count variants share a
|
||||
recipe. Native encoders, projectors and connectors stay in the model's recipe.
|
||||
Community editing recipes require selecting the matching LoRA separately; a
|
||||
recipe does not enable it automatically.
|
||||
|
||||
See [generation defaults and sources](recipes/defaults.md) for the selected
|
||||
publisher recommendations and reference-code defaults, with step defaults capped
|
||||
at 20. Recipes link to the
|
||||
original publisher's Hugging Face repository where one is available. DiT
|
||||
recipes default to Euler, except for models requiring a specialized sampler.
|
||||
|
||||
Compatible recipes offer a TAE slot alongside VAE. When the checkpoint does not
|
||||
bundle a VAE, select at least one of VAE or TAE. The Models panel shows a reminder
|
||||
below the model selectors until this requirement is met. A selected TAE is used for
|
||||
encoding and decoding, including when a full VAE is also selected; use complete,
|
||||
model-compatible TAE weights rather than decoder-only preview weights. Qwen
|
||||
Image 2.1 uses the same recipe for its full VAE and TAEQI2.1 alternatives.
|
||||
|
||||
The following variants have dedicated recipes:
|
||||
|
||||
| Model | Recipe IDs / notes |
|
||||
| --- | --- |
|
||||
| Z-Image L2P | `z-image-l2p`; no VAE |
|
||||
| LLaDA-Image | `llada-image` (base) and `llada-image-turbo`; use matching encoders and connectors |
|
||||
| PixArt Alpha | `pixart-alpha-512`, `pixart-alpha-1024`; Alpha VAE scale and positional settings |
|
||||
| PiD / PiD 1.5 | `pid` / `pid-15` for Flux or Z-Image VAE; append `-sd3`, `-flux2`, or `-qwen-image` for other latent formats |
|
||||
| SDXL adapters | `sdxl-ip-adapter` supports classic and Plus ViT-H adapters; `sdxl-controlnet` requires compatible SDXL weights |
|
||||
| FLUX.1 Fill / Depth | `flux1-fill` uses Init Image and Mask Image; `flux1-depth` uses a precomputed depth map in Control Image |
|
||||
| SD2 / SDXL inpainting | `sd2-inpainting`, `sdxl-inpainting` |
|
||||
| SD-Turbo | `sd-turbo`; single-file SD2.1-derived checkpoint |
|
||||
| Community editing LoRAs | `anima-edit`, `krea2-raw-edit`, `krea2-turbo-edit`; apply a matching LoRA after loading |
|
||||
| HunyuanVideo 1.5 | `hunyuan-video-15-t2v`, `hunyuan-video-15-i2v`; choose the task's checkpoint |
|
||||
| Wan 2.1 / compatible single Wan 2.2 experts | `wan21-t2v`, `wan21-i2v`; the I2V recipe also accepts compatible SkyReels V2 weights |
|
||||
| Qwen Image 2.1 Turbo | `qwen-image-21-turbo`; standalone Turbo weights with the fixed 8-step schedule, Euler and CFG 1 |
|
||||
| MOSS Image | `moss-image-base`, `moss-image-sft`; choose the corresponding checkpoint |
|
||||
|
||||
PiD always requires a reference image and uses the first one. Its recipes expose
|
||||
reference inputs rather than ordinary image-to-image inputs. IP-Adapter recipes
|
||||
expose a separate IP-Adapter image and strength. FLUX Canny/Depth consume Control
|
||||
Image directly without a separate ControlNet; the UI does not calculate edge
|
||||
or depth maps. Their strength is controlled by guidance, not ControlNet strength.
|
||||
Krea2 edit recipes use the default `krea2_ostris_edit` reference preset, so the
|
||||
editing LoRA must match that convention. Ordinary Anima and Krea2 recipes do not
|
||||
expose reference-image editing inputs.
|
||||
|
||||
Fill/Depth guidance defaults follow the [Diffusers FLUX documentation](https://huggingface.co/docs/diffusers/api/pipelines/flux).
|
||||
SD-Turbo uses the [model card's one-step configuration](https://huggingface.co/stabilityai/sd-turbo),
|
||||
with CFG disabled using scale 1 in sd.cpp.
|
||||
|
||||
SVD is not included: legacy model recognition and UNet branches remain, but the
|
||||
current runner/conditioning setup does not provide a dedicated SVD image-conditioning
|
||||
path. Adding a recipe alone would not establish a working SVD pipeline.
|
||||
|
||||
Reference images, masks, audio, first/last video frames, and generation options
|
||||
such as upscaling remain generation inputs. Hardware and memory options remain
|
||||
startup arguments. Tiny SD/Tiny SD2 and Diffusers LCM directories must first be
|
||||
converted into single-file checkpoints for the file catalog. LCM checkpoints
|
||||
and LoRAs use the matching SD family recipe; select LCM sampling and adjust
|
||||
steps/CFG for the chosen weights.
|
||||
Metadata inspection, conversion, standalone upscaling and ADetailer are CLI
|
||||
workflows rather than model-loading recipes. ESRGAN and LTX latent upscalers
|
||||
are selected through generation upscaling settings with `--hires-upscalers-dir`;
|
||||
textual inversion uses the startup `--embd-dir` option.
|
||||
|
||||
Repeat `--model-dir` to share multiple model directories, including an existing
|
||||
SD WebUI model directory:
|
||||
|
||||
```sh
|
||||
sd-server --model-dir ./models --model-dir /data/stable-diffusion-webui/models
|
||||
```
|
||||
|
||||
With no `--model-dir`, the server uses `./models`. Explicit roots replace that
|
||||
default and are scanned in argument order. Duplicate canonical roots and file
|
||||
paths are scanned once. Every root can contain a `recipes/` directory.
|
||||
Directory names are matched case-insensitively on all platforms:
|
||||
|
||||
| Category | Directory names |
|
||||
| --- | --- |
|
||||
| Checkpoints | `checkpoints`, `Stable-diffusion` |
|
||||
| VAE | `vae`, `VAE` |
|
||||
| LoRA | `loras`, `Lora` |
|
||||
| ControlNet | `controlnet`, `ControlNet` |
|
||||
|
||||
Other categories keep their existing directory names. Classification uses only
|
||||
the containing directory: `*.vae.safetensors`, `*.vae.ckpt`, and similar names
|
||||
inside `checkpoints/` or `Stable-diffusion/` remain checkpoints.
|
||||
|
||||
Place model files in category directories, for example:
|
||||
|
||||
```text
|
||||
models/
|
||||
checkpoints/
|
||||
diffusion_models/
|
||||
text_encoders/
|
||||
vae/
|
||||
clip_vision/
|
||||
loras/
|
||||
recipes/
|
||||
my-flux.json
|
||||
```
|
||||
|
||||
Subdirectories within a model category are supported. Use **Refresh files &
|
||||
recipes** after adding files or changing recipes. Scanning lists file names
|
||||
without loading model weights. Category membership does not guarantee
|
||||
compatibility; the model loader performs the final compatibility check.
|
||||
Symlinks resolving outside the model directory are rejected. Dropdown labels
|
||||
omit the category prefix while retaining subdirectory names. Identical display
|
||||
paths are displayed as full file paths, with source directories before the
|
||||
filename instead of a hash suffix. The same applies to duplicate LoRA paths. File IDs include a
|
||||
root qualifier for additional roots; clients should use the returned IDs.
|
||||
Empty optional categories do not display missing-file hints.
|
||||
|
||||
Place Tiny AutoEncoder (TAE) files in `vae/` and select them in the **TAE**
|
||||
slot. The legacy `vae_approx/` directory is also scanned for TAE selections.
|
||||
|
||||
LoRAs are discovered automatically in both `loras/` and `Lora/` under every
|
||||
model root. Use `--lora-model-dir` to replace automatic discovery with one
|
||||
explicit LoRA directory. Duplicate names remain separate selectable entries. The LoRA selectors support
|
||||
searching by name or path with the same keyboard controls as model selectors.
|
||||
|
||||
The server keeps one model context loaded. Loading or unloading is rejected
|
||||
while generation requests or queued jobs are pending. A switch validates the
|
||||
selection before unloading the old context, then loads the replacement.
|
||||
If loading fails, the service stays online with no active model and reports
|
||||
the error. Backend and memory options supplied at startup apply to subsequent
|
||||
loads. Recipe defaults apply to new loads; explicitly supplied generation
|
||||
request parameters take precedence.
|
||||
|
||||
See [the API reference](api.md#model-management) for recipe fields, supported
|
||||
slots, management endpoints, and compatibility behavior.
|
||||
|
||||
# Example
|
||||
|
||||
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 +256,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
|
||||
|
||||
@@ -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,343 @@ Current endpoints include:
|
||||
- `GET /sdcpp/v1/jobs/{id}`
|
||||
- `POST /sdcpp/v1/jobs/{id}/cancel`
|
||||
- `POST /sdcpp/v1/vid_gen`
|
||||
- `POST /sdcpp/v1/upscale`
|
||||
|
||||
## Model management
|
||||
|
||||
Model management is additive: existing generation request bodies, successful
|
||||
responses, job polling and cancellation remain unchanged. All three API families
|
||||
use the current loaded context. Starting with the existing model arguments
|
||||
preserves command-line preloading and its generation defaults; it does not
|
||||
require a recipe.
|
||||
|
||||
Recipe defaults apply when a generation request omits the corresponding
|
||||
parameters. Image editing endpoints retain their existing input-image size
|
||||
inference when dimensions are omitted.
|
||||
|
||||
Without a loaded model, generation endpoints return HTTP 503 with
|
||||
`{"error":"No model is loaded","code":"model_not_loaded"}`.
|
||||
While loading/unloading, endpoints that access the context return HTTP 503
|
||||
with `code: "model_loading"`. These errors retain the existing string-valued
|
||||
`error` field. Job polling/cancellation, catalog queries and model status remain
|
||||
available during loading. Model lists are empty when no model is loaded;
|
||||
`/v1/models` retains the `sd-cpp-local` ID when ready.
|
||||
|
||||
| Method | Endpoint | Response / behavior |
|
||||
| --- | --- | --- |
|
||||
| GET | `/sdcpp/v1/recipes` | `{recipes: [...], errors: [...]}` |
|
||||
| GET | `/sdcpp/v1/model-files` | `{files: [{id, name, category, root, relative_path}], errors: [...]}` |
|
||||
| POST | `/sdcpp/v1/models/refresh` | Rescan recipes and files; return `{errors: [...]}` |
|
||||
| GET | `/sdcpp/v1/models/status` | Current state, recipe, selection, error, loading time and optional revision |
|
||||
| POST | `/sdcpp/v1/models/load` | Load the submitted recipe and selection; return status after completion |
|
||||
| POST | `/sdcpp/v1/models/unload` | Release the active context; return empty status |
|
||||
|
||||
Load request:
|
||||
|
||||
```json
|
||||
{
|
||||
"recipe": "flux1-dev",
|
||||
"models": {
|
||||
"diffusion_model": "diffusion_models/flux1-dev.gguf",
|
||||
"clip_l": "text_encoders/clip_l.safetensors",
|
||||
"t5xxl": "text_encoders/t5xxl.safetensors",
|
||||
"vae": "vae/ae.safetensors"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
File IDs are opaque strings returned by `model-files`. The first model root
|
||||
retains the existing relative IDs, such as `checkpoints/nested/model.gguf`.
|
||||
Additional roots use `@<root-id>/<relative-path>` so identical paths in different
|
||||
roots remain selectable. Root qualifiers derive from the canonical root path;
|
||||
adding unrelated roots or refreshing the catalog does not change them. Moving
|
||||
a root changes its qualifier. Do not construct IDs or use display names in load
|
||||
requests.
|
||||
|
||||
The display `name` omits the physical category directory (including aliases such
|
||||
as `Stable-diffusion`). Duplicate display paths use the full absolute file path,
|
||||
with the source directories before the filename and no hash suffix.
|
||||
`root` is an opaque source identifier, and
|
||||
`relative_path` is the file path beneath that root. Duplicate canonical files
|
||||
appear only once, using the first root that discovers them. Categories depend
|
||||
on directories, never on filename suffixes such as `.vae.safetensors`.
|
||||
|
||||
Repeated `--model-dir` arguments merge roots in order; absent arguments default
|
||||
to `./models`. User recipes override built-ins; the earliest root containing a
|
||||
valid user recipe wins across roots. Duplicate recipe IDs within one root are
|
||||
rejected. Directory aliases do not change recipe slot categories.
|
||||
|
||||
LoRA lists retain relative `path` IDs for `loras/` in the first model root and
|
||||
for an explicit `--lora-model-dir`, and qualify other directories. The additive `display_path` field provides a readable
|
||||
relative path, or the full absolute file path when needed to distinguish duplicates.
|
||||
Continue sending `path` in structured LoRA requests. `--lora-model-dir` overrides
|
||||
automatic discovery across all model roots.
|
||||
Absolute paths, unknown files, wrong categories and
|
||||
paths escaping the configured root are rejected. Optional slots can be omitted
|
||||
or set to an empty string. Load/unload returns HTTP 409 (`model_busy`) while
|
||||
requests or queued jobs are pending. Invalid configurations return HTTP 400
|
||||
(`invalid_model_config`) before unloading the current context. An initialization
|
||||
failure returns HTTP 422 (`model_load_failed`) and leaves no active context.
|
||||
The load HTTP request waits for completion; a separate client can poll status
|
||||
while it runs. Refreshing the catalog never modifies an already loaded context.
|
||||
|
||||
Status uses `state: empty | loading | ready | error`, `recipe` (ID or null),
|
||||
`models` (slot-to-file-ID mapping), and `error` (string or null). A monotonically
|
||||
increasing `revision` identifies runtime transitions within this server process.
|
||||
Command-line preloading keeps `origin: command_line` and `recipe: null` for
|
||||
compatibility, and adds `runtime_recipe`: a read-only `@command-line` recipe
|
||||
with all feature declarations enabled. It is only available while that context
|
||||
is loaded and cannot be submitted to `models/load`. The UI selects it by default
|
||||
and uses command-line generation defaults, not defaults from a catalog recipe.
|
||||
Capabilities also include this status as an additive `runtime` field.
|
||||
|
||||
`load_elapsed_seconds` is the server-measured duration in seconds (a number or
|
||||
null). It updates while a model is loading and freezes on success or failure.
|
||||
Timing starts after selection validation and includes releasing the previous
|
||||
model. It resets for each load and is null before any load and during/after
|
||||
unloading. Command-line preloading reports its completed initialization time.
|
||||
Poll `/sdcpp/v1/models/status` to display elapsed time, including while loading;
|
||||
timer updates alone do not change `revision`.
|
||||
|
||||
### Recipe feature declarations
|
||||
|
||||
Recipes may declare `features` and `disabled_features` arrays. The server starts
|
||||
with shared defaults, enables features declared by component slots and `features`,
|
||||
then applies `disabled_features` last. A disabled feature always wins. Unknown
|
||||
names, non-array values and duplicate names within an array are rejected.
|
||||
Both fields are optional; existing recipes remain valid.
|
||||
|
||||
```json
|
||||
{
|
||||
"features": ["flow_shift", "distilled_guidance"],
|
||||
"disabled_features": ["clip_skip", "lora"]
|
||||
}
|
||||
```
|
||||
|
||||
Default-enabled features are `img2img`, `mask_image`, `lora`, `vae_tiling`,
|
||||
`cache`, `cache_dit` and `preview`. All other features below default to false:
|
||||
|
||||
| Feature | Controls |
|
||||
| --- | --- |
|
||||
| `flow_shift` | Flow shift, where used by the selected scheduler |
|
||||
| `distilled_guidance` | Distilled guidance embedding |
|
||||
| `shifted_timestep` | SDXL timestep shifting |
|
||||
| `img_cfg` | Image CFG |
|
||||
| `slg` | Skip-layer guidance fields |
|
||||
| `clip_skip` | CLIP Skip |
|
||||
| `img2img`, `mask_image` | Image initialization/strength and masks |
|
||||
| `ref_images` | Reference image inputs |
|
||||
| `ref_videos` | Reference videos as frame arrays |
|
||||
| `ref_audios` | Standalone reference audio; runtime requires an Audio VAE or Audio Encoder |
|
||||
| `ref_video_audios` | Soundtracks paired with reference videos; runtime requires an Audio VAE |
|
||||
| `controlnet` | Control image and ControlNet strength |
|
||||
| `control_image` | Control image without a separate ControlNet (FLUX Canny/Depth) |
|
||||
| `ip_adapter` | IP-Adapter image and strength |
|
||||
| `init_image`, `end_image` | Video start/end frames |
|
||||
| `control_frames`, `vace` | Video control frames and VACE strength |
|
||||
| `high_noise` | High-noise pass, MoE boundary and high-noise LoRAs |
|
||||
| `lora`, `vae_tiling` | LoRA overrides and VAE tiling |
|
||||
| `cache`, `cache_unet`, `cache_dit` | Cache panel and architecture-specific algorithms |
|
||||
| `preview`, `preview_tae` | Preview panel and TAE preview option |
|
||||
|
||||
A `control_net` slot enables `controlnet`, `ip_adapter` enables `ip_adapter`,
|
||||
`high_noise_diffusion_model` enables `high_noise`, and `tae`/`taesd` enables `preview_tae`, unless explicitly disabled.
|
||||
ControlNet controls are shown when previewing a supporting recipe, even before
|
||||
a model file is selected. High-noise and TAE controls additionally require their
|
||||
component to be selected. Runtime capabilities require the component to be loaded.
|
||||
PuLID disables SLG while selected.
|
||||
Image-only and video-only inputs are filtered by the active generation mode.
|
||||
Eta depends on the selected sampler; SCM fields only apply to DBCache,
|
||||
TaylorSeer and Cache-DIT. Empty panels and advanced sections are hidden.
|
||||
|
||||
`GET /sdcpp/v1/recipes` adds a resolved boolean `effective_features` object to
|
||||
each recipe. This is response-only metadata, like `source` and slot `categories`;
|
||||
omit it when writing a user recipe file. The defaults are maintained by the
|
||||
server, not duplicated by the frontend.
|
||||
|
||||
`GET /sdcpp/v1/capabilities` adds `generation_features` for the loaded context,
|
||||
derived from the active recipe and component availability without inferring
|
||||
features from model-family names. When no model is loaded, every feature is
|
||||
present and false. The UI can still preview a selected recipe using its
|
||||
`effective_features` before loading.
|
||||
Existing `features` / `features_by_mode` keys are retained and their model-specific
|
||||
values reflect these declarations. A loaded recipe is snapshotted: refreshing
|
||||
recipe files does not change the active context's capabilities until reloading.
|
||||
Command-line preloading uses the runtime-only all-features recipe instead of model-family inference
|
||||
and component paths; variant-specific recipe declarations are more precise.
|
||||
|
||||
These declarations govern the Web UI and advertised capabilities, not API
|
||||
request validation. Existing generation APIs continue accepting their existing
|
||||
fields. The Web UI omits unsupported/hidden fields and ignores hidden image
|
||||
inputs when building requests, so switching recipes cannot submit stale options.
|
||||
|
||||
### User recipe format
|
||||
|
||||
Contributions to the built-in catalog must follow the
|
||||
[built-in recipe policy](recipes/README.md). Personal user recipes may define
|
||||
workflow-specific presets without adding them to the built-in catalog.
|
||||
|
||||
Each UTF-8 JSON file in `<model-dir>/recipes/` defines one complete recipe.
|
||||
The maximum recipe file size is 1 MiB. Unknown fields, invalid types and
|
||||
duplicate slots are errors. Built-ins in `examples/server/recipes/*.json` use
|
||||
the same one-recipe-per-file schema:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": 1,
|
||||
"id": "my-flux",
|
||||
"name": "My FLUX",
|
||||
"description": "FLUX.1 dev",
|
||||
"size_multiple": 16,
|
||||
"model_url": "https://huggingface.co/black-forest-labs/FLUX.1-dev",
|
||||
"slots": [
|
||||
{"id": "diffusion_model", "required": true},
|
||||
{"id": "clip_l", "required": true},
|
||||
{"id": "t5xxl", "required": true},
|
||||
{"id": "vae", "required": false},
|
||||
{"id": "tae", "required": false}
|
||||
],
|
||||
"required_any": [["vae", "tae"]],
|
||||
"defaults": {"width": 1024, "height": 1024, "cfg_scale": 1.0, "distilled_guidance": 3.5, "sampling_method": "euler", "steps": 20}
|
||||
}
|
||||
```
|
||||
|
||||
IDs contain 1–128 ASCII letters, digits, dots, underscores or hyphens.
|
||||
`name`, `schema_version: 1` and `slots` are required; `description` and
|
||||
`model_url`, `required_any`, `defaults`, `context`, `features`, `disabled_features`
|
||||
and `size_multiple`, `video_constraints` are optional. Each slot requires only `id` and boolean `required`.
|
||||
The server supplies the default label and search directories for each slot.
|
||||
An optional nonempty `label` overrides the display name, for example
|
||||
`{"id": "diffusion_model", "label": "Low-noise diffusion model", "required": true}`.
|
||||
Legacy `category` fields remain accepted, but must match one of the slot's
|
||||
supported directories; they cannot redefine or restrict its search directories.
|
||||
At least one required `model` or `diffusion_model` slot is necessary.
|
||||
|
||||
`size_multiple` is a positive integer from 1 to 4096 that declares the width and
|
||||
height increment used by the web UI. It defaults to `8` when omitted; the UI also
|
||||
uses `8` before a recipe is selected. For example, `"size_multiple": 16` makes
|
||||
generated size presets multiples of 16. Exact dimensions that do not match the
|
||||
increment require correction before submission. This is recipe metadata, not a
|
||||
generation parameter, and does not change the core model's alignment behavior.
|
||||
|
||||
`video_constraints` is optional metadata for the web UI's Frames / Duration
|
||||
controls. It does not override the runtime's model-specific rules. For example:
|
||||
|
||||
```json
|
||||
"video_constraints": {
|
||||
"frame_step": 4,
|
||||
"frame_offset": 1,
|
||||
"frame_rounding": "floor",
|
||||
"min_frames": 1
|
||||
}
|
||||
```
|
||||
|
||||
Legal frame counts are `frame_step * n + frame_offset`, with integer `n >= 0`,
|
||||
within the inclusive `min_frames` / `max_frames` range. All fields are optional:
|
||||
|
||||
| Field | Default | Accepted values |
|
||||
| --- | --- | --- |
|
||||
| `frame_step` | `1` | Integer 1–4096 |
|
||||
| `frame_offset` | `0` | Integer 0 to `frame_step - 1` |
|
||||
| `frame_rounding` | `"floor"` | `"floor"` or `"ceil"` |
|
||||
| `min_frames` | `1` | Integer 1–4096 |
|
||||
| `max_frames` | `4096` | Integer 1–4096, at least `min_frames` |
|
||||
| `fixed_fps` | Unset | Integer 1–240; locks the FPS control |
|
||||
|
||||
The range must contain at least one legal frame count. Unknown keys are rejected.
|
||||
Omitting the object adds no frame alignment in the UI. `defaults.fps` only sets
|
||||
the initial FPS; it does not lock it. The UI multiplies requested duration by FPS,
|
||||
aligns using the declared rounding direction, then clamps to the legal range.
|
||||
Duration stays in the UI; generation requests still send only `video_frames` and
|
||||
`fps`. Switching input modes preserves the aligned length. Changing FPS keeps
|
||||
frames in Frames mode, and keeps the requested seconds in Duration mode.
|
||||
|
||||
`model_url` is an optional HTTPS Hugging Face model repository link, shown beside
|
||||
the recipe description. Built-ins link to the model's author/publisher, not repacks.
|
||||
`required_any` contains groups of declared slot IDs. At least one model in each
|
||||
group must be selected. For example, `"required_any": [["vae", "tae"]]` with both
|
||||
slots marked `required: false` allows either the full VAE or a compatible TAE.
|
||||
Selecting both is allowed; the current server uses TAE for encoding/decoding when
|
||||
TAE is selected. Checkpoint recipes with a bundled VAE do not need this group.
|
||||
Use complete, model-compatible TAE weights; decoder-only preview weights cannot
|
||||
replace a full VAE. Pixel-space models and unsupported latent formats have no
|
||||
TAE slot.
|
||||
|
||||
| Slot IDs | Search directories |
|
||||
| --- | --- |
|
||||
| `model` | `checkpoints` |
|
||||
| `diffusion_model`, `high_noise_diffusion_model`, `uncond_diffusion_model` | `diffusion_models` |
|
||||
| `clip_l`, `clip_g`, `t5xxl`, `llm`, `audio_encoder`, `embeddings_connectors` | `text_encoders` |
|
||||
| `clip_vision` | `clip_vision` |
|
||||
| `llm_vision` | `text_encoders` or `clip_vision` |
|
||||
| `vae`, `audio_vae` | `vae` |
|
||||
| `tae` (legacy alias: `taesd`) | `vae` or `vae_approx` |
|
||||
| `tokenizer` | `tokenizers` |
|
||||
| `control_net` | `controlnet` |
|
||||
| `ip_adapter` | `ipadapter` |
|
||||
| `motion_module` | `motion_modules` |
|
||||
| `photo_maker` | `photomaker` |
|
||||
| `pulid_weights` | `pulid` |
|
||||
|
||||
Built-in recipes use `tae` for the Tiny AutoEncoder slot. TAE selections can use files from either `vae/` or the legacy
|
||||
`vae_approx/` directory. Existing recipes and load requests using `taesd`
|
||||
remain supported. Do not specify both `tae` and `taesd` in the same recipe
|
||||
or load request.
|
||||
|
||||
Model files can use `.gguf`, `.safetensors`, `.sft`, `.ckpt`, `.pt`, `.pth`
|
||||
or `.bin` extensions. The tokenizer category lists `.json` files only.
|
||||
Custom recipes compose components supported by the backend; they do not add
|
||||
new model architectures.
|
||||
|
||||
Optional `context` settings apply to each model load and override the matching
|
||||
startup setting. Supported fields are `vae_format` (`auto`, `flux`, `sd3`,
|
||||
`flux2`, or `wan`), `model_args` (a string in the same format as
|
||||
`--model-args`; replaces the startup string), and `backend` / `params_backend`
|
||||
(strings in the same format as `--backend` / `--params-backend`; replace the
|
||||
startup assignments for that load, so a recipe can place components on
|
||||
specific devices, e.g. keep a large text encoder on CPU while another recipe
|
||||
uses the GPU). Other startup settings remain unchanged. Switching to a
|
||||
recipe that omits these fields restores the startup values. For example, PiD
|
||||
uses `{"vae_format": "flux"}` and Qwen Image Edit 2511 uses
|
||||
`{"model_args": "qwen_image_zero_cond_t=true"}`.
|
||||
|
||||
Supported defaults:
|
||||
|
||||
- Integer `width`/`height` (64–4096), `steps` (1–1000), `video_frames`
|
||||
(1–4096), `fps` (1–240), `shifted_timestep` (0–1000), and
|
||||
`high_noise_steps` (1–1000 or -1 for automatic).
|
||||
- Numeric `cfg_scale`, `img_cfg_scale`, `distilled_guidance`, `flow_shift`,
|
||||
`high_noise_cfg_scale`, and `high_noise_flow_shift` (0–1000).
|
||||
- Numeric `strength` (0–1), used for image-to-image denoising, and
|
||||
`moe_boundary` (0–1), used for automatic Wan MoE expert switching.
|
||||
- `custom_sigmas`: an empty array or 2–1001 finite, nonnegative numbers.
|
||||
A nonempty array overrides the generated schedule; the sigma count minus one
|
||||
determines the actual step count. Include the final zero. An empty array uses
|
||||
the generated schedule. Loaded recipe sigmas apply to generation requests
|
||||
unless `sample_params.custom_sigmas` overrides them; switching recipes restores
|
||||
that recipe's schedule or the startup default.
|
||||
- Supported `sampling_method`/`scheduler` and
|
||||
`high_noise_sampling_method`/`high_noise_scheduler` names.
|
||||
|
||||
Other generation parameters continue to use startup defaults unless explicitly
|
||||
supplied by the generation request. Built-in Wan recipes provide video defaults;
|
||||
use `/sdcpp/v1/vid_gen` after loading a video model.
|
||||
LoRAs are merged from `loras/` and `Lora/` in every model root unless
|
||||
`--lora-model-dir` is supplied.
|
||||
|
||||
Recipe responses fill in each slot's `label`, `category` (the legacy value,
|
||||
if supplied, or the first search directory), and `categories` (the complete
|
||||
server-defined list of search directories). Clients should filter model files
|
||||
using `categories`; `category` remains available for older clients.
|
||||
For example, `{"id": "tae", "required": false}` expands to
|
||||
`{"id": "tae", "required": false, "label": "TAE", "category": "vae", "categories": ["vae", "vae_approx"]}`.
|
||||
|
||||
Recipe responses also add `source: builtin | user` and, for user recipes, `file`.
|
||||
Do not include `source`, `file`, or slot `categories` in a recipe file. A valid user recipe
|
||||
replaces a built-in with the same ID in full; inheritance/partial merging is
|
||||
not supported. Invalid recipes are skipped. Multiple valid user files sharing
|
||||
an ID are all skipped, preserving any built-in with that ID. Catalog errors
|
||||
are returned as `{file, message}` entries.
|
||||
|
||||
## `sd_cpp_extra_args`
|
||||
|
||||
@@ -277,6 +615,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:
|
||||
@@ -387,7 +745,7 @@ Common job shape:
|
||||
"id": "job_01HTXYZABC",
|
||||
"kind": "img_gen",
|
||||
"status": "queued",
|
||||
"created": 1775401200,
|
||||
"created": 1775401200000,
|
||||
"started": null,
|
||||
"completed": null,
|
||||
"queue_position": 2,
|
||||
@@ -409,6 +767,25 @@ Field types:
|
||||
| `queue_position` | `integer` |
|
||||
| `result` | `object \| null` |
|
||||
| `error` | `object \| null` |
|
||||
| `preview` | `object \| null` |
|
||||
|
||||
`created`, `started`, and `completed` are integer Unix timestamps in
|
||||
**milliseconds**. Subtract `started` from `completed` and divide by 1000 to get
|
||||
the elapsed generation time in seconds, including output encoding and excluding
|
||||
queue time.
|
||||
|
||||
`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
|
||||
|
||||
@@ -434,7 +811,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`
|
||||
@@ -470,12 +849,15 @@ Shared nested fields:
|
||||
| --- | --- |
|
||||
| `loras[].name` | `string` |
|
||||
| `loras[].path` | `string` |
|
||||
| `loras[].display_path` | `string` (display only) |
|
||||
|
||||
`upscalers`
|
||||
|
||||
| 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.
|
||||
|
||||
@@ -489,6 +871,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`:
|
||||
|
||||
@@ -518,11 +902,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` |
|
||||
@@ -531,7 +915,9 @@ Shared default fields used by both `img_gen` and `vid_gen`:
|
||||
| `output_format` | `string` |
|
||||
| `output_compression` | `integer` |
|
||||
|
||||
`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`).
|
||||
`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`). Wan2.2 TI2V 5B uses `temporal_decode_frames` (default `1`) to decode multiple latent frames per forward pass, trading additional memory for throughput; other model versions ignore this option.
|
||||
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.
|
||||
|
||||
`img_gen`-specific default fields:
|
||||
@@ -560,6 +946,7 @@ LTX and Wan preserve causal state between temporal tiles. Hunyuan Video and TAEH
|
||||
| --- | --- |
|
||||
| `video_frames` | `integer` |
|
||||
| `fps` | `integer` |
|
||||
| `ref_image_args` | `string` |
|
||||
| `moe_boundary` | `number` |
|
||||
| `vace_strength` | `number` |
|
||||
| `high_noise_sample_params` | `object` |
|
||||
@@ -590,6 +977,7 @@ Fields returned in `features_by_mode.img_gen`:
|
||||
- `cache`
|
||||
- `cancel_queued`
|
||||
- `cancel_generating`
|
||||
- `preview`
|
||||
|
||||
Fields returned in `features_by_mode.vid_gen`:
|
||||
|
||||
@@ -602,6 +990,7 @@ Fields returned in `features_by_mode.vid_gen`:
|
||||
- `cache`
|
||||
- `cancel_queued`
|
||||
- `cancel_generating`
|
||||
- `preview`
|
||||
|
||||
#### `POST /sdcpp/v1/img_gen`
|
||||
|
||||
@@ -616,7 +1005,7 @@ Example response:
|
||||
"id": "job_01HTXYZABC",
|
||||
"kind": "img_gen",
|
||||
"status": "queued",
|
||||
"created": 1775401200,
|
||||
"created": 1775401200000,
|
||||
"poll_url": "/sdcpp/v1/jobs/job_01HTXYZABC"
|
||||
}
|
||||
```
|
||||
@@ -641,9 +1030,59 @@ 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.
|
||||
Cancels a queued job immediately, or requests interruption of a generating image or
|
||||
video job. A generating job returns `200 OK` with `cancel_requested: true` and
|
||||
remains `generating` until execution stops at a cancellation checkpoint. Continue
|
||||
polling until its status becomes `cancelled`. Cancelled jobs have no result.
|
||||
Repeated cancellation requests are safe; terminal jobs are returned unchanged.
|
||||
|
||||
Typical status codes:
|
||||
|
||||
@@ -715,11 +1154,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": ""
|
||||
},
|
||||
|
||||
@@ -728,6 +1167,9 @@ Example:
|
||||
"scm_mask": "",
|
||||
"scm_policy_dynamic": true,
|
||||
|
||||
"preview": "none",
|
||||
"preview_interval": 1,
|
||||
|
||||
"output_format": "png",
|
||||
"output_compression": 100
|
||||
}
|
||||
@@ -793,6 +1235,8 @@ Top-level scalar fields:
|
||||
| `control_strength` | `number` |
|
||||
| `ip_adapter_strength` | `number` |
|
||||
| `embed_image_metadata` | `boolean` |
|
||||
| `preview` | `string` |
|
||||
| `preview_interval` | `integer` |
|
||||
|
||||
Image fields:
|
||||
|
||||
@@ -848,11 +1292,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` |
|
||||
@@ -880,6 +1324,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:
|
||||
@@ -889,9 +1337,9 @@ Example completed job:
|
||||
"id": "job_01HTXYZABC",
|
||||
"kind": "img_gen",
|
||||
"status": "completed",
|
||||
"created": 1775401200,
|
||||
"started": 1775401203,
|
||||
"completed": 1775401215,
|
||||
"created": 1775401200000,
|
||||
"started": 1775401203000,
|
||||
"completed": 1775401215237,
|
||||
"queue_position": 0,
|
||||
"result": {
|
||||
"output_format": "png",
|
||||
@@ -915,9 +1363,9 @@ Example failed job:
|
||||
"id": "job_01HTXYZABC",
|
||||
"kind": "img_gen",
|
||||
"status": "failed",
|
||||
"created": 1775401200,
|
||||
"started": 1775401203,
|
||||
"completed": 1775401204,
|
||||
"created": 1775401200000,
|
||||
"started": 1775401203000,
|
||||
"completed": 1775401204000,
|
||||
"queue_position": 0,
|
||||
"result": null,
|
||||
"error": {
|
||||
@@ -936,9 +1384,9 @@ Example cancelled job:
|
||||
"id": "job_01HTXYZABC",
|
||||
"kind": "img_gen",
|
||||
"status": "cancelled",
|
||||
"created": 1775401200,
|
||||
"created": 1775401200000,
|
||||
"started": null,
|
||||
"completed": 1775401202,
|
||||
"completed": 1775401202000,
|
||||
"queue_position": 0,
|
||||
"result": null,
|
||||
"error": {
|
||||
@@ -974,7 +1422,7 @@ Example response:
|
||||
"id": "job_01HTXYZVID",
|
||||
"kind": "vid_gen",
|
||||
"status": "queued",
|
||||
"created": 1775401200,
|
||||
"created": 1775401200000,
|
||||
"poll_url": "/sdcpp/v1/jobs/job_01HTXYZVID"
|
||||
}
|
||||
```
|
||||
@@ -994,8 +1442,9 @@ 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
|
||||
- `vid_gen` adds `end_image`, `control_frames`, `high_noise_sample_params`, `video_frames`, `fps`, `moe_boundary`, and `vace_strength`
|
||||
- `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`, `ref_image_args`, `moe_boundary`, and `vace_strength`
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1010,11 +1459,13 @@ Example:
|
||||
"seed": -1,
|
||||
"video_frames": 33,
|
||||
"fps": 16,
|
||||
"ref_image_args": "",
|
||||
"moe_boundary": 0.875,
|
||||
"vace_strength": 1.0,
|
||||
|
||||
"init_image": null,
|
||||
"end_image": null,
|
||||
"ref_images": [],
|
||||
"control_frames": [],
|
||||
|
||||
"sample_params": {
|
||||
@@ -1063,11 +1514,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": ""
|
||||
},
|
||||
|
||||
@@ -1076,11 +1527,26 @@ 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.
|
||||
- `ref_image_args` applies to `vid_gen`; MiniMax-H3 honors `ref_image_size` there (`match` default, `max` for a 2048 px short edge). Only references above the limit are downscaled; each axis is then snapped to the nearest 32 px multiple.
|
||||
- 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.
|
||||
@@ -1098,6 +1564,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:
|
||||
@@ -1125,8 +1592,11 @@ Top-level scalar fields:
|
||||
| `seed` | `integer` |
|
||||
| `video_frames` | `integer` |
|
||||
| `fps` | `integer` |
|
||||
| `ref_image_args` | `string` |
|
||||
| `moe_boundary` | `number` |
|
||||
| `vace_strength` | `number` |
|
||||
| `preview` | `string` |
|
||||
| `preview_interval` | `integer` |
|
||||
|
||||
Image and frame fields:
|
||||
|
||||
@@ -1134,6 +1604,7 @@ Image and frame fields:
|
||||
| --- | --- |
|
||||
| `init_image` | `string \| null` |
|
||||
| `end_image` | `string \| null` |
|
||||
| `ref_images` | `array<string>` |
|
||||
| `control_frames` | `array<string>` |
|
||||
|
||||
LoRA fields:
|
||||
@@ -1188,11 +1659,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` |
|
||||
@@ -1263,15 +1734,26 @@ When omitted, backend defaults apply to these fields:
|
||||
|
||||
### Frame Count Semantics
|
||||
|
||||
`video_frames` is the requested target length, but the current core video path internally normalizes the effective frame count to the largest `4n + 1` value that does not exceed the requested count.
|
||||
`video_frames` is the requested target length. The core normalizes it according
|
||||
to the loaded model:
|
||||
|
||||
Examples:
|
||||
| Model family | Frame alignment | Fixed FPS |
|
||||
| --- | --- | --- |
|
||||
| Wan, HunyuanVideo, LingBot | `4n + 1`, round down | Wan 2.2 S2V: 16 |
|
||||
| LTX | `8n + 1`, round down | None |
|
||||
| MiniMax-H3 | `17n + 5`, round up, minimum 5 | 24 |
|
||||
| AnimateDiff | Whole frames, maximum 32 | None |
|
||||
|
||||
Wan examples:
|
||||
|
||||
- `video_frames = 33` stays `33`
|
||||
- `video_frames = 34` becomes `33`
|
||||
- `video_frames = 32` becomes `29`
|
||||
|
||||
The completed job payload includes the actual decoded `frame_count`.
|
||||
At 16 FPS, a 5-second target requests 80 frames and aligns to 77 frames for Wan,
|
||||
or approximately 4.813 seconds. Audio-driven generation and temporal upscaling
|
||||
can change the final length. Use the completed job's actual decoded `frame_count`
|
||||
and `fps` for the final playback duration.
|
||||
|
||||
### Completion Result
|
||||
|
||||
@@ -1282,9 +1764,9 @@ Example completed job:
|
||||
"id": "job_01HTXYZVID",
|
||||
"kind": "vid_gen",
|
||||
"status": "completed",
|
||||
"created": 1775401200,
|
||||
"started": 1775401203,
|
||||
"completed": 1775401215,
|
||||
"created": 1775401200000,
|
||||
"started": 1775401203000,
|
||||
"completed": 1775401215237,
|
||||
"queue_position": 0,
|
||||
"result": {
|
||||
"output_format": "webm",
|
||||
@@ -1308,9 +1790,9 @@ Example failed job:
|
||||
"id": "job_01HTXYZVID",
|
||||
"kind": "vid_gen",
|
||||
"status": "failed",
|
||||
"created": 1775401200,
|
||||
"started": 1775401203,
|
||||
"completed": 1775401204,
|
||||
"created": 1775401200000,
|
||||
"started": 1775401203000,
|
||||
"completed": 1775401204000,
|
||||
"queue_position": 0,
|
||||
"result": null,
|
||||
"error": {
|
||||
@@ -1329,9 +1811,9 @@ Example cancelled job:
|
||||
"id": "job_01HTXYZVID",
|
||||
"kind": "vid_gen",
|
||||
"status": "cancelled",
|
||||
"created": 1775401200,
|
||||
"created": 1775401200000,
|
||||
"started": null,
|
||||
"completed": 1775401202,
|
||||
"completed": 1775401202000,
|
||||
"queue_position": 0,
|
||||
"result": null,
|
||||
"error": {
|
||||
@@ -1349,3 +1831,14 @@ Example cancelled job:
|
||||
- `400 Bad Request` for an empty body, unsupported model mode, invalid JSON, invalid generation parameters, or an unsupported output format
|
||||
- `429 Too Many Requests` when the job queue is full
|
||||
- `500 Internal Server Error` for unexpected server exceptions during submission
|
||||
|
||||
## Actual generation parameters
|
||||
|
||||
Generation responses expose the core `sdcpp.generation/v1` record as
|
||||
`generation_info`, independently of `embed_image_metadata`. Native async image
|
||||
result items and OpenAI image items contain the parameter snapshot with each
|
||||
image's actual seed and without `batch_count`. Native video
|
||||
results contain the video request snapshot. SD API `info.generation_info` is an array
|
||||
aligned with `info.infotexts` and `info.all_seeds`; the outer `parameters` field
|
||||
remains the request echo. See [generation metadata](../../docs/generation_metadata.md)
|
||||
for field semantics, postprocessing and container storage.
|
||||
@@ -3,13 +3,95 @@
|
||||
#include "async_jobs.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#include "common/log.h"
|
||||
#include "common/media_io.h"
|
||||
#include "common/resource_owners.hpp"
|
||||
|
||||
class JobCancellationMonitor {
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cv_;
|
||||
bool done_ = false;
|
||||
std::thread thread_;
|
||||
|
||||
public:
|
||||
// The caller holds sd_ctx_mutex until this monitor has joined, keeping the
|
||||
// context alive and preventing cancellation from reaching another request.
|
||||
JobCancellationMonitor(sd_ctx_t* ctx, const AsyncGenerationJob& job)
|
||||
: thread_([this, ctx, &job]() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
while (!done_) {
|
||||
if (job.cancel_requested.load()) {
|
||||
// Each generation pass resets the library flag, including ADetailer.
|
||||
sd_cancel_generation(ctx, SD_CANCEL_ALL);
|
||||
}
|
||||
cv_.wait_for(lock, std::chrono::milliseconds(10), [this]() { return done_; });
|
||||
}
|
||||
}) {}
|
||||
|
||||
~JobCancellationMonitor() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
done_ = true;
|
||||
}
|
||||
cv_.notify_one();
|
||||
thread_.join();
|
||||
}
|
||||
};
|
||||
|
||||
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:
|
||||
@@ -39,7 +121,7 @@ const char* async_job_status_name(AsyncJobStatus status) {
|
||||
}
|
||||
|
||||
void purge_expired_jobs(AsyncJobManager& manager) {
|
||||
const int64_t now = unix_timestamp_now();
|
||||
const int64_t now = unix_timestamp_now_ms();
|
||||
|
||||
for (auto it = manager.expired_jobs.begin(); it != manager.expired_jobs.end();) {
|
||||
if (it->second <= now) {
|
||||
@@ -59,8 +141,8 @@ void purge_expired_jobs(AsyncJobManager& manager) {
|
||||
int64_t ttl_seconds = job->status == AsyncJobStatus::Completed
|
||||
? manager.completed_ttl_seconds
|
||||
: manager.failed_ttl_seconds;
|
||||
if (now - job->completed_at >= ttl_seconds) {
|
||||
manager.expired_jobs[job->id] = now + std::max<int64_t>(ttl_seconds, 60);
|
||||
if (now - job->completed_at >= ttl_seconds * 1000) {
|
||||
manager.expired_jobs[job->id] = now + std::max<int64_t>(ttl_seconds, 60) * 1000;
|
||||
it = manager.jobs.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
@@ -81,7 +163,7 @@ size_t count_pending_jobs(const AsyncJobManager& manager) {
|
||||
|
||||
std::string make_async_job_id(AsyncJobManager& manager) {
|
||||
std::ostringstream oss;
|
||||
oss << "job_" << std::hex << unix_timestamp_now() << "_" << std::setw(8)
|
||||
oss << "job_" << std::hex << unix_timestamp_now_ms() << "_" << std::setw(8)
|
||||
<< std::setfill('0') << manager.next_id++;
|
||||
return oss.str();
|
||||
}
|
||||
@@ -93,9 +175,11 @@ bool cancel_queued_job(AsyncJobManager& manager, AsyncGenerationJob& job) {
|
||||
}
|
||||
|
||||
manager.queue.erase(new_end, manager.queue.end());
|
||||
job.cancel_requested.store(true);
|
||||
job.status = AsyncJobStatus::Cancelled;
|
||||
job.completed_at = unix_timestamp_now();
|
||||
job.completed_at = unix_timestamp_now_ms();
|
||||
job.result_images_b64.clear();
|
||||
job.result_generation_info = nullptr;
|
||||
job.result_media_b64.clear();
|
||||
job.result_media_mime_type.clear();
|
||||
job.result_frame_count = 0;
|
||||
@@ -107,13 +191,14 @@ bool cancel_queued_job(AsyncJobManager& manager, AsyncGenerationJob& job) {
|
||||
|
||||
json make_async_job_json(const AsyncJobManager& manager, const AsyncGenerationJob& job) {
|
||||
json result;
|
||||
result["id"] = job.id;
|
||||
result["kind"] = async_job_kind_name(job.kind);
|
||||
result["status"] = async_job_status_name(job.status);
|
||||
result["created"] = job.created_at;
|
||||
result["started"] = job.started_at == 0 ? json(nullptr) : json(job.started_at);
|
||||
result["completed"] = job.completed_at == 0 ? json(nullptr) : json(job.completed_at);
|
||||
result["queue_position"] = 0;
|
||||
result["id"] = job.id;
|
||||
result["kind"] = async_job_kind_name(job.kind);
|
||||
result["status"] = async_job_status_name(job.status);
|
||||
result["cancel_requested"] = job.cancel_requested.load();
|
||||
result["created"] = job.created_at;
|
||||
result["started"] = job.started_at == 0 ? json(nullptr) : json(job.started_at);
|
||||
result["completed"] = job.completed_at == 0 ? json(nullptr) : json(job.completed_at);
|
||||
result["queue_position"] = 0;
|
||||
|
||||
if (job.status == AsyncJobStatus::Queued) {
|
||||
size_t position = 1;
|
||||
@@ -134,11 +219,12 @@ json make_async_job_json(const AsyncJobManager& manager, const AsyncGenerationJo
|
||||
{"fps", job.result_fps},
|
||||
{"frame_count", job.result_frame_count},
|
||||
{"b64_json", job.result_media_b64},
|
||||
{"generation_info", job.result_generation_info},
|
||||
};
|
||||
} else {
|
||||
json images = json::array();
|
||||
for (size_t i = 0; i < job.result_images_b64.size(); ++i) {
|
||||
images.push_back({{"index", i}, {"b64_json", job.result_images_b64[i]}});
|
||||
images.push_back({{"index", i}, {"b64_json", job.result_images_b64[i]}, {"generation_info", job.result_generation_info.at(i)}});
|
||||
}
|
||||
result["result"] = {
|
||||
{"output_format", job.img_gen.output_format},
|
||||
@@ -150,41 +236,64 @@ json make_async_job_json(const AsyncJobManager& manager, const AsyncGenerationJo
|
||||
job.status == AsyncJobStatus::Cancelled) {
|
||||
result["result"] = nullptr;
|
||||
result["error"] = {
|
||||
{"code",
|
||||
{"code",
|
||||
job.error_code.empty()
|
||||
? (job.status == AsyncJobStatus::Cancelled ? "cancelled" : "generation_failed")
|
||||
: job.error_code},
|
||||
{"message", job.error_message},
|
||||
? (job.status == AsyncJobStatus::Cancelled ? "cancelled" : "generation_failed")
|
||||
: job.error_code},
|
||||
{"message", job.error_message},
|
||||
};
|
||||
} else {
|
||||
result["result"] = nullptr;
|
||||
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;
|
||||
}
|
||||
|
||||
bool execute_img_gen_job(ServerRuntime& runtime,
|
||||
AsyncGenerationJob& job,
|
||||
std::vector<std::string>& output_images,
|
||||
json& output_info,
|
||||
std::string& error_message) {
|
||||
output_info = json::array();
|
||||
sd_img_gen_params_t params = job.img_gen.to_sd_img_gen_params_t();
|
||||
|
||||
SDImageVec results;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
|
||||
sd_image_t* raw_results = nullptr;
|
||||
int num_results = 0;
|
||||
if (!generate_image(runtime.sd_ctx, ¶ms, &raw_results, &num_results)) {
|
||||
raw_results = nullptr;
|
||||
num_results = 0;
|
||||
if (job.cancel_requested.load()) {
|
||||
return false;
|
||||
}
|
||||
JobCancellationMonitor cancellation(runtime.sd_ctx, job);
|
||||
set_preview_callback(job);
|
||||
sd_image_generation_result_t* generated = nullptr;
|
||||
generate_image(runtime.sd_ctx, ¶ms, &generated);
|
||||
results.adopt(generated, runtime.ctx_params->lora_model_dir);
|
||||
clear_preview_callback();
|
||||
if (job.cancel_requested.load()) {
|
||||
return false;
|
||||
}
|
||||
if (!results.empty() && !apply_server_adetailer(runtime, job.img_gen, params, results)) {
|
||||
error_message = "ADetailer failed";
|
||||
return false;
|
||||
}
|
||||
results.adopt(raw_results, num_results);
|
||||
}
|
||||
|
||||
const int num_results = results.count();
|
||||
if (num_results <= 0) {
|
||||
if (job.cancel_requested.load() || num_results <= 0) {
|
||||
error_message = "generate_image returned no results";
|
||||
return false;
|
||||
}
|
||||
@@ -196,17 +305,13 @@ bool execute_img_gen_job(ServerRuntime& runtime,
|
||||
encoded_format = EncodedImageFormat::WEBP;
|
||||
}
|
||||
|
||||
int batch_count = job.img_gen.gen_params.batch_count;
|
||||
int images_per_batch = batch_count > 0 ? std::max(1, num_results / batch_count) : 1;
|
||||
for (int i = 0; i < num_results; ++i) {
|
||||
if (results[i].data == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string metadata = job.img_gen.gen_params.embed_image_metadata
|
||||
? get_image_params(*runtime.ctx_params,
|
||||
job.img_gen.gen_params,
|
||||
job.img_gen.gen_params.seed + i / images_per_batch)
|
||||
? results.generation_info(i)
|
||||
: "";
|
||||
auto image_bytes = encode_image_to_vector(encoded_format,
|
||||
results[i].data,
|
||||
@@ -219,6 +324,7 @@ bool execute_img_gen_job(ServerRuntime& runtime,
|
||||
continue;
|
||||
}
|
||||
output_images.push_back(base64_encode(image_bytes));
|
||||
output_info.push_back(json::parse(results.generation_info(i)));
|
||||
}
|
||||
|
||||
if (output_images.empty()) {
|
||||
@@ -235,11 +341,10 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
std::string& output_media_mime_type,
|
||||
int& output_frame_count,
|
||||
int& output_fps,
|
||||
json& output_info,
|
||||
std::string& error_message) {
|
||||
sd_vid_gen_params_t params = job.vid_gen.to_sd_vid_gen_params_t();
|
||||
std::string str_params = job.vid_gen.gen_params.embed_image_metadata
|
||||
? get_image_params(*runtime.ctx_params, job.vid_gen.gen_params, job.vid_gen.gen_params.seed, VID_GEN)
|
||||
: "";
|
||||
std::string str_params;
|
||||
|
||||
SDImageVec results;
|
||||
int num_results = 0;
|
||||
@@ -247,15 +352,26 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
|
||||
sd_image_t* raw_results = nullptr;
|
||||
if (!generate_video(runtime.sd_ctx, ¶ms, &raw_results, &num_results, &generated_audio, &output_fps)) {
|
||||
raw_results = nullptr;
|
||||
if (job.cancel_requested.load()) {
|
||||
return false;
|
||||
}
|
||||
results.adopt(raw_results, num_results);
|
||||
JobCancellationMonitor cancellation(runtime.sd_ctx, job);
|
||||
set_preview_callback(job);
|
||||
sd_video_generation_result_t* generated = nullptr;
|
||||
if (generate_video(runtime.sd_ctx, ¶ms, &generated)) {
|
||||
output_fps = generated->fps;
|
||||
generated_audio = generated->audio;
|
||||
generated->audio = nullptr;
|
||||
results.adopt_video(generated, runtime.ctx_params->lora_model_dir);
|
||||
output_info = json::parse(results.generation_info(0));
|
||||
if (job.vid_gen.gen_params.embed_image_metadata)
|
||||
str_params = results.generation_info(0);
|
||||
}
|
||||
clear_preview_callback();
|
||||
}
|
||||
|
||||
num_results = results.count();
|
||||
if (num_results <= 0) {
|
||||
if (job.cancel_requested.load() || num_results <= 0) {
|
||||
free_sd_audio(generated_audio);
|
||||
error_message = "generate_video returned no results";
|
||||
return false;
|
||||
@@ -308,10 +424,11 @@ void async_job_worker(ServerRuntime& runtime) {
|
||||
|
||||
job = it->second;
|
||||
job->status = AsyncJobStatus::Generating;
|
||||
job->started_at = unix_timestamp_now();
|
||||
job->started_at = unix_timestamp_now_ms();
|
||||
}
|
||||
|
||||
std::vector<std::string> output_images;
|
||||
json output_info;
|
||||
std::string output_media_b64;
|
||||
std::string output_media_mime_type;
|
||||
int output_frame_count = 0;
|
||||
@@ -320,7 +437,7 @@ void async_job_worker(ServerRuntime& runtime) {
|
||||
bool ok = false;
|
||||
|
||||
if (job->kind == AsyncJobKind::ImgGen) {
|
||||
ok = execute_img_gen_job(runtime, *job, output_images, error_message);
|
||||
ok = execute_img_gen_job(runtime, *job, output_images, output_info, error_message);
|
||||
} else if (job->kind == AsyncJobKind::VidGen) {
|
||||
ok = execute_vid_gen_job(runtime,
|
||||
*job,
|
||||
@@ -328,6 +445,7 @@ void async_job_worker(ServerRuntime& runtime) {
|
||||
output_media_mime_type,
|
||||
output_frame_count,
|
||||
output_fps,
|
||||
output_info,
|
||||
error_message);
|
||||
} else {
|
||||
error_message = "unsupported job kind";
|
||||
@@ -340,10 +458,15 @@ void async_job_worker(ServerRuntime& runtime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
job->completed_at = unix_timestamp_now();
|
||||
if (ok) {
|
||||
job->completed_at = unix_timestamp_now_ms();
|
||||
if (job->cancel_requested.load()) {
|
||||
job->status = AsyncJobStatus::Cancelled;
|
||||
job->error_code = "cancelled";
|
||||
job->error_message = "job cancelled by client";
|
||||
} else if (ok) {
|
||||
job->status = AsyncJobStatus::Completed;
|
||||
job->result_images_b64 = std::move(output_images);
|
||||
job->result_generation_info = std::move(output_info);
|
||||
job->result_media_b64 = std::move(output_media_b64);
|
||||
job->result_media_mime_type = std::move(output_media_mime_type);
|
||||
job->result_frame_count = output_frame_count;
|
||||
@@ -355,6 +478,7 @@ void async_job_worker(ServerRuntime& runtime) {
|
||||
job->error_code = "generation_failed";
|
||||
job->error_message = error_message.empty() ? "unknown generation error" : error_message;
|
||||
job->result_images_b64.clear();
|
||||
job->result_generation_info = nullptr;
|
||||
job->result_media_b64.clear();
|
||||
job->result_media_mime_type.clear();
|
||||
job->result_frame_count = 0;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
@@ -32,18 +33,25 @@ struct AsyncGenerationJob {
|
||||
std::string id;
|
||||
AsyncJobKind kind = AsyncJobKind::ImgGen;
|
||||
AsyncJobStatus status = AsyncJobStatus::Queued;
|
||||
int64_t created_at = unix_timestamp_now();
|
||||
int64_t started_at = 0;
|
||||
int64_t completed_at = 0;
|
||||
std::atomic<bool> cancel_requested{false};
|
||||
int64_t created_at = unix_timestamp_now_ms();
|
||||
int64_t started_at = 0;
|
||||
int64_t completed_at = 0;
|
||||
ImgGenJobRequest img_gen;
|
||||
VidGenJobRequest vid_gen;
|
||||
std::vector<std::string> result_images_b64;
|
||||
json result_generation_info;
|
||||
std::string result_media_b64;
|
||||
std::string result_media_mime_type;
|
||||
int result_frame_count = 0;
|
||||
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 {
|
||||
@@ -67,6 +75,7 @@ json make_async_job_json(const AsyncJobManager& manager, const AsyncGenerationJo
|
||||
bool execute_img_gen_job(ServerRuntime& runtime,
|
||||
AsyncGenerationJob& job,
|
||||
std::vector<std::string>& output_images,
|
||||
json& output_info,
|
||||
std::string& error_message);
|
||||
bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
AsyncGenerationJob& job,
|
||||
@@ -74,5 +83,6 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
std::string& output_media_mime_type,
|
||||
int& output_frame_count,
|
||||
int& output_fps,
|
||||
json& output_info,
|
||||
std::string& error_message);
|
||||
void async_job_worker(ServerRuntime& runtime);
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "async_jobs.h"
|
||||
#include "common/common.h"
|
||||
#include "common/resource_owners.hpp"
|
||||
#include "model_catalog.h"
|
||||
#include "routes.h"
|
||||
#include "runtime.h"
|
||||
|
||||
@@ -47,10 +48,14 @@ static void parse_args(int argc,
|
||||
log_level = svr_params.log_level;
|
||||
log_color = svr_params.color;
|
||||
|
||||
if (ctx_params.list_devices) {
|
||||
exit(ctx_params.print_devices() ? 0 : 1);
|
||||
}
|
||||
|
||||
const bool random_seed_requested = default_gen_params.seed < 0;
|
||||
|
||||
if (!svr_params.resolve_and_validate() ||
|
||||
!ctx_params.resolve_and_validate(IMG_GEN) ||
|
||||
!ctx_params.resolve_and_validate(ctx_params.model_path.empty() && ctx_params.diffusion_model_path.empty() ? METADATA : IMG_GEN) ||
|
||||
!default_gen_params.resolve_and_validate(IMG_GEN,
|
||||
ctx_params.lora_model_dir,
|
||||
ctx_params.hires_upscalers_dir)) {
|
||||
@@ -75,6 +80,8 @@ 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;
|
||||
|
||||
sd_set_log_callback(sd_log_cb, (void*)&svr_params);
|
||||
@@ -82,14 +89,19 @@ int main(int argc, const char** argv) {
|
||||
|
||||
LOG_VERBOSE("version: %s", version_string().c_str());
|
||||
LOG_VERBOSE("%s", sd_get_system_info());
|
||||
LOG_VERBOSE("%s", svr_params.to_string().c_str());
|
||||
LOG_VERBOSE("%s", ctx_params.to_string().c_str());
|
||||
LOG_VERBOSE("%s", default_gen_params.to_string().c_str());
|
||||
LOG_DEBUG("%s", svr_params.to_string().c_str());
|
||||
LOG_DEBUG("%s", ctx_params.to_string().c_str());
|
||||
LOG_DEBUG("%s", default_gen_params.to_string().c_str());
|
||||
|
||||
sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(false);
|
||||
SDCtxPtr sd_ctx(new_sd_ctx(&sd_ctx_params));
|
||||
sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t();
|
||||
ServerModels models;
|
||||
if (!ctx_params.model_path.empty() || !ctx_params.diffusion_model_path.empty()) {
|
||||
const auto started = std::chrono::steady_clock::now();
|
||||
models.context.reset(new_sd_ctx(&sd_ctx_params));
|
||||
models.status["load_elapsed_seconds"] = std::chrono::duration<double>(std::chrono::steady_clock::now() - started).count();
|
||||
}
|
||||
|
||||
if (sd_ctx == nullptr) {
|
||||
if ((!ctx_params.model_path.empty() || !ctx_params.diffusion_model_path.empty()) && !models.context) {
|
||||
LOG_ERROR("new_sd_ctx_t failed");
|
||||
return 1;
|
||||
}
|
||||
@@ -102,7 +114,7 @@ int main(int argc, const char** argv) {
|
||||
std::mutex upscaler_mutex;
|
||||
AsyncJobManager async_job_manager;
|
||||
ServerRuntime runtime = {
|
||||
sd_ctx.get(),
|
||||
models.context.get(),
|
||||
&sd_ctx_mutex,
|
||||
&svr_params,
|
||||
&ctx_params,
|
||||
@@ -112,8 +124,16 @@ int main(int argc, const char** argv) {
|
||||
&upscaler_cache,
|
||||
&upscaler_mutex,
|
||||
&async_job_manager,
|
||||
&models,
|
||||
};
|
||||
|
||||
try {
|
||||
initialize_server_models(runtime);
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("model catalog initialization failed: %s", e.what());
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::thread async_worker(async_job_worker, std::ref(runtime));
|
||||
|
||||
httplib::Server svr;
|
||||
@@ -145,6 +165,7 @@ int main(int argc, const char** argv) {
|
||||
register_openai_api_endpoints(svr, runtime);
|
||||
register_sdapi_endpoints(svr, runtime);
|
||||
register_sdcpp_api_endpoints(svr, runtime);
|
||||
register_model_endpoints(svr, runtime);
|
||||
|
||||
LOG_INFO("listening on: http://%s:%d\n", svr_params.listen_ip.c_str(), svr_params.listen_port);
|
||||
svr.listen(svr_params.listen_ip, svr_params.listen_port);
|
||||
|
||||
@@ -0,0 +1,710 @@
|
||||
#include "model_catalog.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "common/log.h"
|
||||
#include "recipes_builtin.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
struct ModelSlot {
|
||||
const char* id;
|
||||
const char* label;
|
||||
std::vector<std::string> categories;
|
||||
std::string SDContextParams::* field;
|
||||
};
|
||||
|
||||
static const ModelSlot k_model_slots[] = {
|
||||
{"model", "Checkpoint", {"checkpoints"}, &SDContextParams::model_path},
|
||||
{"diffusion_model", "Diffusion model", {"diffusion_models"}, &SDContextParams::diffusion_model_path},
|
||||
{"high_noise_diffusion_model", "High-noise diffusion model", {"diffusion_models"}, &SDContextParams::high_noise_diffusion_model_path},
|
||||
{"uncond_diffusion_model", "Unconditional diffusion model", {"diffusion_models"}, &SDContextParams::uncond_diffusion_model_path},
|
||||
{"clip_l", "CLIP-L", {"text_encoders"}, &SDContextParams::clip_l_path},
|
||||
{"clip_g", "CLIP-G", {"text_encoders"}, &SDContextParams::clip_g_path},
|
||||
{"clip_vision", "CLIP Vision", {"clip_vision"}, &SDContextParams::clip_vision_path},
|
||||
{"t5xxl", "T5", {"text_encoders"}, &SDContextParams::t5xxl_path},
|
||||
{"llm", "LLM Text Encoder", {"text_encoders"}, &SDContextParams::llm_path},
|
||||
{"llm_vision", "LLM Vision Encoder", {"text_encoders", "clip_vision"}, &SDContextParams::llm_vision_path},
|
||||
{"tokenizer", "Tokenizer", {"tokenizers"}, &SDContextParams::tokenizer},
|
||||
{"vae", "VAE", {"vae"}, &SDContextParams::vae_path},
|
||||
{"audio_vae", "Audio VAE", {"vae"}, &SDContextParams::audio_vae_path},
|
||||
{"audio_encoder", "Audio Encoder", {"text_encoders"}, &SDContextParams::audio_encoder_path},
|
||||
{"embeddings_connectors", "Embeddings Connectors", {"text_encoders"}, &SDContextParams::embeddings_connectors_path},
|
||||
{"tae", "TAE", {"vae", "vae_approx"}, &SDContextParams::taesd_path},
|
||||
{"taesd", "TAE", {"vae_approx", "vae"}, &SDContextParams::taesd_path},
|
||||
{"control_net", "ControlNet", {"controlnet"}, &SDContextParams::control_net_path},
|
||||
{"ip_adapter", "IP-Adapter", {"ipadapter"}, &SDContextParams::ip_adapter_path},
|
||||
{"motion_module", "Motion Module", {"motion_modules"}, &SDContextParams::motion_module_path},
|
||||
{"photo_maker", "PhotoMaker", {"photomaker"}, &SDContextParams::photo_maker_path},
|
||||
{"pulid_weights", "PuLID", {"pulid"}, &SDContextParams::pulid_weights_path},
|
||||
};
|
||||
|
||||
static const ModelSlot* find_model_slot(const std::string& id) {
|
||||
for (const auto& slot : k_model_slots) {
|
||||
if (id == slot.id) {
|
||||
return &slot;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool matches_model_category(const ModelSlot& slot, const std::string& category) {
|
||||
return std::find(slot.categories.begin(), slot.categories.end(), category) != slot.categories.end();
|
||||
}
|
||||
|
||||
static void clear_model_paths(SDContextParams& params) {
|
||||
for (const auto& slot : k_model_slots) {
|
||||
(params.*slot.field).clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::string model_path_key(const fs::path& path) {
|
||||
#ifdef _WIN32
|
||||
const auto value = path.generic_wstring();
|
||||
const int length = LCMapStringEx(LOCALE_NAME_INVARIANT, LCMAP_LOWERCASE, value.data(),
|
||||
static_cast<int>(value.size()), nullptr, 0, nullptr, nullptr, 0);
|
||||
if (length > 0) {
|
||||
std::wstring folded(length, L'\0');
|
||||
LCMapStringEx(LOCALE_NAME_INVARIANT, LCMAP_LOWERCASE, value.data(),
|
||||
static_cast<int>(value.size()), &folded[0], length, nullptr, nullptr, 0);
|
||||
return fs::path(folded).generic_u8string();
|
||||
}
|
||||
#endif
|
||||
return path.generic_u8string();
|
||||
}
|
||||
|
||||
bool model_path_inside(const fs::path& root, const fs::path& path) {
|
||||
auto parent = root.begin();
|
||||
auto child = path.begin();
|
||||
for (; parent != root.end(); ++parent, ++child) {
|
||||
if (child == path.end() || model_path_key(*parent) != model_path_key(*child)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return child != path.end();
|
||||
}
|
||||
|
||||
ModelRoot make_model_root(const fs::path& path, bool primary) {
|
||||
ModelRoot root;
|
||||
const auto absolute = fs::absolute(path);
|
||||
std::error_code ec;
|
||||
root.path = fs::weakly_canonical(absolute, ec);
|
||||
if (ec)
|
||||
root.path = absolute.lexically_normal();
|
||||
root.primary = primary;
|
||||
uint64_t hash = 14695981039346656037ULL;
|
||||
for (unsigned char c : model_path_key(root.path)) {
|
||||
hash ^= c;
|
||||
hash *= 1099511628211ULL;
|
||||
}
|
||||
std::ostringstream id;
|
||||
id << '@' << std::hex << std::setfill('0') << std::setw(16) << hash;
|
||||
root.id = id.str();
|
||||
root.label = root.path.filename().u8string() + " [" + root.id.substr(1, 8) + "]";
|
||||
return root;
|
||||
}
|
||||
|
||||
std::vector<ModelRoot> get_model_roots(const SDSvrParams& params) {
|
||||
const auto directories = params.model_dirs.empty() ? std::vector<std::string>{"models"} : params.model_dirs;
|
||||
std::vector<ModelRoot> roots;
|
||||
std::set<std::string> seen;
|
||||
for (size_t i = 0; i < directories.size(); ++i) {
|
||||
auto root = make_model_root(fs::u8path(directories[i]), i == 0);
|
||||
if (seen.insert(model_path_key(root.path)).second)
|
||||
roots.push_back(std::move(root));
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
std::vector<fs::path> model_category_directories(const ModelRoot& root, const std::string& category) {
|
||||
std::vector<fs::path> directories;
|
||||
if (!fs::exists(root.path))
|
||||
return directories;
|
||||
for (const auto& entry : fs::directory_iterator(root.path)) {
|
||||
auto name = entry.path().filename().u8string();
|
||||
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
if (name == category || (category == "checkpoints" && name == "stable-diffusion") ||
|
||||
(category == "loras" && name == "lora")) {
|
||||
directories.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
std::sort(directories.begin(), directories.end(), [&](const fs::path& a, const fs::path& b) {
|
||||
const bool a_standard = a.filename().u8string() == category;
|
||||
const bool b_standard = b.filename().u8string() == category;
|
||||
return a_standard != b_standard ? a_standard : a < b;
|
||||
});
|
||||
return directories;
|
||||
}
|
||||
|
||||
static bool valid_identifier(const std::string& id) {
|
||||
return !id.empty() && id.size() <= 128 &&
|
||||
std::all_of(id.begin(), id.end(), [](unsigned char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.';
|
||||
});
|
||||
}
|
||||
|
||||
static void check_keys(const json& object, const std::set<std::string>& keys) {
|
||||
if (!object.is_object()) {
|
||||
throw std::runtime_error("expected an object");
|
||||
}
|
||||
for (auto it = object.begin(); it != object.end(); ++it) {
|
||||
if (!keys.count(it.key())) {
|
||||
throw std::runtime_error("unknown field: " + it.key());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void apply_recipe_defaults(const json& defaults, SDGenerationParams& params) {
|
||||
check_keys(defaults, {"width", "height", "steps", "cfg_scale", "strength", "distilled_guidance", "sampling_method", "scheduler", "custom_sigmas",
|
||||
"video_frames", "fps", "flow_shift", "moe_boundary", "high_noise_steps", "high_noise_cfg_scale",
|
||||
"high_noise_flow_shift", "high_noise_sampling_method", "high_noise_scheduler", "img_cfg_scale", "shifted_timestep"});
|
||||
for (const char* key : {"width", "height", "steps", "video_frames", "fps", "high_noise_steps", "shifted_timestep"}) {
|
||||
if (!defaults.contains(key)) {
|
||||
continue;
|
||||
}
|
||||
if (!defaults[key].is_number_integer()) {
|
||||
throw std::runtime_error(std::string(key) + " must be an integer");
|
||||
}
|
||||
const int64_t value = defaults[key].get<int64_t>();
|
||||
const std::string field = key;
|
||||
const bool dimension = field == "width" || field == "height";
|
||||
const int64_t maximum = dimension || field == "video_frames" ? 4096 : field == "fps" ? 240
|
||||
: 1000;
|
||||
const int64_t minimum = dimension ? 64 : field == "shifted_timestep" ? 0
|
||||
: 1;
|
||||
if ((value < minimum || value > maximum) && !(field == "high_noise_steps" && value == -1)) {
|
||||
throw std::runtime_error(std::string(key) + " is out of range");
|
||||
}
|
||||
if (std::string(key) == "width")
|
||||
params.width = static_cast<int>(value);
|
||||
if (std::string(key) == "height")
|
||||
params.height = static_cast<int>(value);
|
||||
if (std::string(key) == "steps")
|
||||
params.sample_params.sample_steps = static_cast<int>(value);
|
||||
if (field == "shifted_timestep")
|
||||
params.sample_params.shifted_timestep = static_cast<int>(value);
|
||||
if (field == "video_frames")
|
||||
params.video_frames = static_cast<int>(value);
|
||||
if (field == "fps")
|
||||
params.fps = static_cast<int>(value);
|
||||
if (field == "high_noise_steps")
|
||||
params.high_noise_sample_params.sample_steps = static_cast<int>(value);
|
||||
}
|
||||
for (const char* key : {"cfg_scale", "strength", "moe_boundary", "img_cfg_scale", "distilled_guidance", "flow_shift", "high_noise_cfg_scale", "high_noise_flow_shift"}) {
|
||||
if (!defaults.contains(key)) {
|
||||
continue;
|
||||
}
|
||||
if (!defaults[key].is_number()) {
|
||||
throw std::runtime_error(std::string(key) + " must be a number");
|
||||
}
|
||||
const float value = defaults[key].get<float>();
|
||||
if (!std::isfinite(value) || value < 0 || value > ((std::string(key) == "strength" || std::string(key) == "moe_boundary") ? 1.f : 1000.f)) {
|
||||
throw std::runtime_error(std::string(key) + " is out of range");
|
||||
}
|
||||
if (std::string(key) == "strength")
|
||||
params.strength = value;
|
||||
if (std::string(key) == "moe_boundary")
|
||||
params.moe_boundary = value;
|
||||
if (std::string(key) == "cfg_scale")
|
||||
params.sample_params.guidance.txt_cfg = value;
|
||||
if (std::string(key) == "img_cfg_scale")
|
||||
params.sample_params.guidance.img_cfg = value;
|
||||
if (std::string(key) == "distilled_guidance")
|
||||
params.sample_params.guidance.distilled_guidance = value;
|
||||
if (std::string(key) == "flow_shift")
|
||||
params.sample_params.flow_shift = value;
|
||||
if (std::string(key) == "high_noise_cfg_scale")
|
||||
params.high_noise_sample_params.guidance.txt_cfg = value;
|
||||
if (std::string(key) == "high_noise_flow_shift")
|
||||
params.high_noise_sample_params.flow_shift = value;
|
||||
}
|
||||
if (defaults.contains("custom_sigmas")) {
|
||||
const auto& sigmas = defaults.at("custom_sigmas");
|
||||
if (!sigmas.is_array() || sigmas.size() == 1 || sigmas.size() > 1001)
|
||||
throw std::runtime_error("custom_sigmas must be empty or contain 2-1001 values");
|
||||
for (const auto& sigma : sigmas) {
|
||||
if (!sigma.is_number() || !std::isfinite(sigma.get<float>()) || sigma.get<float>() < 0)
|
||||
throw std::runtime_error("custom_sigmas must contain finite nonnegative numbers");
|
||||
}
|
||||
params.custom_sigmas = sigmas.get<std::vector<float>>();
|
||||
}
|
||||
for (const char* key : {"sampling_method", "high_noise_sampling_method"}) {
|
||||
if (!defaults.contains(key))
|
||||
continue;
|
||||
auto& sample = std::string(key) == "sampling_method" ? params.sample_params : params.high_noise_sample_params;
|
||||
const auto name = defaults.at(key).get<std::string>();
|
||||
bool found = false;
|
||||
for (int i = 0; i < SAMPLE_METHOD_COUNT; ++i) {
|
||||
if (name == sd_sample_method_name(static_cast<sample_method_t>(i))) {
|
||||
sample.sample_method = static_cast<sample_method_t>(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
throw std::runtime_error(std::string("unknown ") + key);
|
||||
}
|
||||
for (const char* key : {"scheduler", "high_noise_scheduler"}) {
|
||||
if (!defaults.contains(key))
|
||||
continue;
|
||||
auto& sample = std::string(key) == "scheduler" ? params.sample_params : params.high_noise_sample_params;
|
||||
const auto name = defaults.at(key).get<std::string>();
|
||||
bool found = false;
|
||||
for (int i = 0; i < SCHEDULER_COUNT; ++i) {
|
||||
if (name == sd_scheduler_name(static_cast<scheduler_t>(i)) ||
|
||||
(name == "normal" && i == DISCRETE_SCHEDULER)) {
|
||||
sample.scheduler = static_cast<scheduler_t>(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
throw std::runtime_error(std::string("unknown ") + key);
|
||||
}
|
||||
}
|
||||
|
||||
static void apply_recipe_context(const json& settings, SDContextParams& params) {
|
||||
check_keys(settings, {"vae_format", "model_args", "backend", "params_backend"});
|
||||
if (settings.contains("vae_format")) {
|
||||
const auto format = settings.at("vae_format").get<std::string>();
|
||||
if (format != "auto" && format != "flux" && format != "sd3" && format != "flux2" && format != "wan")
|
||||
throw std::runtime_error("unknown vae_format");
|
||||
params.vae_format = format;
|
||||
}
|
||||
if (settings.contains("model_args")) {
|
||||
params.model_args = settings.at("model_args").get<std::string>();
|
||||
}
|
||||
if (settings.contains("backend")) {
|
||||
params.backend = settings.at("backend").get<std::string>();
|
||||
}
|
||||
if (settings.contains("params_backend")) {
|
||||
params.params_backend = settings.at("params_backend").get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
// Known recipe feature names with their defaults; recipes may flip any of these.
|
||||
static const std::map<std::string, bool> k_recipe_features = {
|
||||
{"img2img", true},
|
||||
{"mask_image", true},
|
||||
{"lora", true},
|
||||
{"vae_tiling", true},
|
||||
{"cache", true},
|
||||
{"cache_dit", true},
|
||||
{"preview", true},
|
||||
{"flow_shift", false},
|
||||
{"distilled_guidance", false},
|
||||
{"shifted_timestep", false},
|
||||
{"img_cfg", false},
|
||||
{"slg", false},
|
||||
{"clip_skip", false},
|
||||
{"ref_images", false},
|
||||
{"controlnet", false},
|
||||
{"control_image", false},
|
||||
{"ip_adapter", false},
|
||||
{"init_image", false},
|
||||
{"end_image", false},
|
||||
{"control_frames", false},
|
||||
{"ref_videos", false},
|
||||
{"ref_audios", false},
|
||||
{"ref_video_audios", false},
|
||||
{"vace", false},
|
||||
{"high_noise", false},
|
||||
{"cache_unet", false},
|
||||
{"preview_tae", false},
|
||||
};
|
||||
|
||||
static void validate_recipe_features(const json& recipe) {
|
||||
for (const char* key : {"features", "disabled_features"}) {
|
||||
if (!recipe.contains(key))
|
||||
continue;
|
||||
const auto& values = recipe.at(key);
|
||||
if (!values.is_array() || values.size() > k_recipe_features.size())
|
||||
throw std::runtime_error(std::string(key) + " must be an array of feature names");
|
||||
std::set<std::string> seen;
|
||||
for (const auto& value : values) {
|
||||
const auto name = value.get<std::string>();
|
||||
if (!k_recipe_features.count(name) || !seen.insert(name).second)
|
||||
throw std::runtime_error("unknown or duplicate feature: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json resolve_recipe_features(const json& recipe) {
|
||||
json features = k_recipe_features;
|
||||
for (const auto& slot : recipe.at("slots")) {
|
||||
const auto id = slot.at("id").get<std::string>();
|
||||
if (id == "ip_adapter")
|
||||
features["ip_adapter"] = true;
|
||||
if (id == "control_net")
|
||||
features["controlnet"] = true;
|
||||
if (id == "high_noise_diffusion_model")
|
||||
features["high_noise"] = true;
|
||||
if (id == "tae" || id == "taesd")
|
||||
features["preview_tae"] = true;
|
||||
}
|
||||
for (const auto& name : recipe.value("features", json::array()))
|
||||
features[name.get<std::string>()] = true;
|
||||
for (const auto& name : recipe.value("disabled_features", json::array()))
|
||||
features[name.get<std::string>()] = false;
|
||||
return features;
|
||||
}
|
||||
|
||||
json model_generation_features(const ServerRuntime& runtime) {
|
||||
if (!runtime.sd_ctx || runtime.models->active_recipe.is_null()) {
|
||||
json features = k_recipe_features;
|
||||
for (auto& feature : features)
|
||||
feature = false;
|
||||
return features;
|
||||
}
|
||||
json features = resolve_recipe_features(runtime.models->active_recipe);
|
||||
if (runtime.models->active_recipe.value("source", "") == "command_line")
|
||||
return features;
|
||||
// Optional components only become usable once selected and loaded.
|
||||
features["ip_adapter"] = features.value("ip_adapter", false) && !runtime.ctx_params->ip_adapter_path.empty();
|
||||
features["controlnet"] = features.value("controlnet", false) && !runtime.ctx_params->control_net_path.empty();
|
||||
features["high_noise"] = features.value("high_noise", false) && !runtime.ctx_params->high_noise_diffusion_model_path.empty();
|
||||
features["preview_tae"] = features.value("preview_tae", false) && !runtime.ctx_params->taesd_path.empty();
|
||||
features["ref_audios"] = features.value("ref_audios", false) &&
|
||||
(!runtime.ctx_params->audio_vae_path.empty() || !runtime.ctx_params->audio_encoder_path.empty());
|
||||
features["ref_video_audios"] = features.value("ref_video_audios", false) && !runtime.ctx_params->audio_vae_path.empty();
|
||||
if (!runtime.ctx_params->pulid_weights_path.empty())
|
||||
features["slg"] = false;
|
||||
return features;
|
||||
}
|
||||
|
||||
static void validate_video_constraints(const json& constraints) {
|
||||
check_keys(constraints, {"frame_step", "frame_offset", "frame_rounding", "min_frames", "max_frames", "fixed_fps"});
|
||||
for (const auto* key : {"frame_step", "frame_offset", "min_frames", "max_frames", "fixed_fps"}) {
|
||||
const int minimum = std::string(key) == "frame_offset" ? 0 : 1;
|
||||
const int maximum = std::string(key) == "fixed_fps" ? 240 : 4096;
|
||||
if (constraints.contains(key) &&
|
||||
(!constraints[key].is_number_integer() || constraints[key] < minimum || constraints[key] > maximum)) {
|
||||
throw std::runtime_error(std::string("video_constraints.") + key + " is outside its integer range");
|
||||
}
|
||||
}
|
||||
const int step = constraints.value("frame_step", 1);
|
||||
const int offset = constraints.value("frame_offset", 0);
|
||||
const int minimum = constraints.value("min_frames", 1);
|
||||
const int maximum = constraints.value("max_frames", 4096);
|
||||
if (offset >= step || minimum > maximum ||
|
||||
offset + std::max(0, (minimum - offset + step - 1) / step) * step > maximum) {
|
||||
throw std::runtime_error("video_constraints must describe a nonempty frame range with frame_offset < frame_step");
|
||||
}
|
||||
if (constraints.contains("frame_rounding") &&
|
||||
constraints["frame_rounding"] != "floor" && constraints["frame_rounding"] != "ceil") {
|
||||
throw std::runtime_error("video_constraints.frame_rounding must be floor or ceil");
|
||||
}
|
||||
}
|
||||
|
||||
static void validate_recipe(const json& recipe) {
|
||||
check_keys(recipe, {"schema_version", "id", "name", "description", "model_url", "slots", "required_any", "defaults", "context", "features", "disabled_features", "size_multiple", "video_constraints"});
|
||||
if (!recipe.at("schema_version").is_number_integer() || recipe.at("schema_version") != 1 ||
|
||||
!valid_identifier(recipe.at("id").get<std::string>()) || recipe.at("name").get<std::string>().empty()) {
|
||||
throw std::runtime_error("invalid schema_version, id or name");
|
||||
}
|
||||
if (recipe.contains("description") && !recipe["description"].is_string()) {
|
||||
throw std::runtime_error("description must be a string");
|
||||
}
|
||||
if (recipe.contains("size_multiple") &&
|
||||
(!recipe["size_multiple"].is_number_integer() || recipe["size_multiple"] < 1 || recipe["size_multiple"] > 4096)) {
|
||||
throw std::runtime_error("size_multiple must be an integer from 1 to 4096");
|
||||
}
|
||||
if (recipe.contains("video_constraints")) {
|
||||
validate_video_constraints(recipe["video_constraints"]);
|
||||
}
|
||||
if (recipe.contains("model_url")) {
|
||||
const auto url = recipe.at("model_url").get<std::string>();
|
||||
const std::string prefix = "https://huggingface.co/";
|
||||
if (url.size() <= prefix.size() || url.size() > 2048 || url.rfind(prefix, 0) != 0 ||
|
||||
std::any_of(url.begin(), url.end(), [](unsigned char c) { return std::isspace(c) || std::iscntrl(c); })) {
|
||||
throw std::runtime_error("model_url must be a Hugging Face HTTPS URL");
|
||||
}
|
||||
}
|
||||
const auto& slots = recipe.at("slots");
|
||||
if (!slots.is_array() || slots.empty() || slots.size() > 32) {
|
||||
throw std::runtime_error("slots must be a nonempty array of at most 32 entries");
|
||||
}
|
||||
std::set<std::string> ids;
|
||||
bool has_required_model = false;
|
||||
for (const auto& slot : slots) {
|
||||
check_keys(slot, {"id", "label", "category", "required"});
|
||||
const auto id = slot.at("id").get<std::string>();
|
||||
const auto* spec = find_model_slot(id);
|
||||
if (!spec || !ids.insert(id == "taesd" ? "tae" : id).second ||
|
||||
(slot.contains("category") && !matches_model_category(*spec, slot.at("category").get<std::string>())) ||
|
||||
(slot.contains("label") && slot.at("label").get<std::string>().empty()) || !slot.at("required").is_boolean()) {
|
||||
throw std::runtime_error("invalid or duplicate model slot: " + id);
|
||||
}
|
||||
has_required_model |= (id == "model" || id == "diffusion_model") && slot["required"].get<bool>();
|
||||
}
|
||||
if (!has_required_model) {
|
||||
throw std::runtime_error("a required model or diffusion_model slot is needed");
|
||||
}
|
||||
if (recipe.contains("required_any")) {
|
||||
const auto& groups = recipe.at("required_any");
|
||||
if (!groups.is_array() || groups.size() > 32)
|
||||
throw std::runtime_error("required_any must be an array of at most 32 slot groups");
|
||||
for (const auto& group : groups) {
|
||||
if (!group.is_array() || group.size() < 2 || group.size() > slots.size())
|
||||
throw std::runtime_error("required_any groups need at least two declared slots");
|
||||
std::set<std::string> seen;
|
||||
for (const auto& value : group) {
|
||||
const auto id = value.get<std::string>();
|
||||
if (!seen.insert(id).second || std::none_of(slots.begin(), slots.end(), [&](const json& slot) { return slot.at("id") == id; }))
|
||||
throw std::runtime_error("unknown or duplicate required_any slot: " + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
validate_recipe_features(recipe);
|
||||
SDContextParams context;
|
||||
apply_recipe_context(recipe.value("context", json::object()), context);
|
||||
SDGenerationParams defaults;
|
||||
apply_recipe_defaults(recipe.value("defaults", json::object()), defaults);
|
||||
}
|
||||
|
||||
static json normalize_recipe(json recipe) {
|
||||
validate_recipe(recipe);
|
||||
recipe["size_multiple"] = recipe.value("size_multiple", 8);
|
||||
recipe["effective_features"] = resolve_recipe_features(recipe);
|
||||
for (auto& slot : recipe.at("slots")) {
|
||||
const auto& spec = *find_model_slot(slot.at("id").get<std::string>());
|
||||
slot["label"] = slot.value("label", std::string(spec.label));
|
||||
slot["category"] = slot.value("category", spec.categories.front());
|
||||
slot["categories"] = spec.categories;
|
||||
}
|
||||
return recipe;
|
||||
}
|
||||
|
||||
static bool supported_model_file(const fs::path& path, const std::string& category) {
|
||||
std::string ext = path.extension().u8string();
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
if (category == "tokenizers") {
|
||||
return ext == ".json";
|
||||
}
|
||||
return ext == ".gguf" || ext == ".safetensors" || ext == ".sft" ||
|
||||
ext == ".ckpt" || ext == ".pt" || ext == ".pth" || ext == ".bin";
|
||||
}
|
||||
|
||||
void refresh_model_catalog(ServerRuntime& runtime) {
|
||||
auto& models = *runtime.models;
|
||||
std::lock_guard<std::mutex> lock(models.catalog_mutex);
|
||||
std::map<std::string, json> recipes;
|
||||
std::map<std::string, ModelFile> files;
|
||||
json errors = json::array();
|
||||
auto report = [&](const std::string& file, const std::string& message) {
|
||||
errors.push_back({{"file", file}, {"message", message}});
|
||||
};
|
||||
for (const char* builtin : k_builtin_recipes) {
|
||||
auto recipe = normalize_recipe(json::parse(builtin));
|
||||
const auto id = recipe.at("id").get<std::string>();
|
||||
recipe["source"] = "builtin";
|
||||
recipes[id] = std::move(recipe);
|
||||
}
|
||||
|
||||
std::set<std::string> seen_files;
|
||||
for (const auto& source : get_model_roots(*runtime.svr_params)) {
|
||||
const auto& root = source.path;
|
||||
auto display_path = [&](const fs::path& path) {
|
||||
return source.id + "/" + path.lexically_relative(root).generic_u8string();
|
||||
};
|
||||
const fs::path recipe_dir = root / "recipes";
|
||||
std::map<std::string, std::vector<json>> user_recipes;
|
||||
try {
|
||||
if (fs::exists(recipe_dir)) {
|
||||
if (!model_path_inside(root, fs::canonical(recipe_dir)))
|
||||
throw std::runtime_error("recipes directory escapes model directory");
|
||||
for (const auto& entry : fs::directory_iterator(recipe_dir)) {
|
||||
const std::string display = display_path(entry.path());
|
||||
if (entry.path().extension() != ".json")
|
||||
continue;
|
||||
try {
|
||||
if (!entry.is_regular_file() || !model_path_inside(root, fs::canonical(entry.path())))
|
||||
throw std::runtime_error("recipe must be a file inside the model directory");
|
||||
if (entry.file_size() > 1024 * 1024)
|
||||
throw std::runtime_error("recipe exceeds 1 MiB");
|
||||
std::ifstream input(entry.path(), std::ios::binary);
|
||||
json recipe;
|
||||
input >> recipe;
|
||||
recipe = normalize_recipe(std::move(recipe));
|
||||
recipe["source"] = "user";
|
||||
recipe["file"] = entry.path().filename().u8string();
|
||||
recipe["root"] = source.id;
|
||||
const auto id = recipe.at("id").get<std::string>();
|
||||
user_recipes[id].push_back(std::move(recipe));
|
||||
} catch (const std::exception& e) {
|
||||
report(display, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
report(display_path(recipe_dir), e.what());
|
||||
}
|
||||
for (auto& item : user_recipes) {
|
||||
if (item.second.size() != 1) {
|
||||
for (const auto& recipe : item.second)
|
||||
report(source.id + "/recipes/" + recipe["file"].get<std::string>(), "duplicate user recipe id: " + item.first);
|
||||
continue;
|
||||
}
|
||||
const auto existing = recipes.find(item.first);
|
||||
if (existing != recipes.end()) {
|
||||
if (existing->second["source"] == "user") {
|
||||
LOG_WARN("recipe '%s' from %s is shadowed by an earlier model directory", item.first.c_str(), source.label.c_str());
|
||||
continue;
|
||||
}
|
||||
LOG_INFO("user recipe '%s' from %s overrides the builtin", item.first.c_str(), source.label.c_str());
|
||||
}
|
||||
recipes[item.first] = std::move(item.second.front());
|
||||
}
|
||||
|
||||
std::set<std::string> categories;
|
||||
for (const auto& slot : k_model_slots)
|
||||
categories.insert(slot.categories.begin(), slot.categories.end());
|
||||
for (const auto& category : categories) {
|
||||
try {
|
||||
for (const auto& directory : model_category_directories(source, category)) {
|
||||
if (!model_path_inside(root, fs::canonical(directory))) {
|
||||
report(display_path(directory), "category directory escapes model directory");
|
||||
continue;
|
||||
}
|
||||
for (fs::recursive_directory_iterator it(directory), end; it != end; ++it) {
|
||||
std::error_code path_error;
|
||||
const fs::path canonical = fs::canonical(it->path(), path_error);
|
||||
if (path_error || !model_path_inside(root, canonical)) {
|
||||
it.disable_recursion_pending();
|
||||
report(display_path(it->path()), path_error ? path_error.message() : "path escapes model directory");
|
||||
continue;
|
||||
}
|
||||
if (!it->is_regular_file() || !supported_model_file(it->path(), category) ||
|
||||
!seen_files.insert(model_path_key(canonical)).second)
|
||||
continue;
|
||||
const auto relative = it->path().lexically_relative(root).generic_u8string();
|
||||
const auto id = source.primary ? relative : source.id + "/" + relative;
|
||||
files[id] = {category, it->path().lexically_relative(directory).generic_u8string(), relative, source};
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
report(source.id + "/" + category, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
std::map<std::pair<std::string, std::string>, size_t> names;
|
||||
for (const auto& entry : files)
|
||||
++names[{entry.second.category, entry.second.name}];
|
||||
for (auto& entry : files) {
|
||||
auto& file = entry.second;
|
||||
if (names[{file.category, file.name}] > 1)
|
||||
file.name = (file.root.path / fs::u8path(file.relative_path)).generic_u8string();
|
||||
}
|
||||
models.recipes = std::move(recipes);
|
||||
models.files = std::move(files);
|
||||
models.catalog_errors = std::move(errors);
|
||||
}
|
||||
|
||||
json model_status(ServerRuntime& runtime) {
|
||||
std::lock_guard<std::mutex> lock(runtime.models->status_mutex);
|
||||
auto& models = *runtime.models;
|
||||
json status = models.status;
|
||||
if (status["state"] == "loading" && !status["load_elapsed_seconds"].is_null()) {
|
||||
status["load_elapsed_seconds"] = std::chrono::duration<double>(std::chrono::steady_clock::now() - models.load_started_at).count();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void initialize_server_models(ServerRuntime& runtime) {
|
||||
auto& models = *runtime.models;
|
||||
models.base_context = *runtime.ctx_params;
|
||||
clear_model_paths(models.base_context);
|
||||
models.base_defaults = *runtime.default_gen_params;
|
||||
if (runtime.sd_ctx) {
|
||||
models.status["state"] = "ready";
|
||||
models.status["origin"] = "command_line";
|
||||
json features = json::array();
|
||||
for (const auto& feature : k_recipe_features)
|
||||
features.push_back(feature.first);
|
||||
models.active_recipe = {
|
||||
{"id", "@command-line"},
|
||||
{"name", "Command-line model (all features)"},
|
||||
{"source", "command_line"},
|
||||
{"description", "Uses the loaded command-line model and its generation defaults."},
|
||||
{"slots", json::array()},
|
||||
{"defaults", json::object()},
|
||||
{"size_multiple", 8},
|
||||
{"features", features},
|
||||
};
|
||||
models.active_recipe["effective_features"] = resolve_recipe_features(models.active_recipe);
|
||||
models.status["runtime_recipe"] = models.active_recipe;
|
||||
}
|
||||
refresh_model_catalog(runtime);
|
||||
}
|
||||
|
||||
void prepare_model_load(ServerRuntime& runtime, const json& body, SDContextParams& context, SDGenerationParams& defaults, json* loaded_recipe) {
|
||||
check_keys(body, {"recipe", "models"});
|
||||
const auto recipe_id = body.at("recipe").get<std::string>();
|
||||
auto selection = body.at("models");
|
||||
if (!selection.is_object())
|
||||
throw std::runtime_error("models must be an object");
|
||||
auto& models = *runtime.models;
|
||||
std::lock_guard<std::mutex> lock(models.catalog_mutex);
|
||||
const auto found = models.recipes.find(recipe_id);
|
||||
if (found == models.recipes.end())
|
||||
throw std::runtime_error("unknown recipe");
|
||||
const auto& recipe = found->second;
|
||||
if (loaded_recipe)
|
||||
*loaded_recipe = recipe;
|
||||
context = models.base_context;
|
||||
defaults = models.base_defaults;
|
||||
apply_recipe_context(recipe.value("context", json::object()), context);
|
||||
apply_recipe_defaults(recipe.value("defaults", json::object()), defaults);
|
||||
std::set<std::string> allowed;
|
||||
for (const auto& slot : recipe.at("slots")) {
|
||||
const auto id = slot.at("id").get<std::string>();
|
||||
allowed.insert(id);
|
||||
if (id == "tae" || id == "taesd") {
|
||||
const std::string alias = id == "tae" ? "taesd" : "tae";
|
||||
allowed.insert(alias);
|
||||
if (selection.contains(alias)) {
|
||||
if (selection.contains(id))
|
||||
throw std::runtime_error("specify only one of tae and taesd");
|
||||
selection[id] = selection.at(alias);
|
||||
}
|
||||
}
|
||||
const std::string file_id = selection.value(id, std::string());
|
||||
if (file_id.empty()) {
|
||||
if (slot.at("required").get<bool>())
|
||||
throw std::runtime_error("missing required model: " + id);
|
||||
continue;
|
||||
}
|
||||
const auto file = models.files.find(file_id);
|
||||
if (file == models.files.end() || !matches_model_category(*find_model_slot(id), file->second.category)) {
|
||||
throw std::runtime_error("unknown model or incorrect category: " + id);
|
||||
}
|
||||
const auto& root = file->second.root.path;
|
||||
const fs::path path = fs::canonical(root / fs::u8path(file->second.relative_path));
|
||||
if (!model_path_inside(root, path) || !fs::is_regular_file(path)) {
|
||||
throw std::runtime_error("model file must remain inside the model directory: " + id);
|
||||
}
|
||||
context.*find_model_slot(id)->field = path.u8string();
|
||||
}
|
||||
for (auto it = selection.begin(); it != selection.end(); ++it) {
|
||||
if (!allowed.count(it.key()))
|
||||
throw std::runtime_error("unknown model slot: " + it.key());
|
||||
}
|
||||
for (const auto& group : recipe.value("required_any", json::array())) {
|
||||
if (std::none_of(group.begin(), group.end(), [&](const json& value) {
|
||||
return !selection.value(value.get<std::string>(), std::string()).empty();
|
||||
})) {
|
||||
throw std::runtime_error("select at least one model from: " + group.dump());
|
||||
}
|
||||
}
|
||||
if (!context.resolve_and_validate(IMG_GEN))
|
||||
throw std::runtime_error("invalid model context parameters");
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#ifndef __SD_EXAMPLES_SERVER_MODEL_CATALOG_H__
|
||||
#define __SD_EXAMPLES_SERVER_MODEL_CATALOG_H__
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
|
||||
#include "runtime.h"
|
||||
|
||||
struct ModelRoot {
|
||||
std::filesystem::path path;
|
||||
std::string id;
|
||||
std::string label;
|
||||
bool primary = false;
|
||||
};
|
||||
|
||||
struct ModelFile {
|
||||
std::string category;
|
||||
std::string name;
|
||||
std::string relative_path;
|
||||
ModelRoot root;
|
||||
};
|
||||
|
||||
std::string model_path_key(const std::filesystem::path& path);
|
||||
bool model_path_inside(const std::filesystem::path& root, const std::filesystem::path& path);
|
||||
ModelRoot make_model_root(const std::filesystem::path& path, bool primary);
|
||||
std::vector<ModelRoot> get_model_roots(const SDSvrParams& params);
|
||||
std::vector<std::filesystem::path> model_category_directories(const ModelRoot& root, const std::string& category);
|
||||
|
||||
struct ServerModels {
|
||||
std::shared_mutex runtime_mutex;
|
||||
std::mutex catalog_mutex;
|
||||
std::mutex status_mutex;
|
||||
SDCtxPtr context;
|
||||
SDContextParams base_context;
|
||||
SDGenerationParams base_defaults;
|
||||
std::map<std::string, json> recipes;
|
||||
std::map<std::string, ModelFile> files;
|
||||
std::chrono::steady_clock::time_point load_started_at;
|
||||
json active_recipe = nullptr;
|
||||
json catalog_errors = json::array();
|
||||
json status = {{"state", "empty"}, {"recipe", nullptr}, {"models", json::object()}, {"error", nullptr}, {"load_elapsed_seconds", nullptr}};
|
||||
};
|
||||
|
||||
void refresh_model_catalog(ServerRuntime& runtime);
|
||||
json model_status(ServerRuntime& runtime);
|
||||
void initialize_server_models(ServerRuntime& runtime);
|
||||
|
||||
json resolve_recipe_features(const json& recipe);
|
||||
json model_generation_features(const ServerRuntime& runtime);
|
||||
|
||||
void prepare_model_load(ServerRuntime& runtime, const json& body, SDContextParams& context, SDGenerationParams& defaults, json* loaded_recipe = nullptr);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,111 @@
|
||||
# Built-in recipe policy
|
||||
|
||||
Read these rules before adding or changing JSON files in this directory. A recipe
|
||||
represents a supported model-loading and capability configuration, not a saved
|
||||
prompt, a style preset, or a collection of sampling settings.
|
||||
|
||||
## Required rules
|
||||
|
||||
1. **Community editing adapters get a separate recipe.** When the base model
|
||||
does not natively support instruction/reference-image editing and needs a
|
||||
community LoRA to gain it, provide a dedicated editing recipe. Keep editing
|
||||
inputs disabled in the base recipe. Examples: `anima` / `anima-edit` and
|
||||
`krea2-raw` / `krea2-raw-edit`.
|
||||
2. **Split different task weights; share native multi-task weights.** If editing
|
||||
and text-to-image use different generation checkpoints, use separate recipes.
|
||||
If the same checkpoint natively supports both, use one recipe. Examples:
|
||||
`boogu-image` / `boogu-image-edit` are separate; `flux2-dev` and
|
||||
`qwen-image-21` each cover both generation and editing. Apply the same rule
|
||||
to video: separate HunyuanVideo 1.5 T2V/I2V and Wan 2.2 A14B T2V/I2V weights,
|
||||
but share one `wan22-ti2v-5b` recipe for its native T2V/I2V checkpoint.
|
||||
3. **Capability-extending weights get a separate recipe.** ControlNet,
|
||||
IP-Adapter, PuLID, PhotoMaker, motion modules, and similar adapters that add
|
||||
a distinct conditioning or generation capability belong in dedicated
|
||||
recipes. Make the extension's model slots required when the schema provides
|
||||
them. Do not add these as optional slots on the ordinary base recipe.
|
||||
4. **A LoRA alone is not a reason to split.** If adding a LoRA does not change
|
||||
supported functionality, reuse the base recipe. Style, character, quality,
|
||||
acceleration and distillation LoRAs do not get dedicated built-in recipes.
|
||||
Users select the LoRA and change steps, guidance or other generation settings
|
||||
separately. Standalone base/turbo/distilled checkpoints still follow the task,
|
||||
component and layer-count rules; a different filename alone is not a reason
|
||||
for another recipe.
|
||||
5. **Keep native components in the model's recipe.** Native vision encoders,
|
||||
projectors, connectors and other components required by the model belong in
|
||||
its existing recipe. Splitting a native component into another file does not
|
||||
make it a capability-extending adapter under rule 3. For example, Qwen vision
|
||||
projectors and LLaDA/LTX connectors do not get separate recipes. A slot may be
|
||||
optional when its weights can be bundled in another selected file.
|
||||
6. **Layer-count variants share the family recipe.** Adding, repeating, removing
|
||||
or pruning model layers does not justify a separate recipe when the runtime
|
||||
supports the variant with the same components and capabilities. This also
|
||||
applies to distilled checkpoints whose relevant difference is reduced depth.
|
||||
Use `flux1-dev` for FLUX.1 Lite, `sdxl-checkpoint` for SSD-1B and Segmind Vega,
|
||||
and the corresponding SD 1/2 checkpoint recipes for Tiny SD/Tiny SD2. Only an
|
||||
independent difference in task, required component compatibility or latent
|
||||
format can justify splitting such a variant; document that difference.
|
||||
|
||||
## Interpretation and review
|
||||
|
||||
- Instruction/reference-image editing is distinct from generic image-to-image
|
||||
denoising. The shared `img2img` capability does not imply native editing.
|
||||
- Native components and external capability adapters are distinguished by their
|
||||
role, not by whether their weights are stored in a separate file. LLaDA's
|
||||
SigVQ-containing connectors remain in its shared generation/editing recipe.
|
||||
- Do not merge models solely because they descend from the same family. Flex.2
|
||||
has different native inpainting/control conditioning, beyond its layer count
|
||||
([model card](https://huggingface.co/ostris/Flex.2-preview)). SDXS uses an
|
||||
adjusted AutoEncoderTiny and a one-step model, rather than only a shallower
|
||||
ordinary SD checkpoint ([runtime guide](../../../docs/distilled_sd.md#sdxs-512-dreamshaper)).
|
||||
Their native components stay in their own model recipe.
|
||||
- Quantization, file format, prompt wording, seeds, step counts and CFG values
|
||||
are not grounds for another recipe. Differences in required components,
|
||||
latent formats or supported capabilities must be documented.
|
||||
- Evaluate what an adapter actually changes. Do not infer new functionality just
|
||||
from a LoRA's name or from the presence of another weight file. In particular,
|
||||
do not create an Omni-Kontext recipe solely because a LoRA is selected.
|
||||
- A recipe does not load generation LoRAs automatically. Community-edit recipes
|
||||
must explain that the matching LoRA is required and must be selected separately.
|
||||
- Only advertise features implemented by the current runtime. Check the relevant
|
||||
model documentation and code, and match each task's slots and feature flags to
|
||||
its weights. Record any unverified inference paths in the change description.
|
||||
- Before submitting, validate all recipe JSON, inspect the embedded recipe API,
|
||||
and check that task-specific inputs appear only on the appropriate recipes.
|
||||
A catalog check is not a substitute for model-generation verification.
|
||||
|
||||
These are built-in catalog rules. Users may keep personal presets in
|
||||
`<model-dir>/recipes/`; they need not add those presets to the built-in list.
|
||||
See [the recipe schema and feature reference](../api.md#user-recipe-format)
|
||||
for supported fields.
|
||||
|
||||
## Output dimension alignment
|
||||
|
||||
Set `size_multiple` when the runtime's output dimension alignment differs from
|
||||
the default of `8`. Match `GenerationRequest::align_image_size` in
|
||||
[`src/pipeline/request.cpp`](../../../src/pipeline/request.cpp): the VAE scale
|
||||
factor multiplied by the model definition's `diffusion_down_factor`. The scale
|
||||
comes from [`VAE::get_scale_factor`](../../../src/model/vae/vae.hpp), with the
|
||||
PiD override in [`diffusion_engine.cpp`](../../../src/pipeline/diffusion_engine.cpp);
|
||||
model geometry is defined under [`src/model/integrations`](../../../src/model/integrations/).
|
||||
|
||||
Use the runtime's output alignment, not the nominal patch size: some models pad
|
||||
patches internally and crop their output back to the requested size. Pixel-space
|
||||
models with an alignment of `1` explicitly use `size_multiple: 1`. MiniT2I also
|
||||
has this alignment, but its separate fixed `512 × 512` output restriction still
|
||||
applies. Reference-image preprocessing constraints are independent of this field.
|
||||
|
||||
## Video length constraints
|
||||
|
||||
Video recipes declare `video_constraints` to match `align_video_frames` in
|
||||
[`diffusion_engine.cpp`](../../../src/pipeline/diffusion_engine.cpp) and the FPS
|
||||
and AnimateDiff overrides in
|
||||
[`request.cpp`](../../../src/pipeline/request.cpp). Wan, HunyuanVideo and LingBot
|
||||
round down to `4n + 1`; LTX rounds down to `8n + 1`. MiniMax-H3 rounds up to
|
||||
`17n + 5`, with at least 5 frames and fixed 24 FPS. AnimateDiff accepts whole
|
||||
frames up to 32. Wan 2.2 S2V fixes FPS at 16.
|
||||
|
||||
Declare fixed FPS only where the runtime enforces it; recommended FPS belongs
|
||||
in `defaults.fps`. These constraints guide UI conversion and estimates, not
|
||||
runtime behavior. Audio-driven output and temporal upscaling may still change
|
||||
the final frame count. See the [recipe schema](../api.md#user-recipe-format)
|
||||
for field defaults and validation rules.
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"id": "anima-edit",
|
||||
"name": "Anima Edit (LoRA)",
|
||||
"description": "Requires a matching community editing LoRA. Apply it after loading and supply reference images; uses the cosmos_reference preset.",
|
||||
"model_url": "https://huggingface.co/circlestone-labs/Anima",
|
||||
"slots": [
|
||||
{
|
||||
"id": "diffusion_model",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "llm",
|
||||
"required": true,
|
||||
"label": "Qwen 3 0.6B Base Text Encoder"
|
||||
},
|
||||
{
|
||||
"id": "vae",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "tae",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"required_any": [
|
||||
[
|
||||
"vae",
|
||||
"tae"
|
||||
]
|
||||
],
|
||||
"defaults": {
|
||||
"sampling_method": "euler",
|
||||
"scheduler": "discrete",
|
||||
"cfg_scale": 4,
|
||||
"steps": 20,
|
||||
"width": 1024,
|
||||
"height": 1024
|
||||
},
|
||||
"features": [
|
||||
"flow_shift",
|
||||
"ref_images"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"id": "anima",
|
||||
"name": "Anima",
|
||||
"description": "Supports all Anima variants supported by sd.cpp. Use a compatible Qwen Image VAE or TAE; adjust generation settings for distilled variants.",
|
||||
"model_url": "https://huggingface.co/circlestone-labs/Anima",
|
||||
"slots": [
|
||||
{
|
||||
"id": "diffusion_model",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "llm",
|
||||
"required": true,
|
||||
"label": "Qwen 3 0.6B Base Text Encoder"
|
||||
},
|
||||
{
|
||||
"id": "vae",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "tae",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"required_any": [
|
||||
[
|
||||
"vae",
|
||||
"tae"
|
||||
]
|
||||
],
|
||||
"defaults": {
|
||||
"sampling_method": "euler",
|
||||
"cfg_scale": 4,
|
||||
"steps": 20,
|
||||
"width": 1024,
|
||||
"height": 1024
|
||||
},
|
||||
"features": [
|
||||
"flow_shift"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"id": "animatediff-sd15",
|
||||
"name": "AnimateDiff SD 1.5",
|
||||
"size_multiple": 64,
|
||||
"description": "Use Video Generation with an SD 1.5 checkpoint and compatible motion module.",
|
||||
"model_url": "https://huggingface.co/guoyww/animatediff",
|
||||
"slots": [
|
||||
{
|
||||
"id": "model",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "motion_module",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "vae",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "tae",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"video_constraints": {
|
||||
"frame_step": 1,
|
||||
"frame_offset": 0,
|
||||
"frame_rounding": "floor",
|
||||
"min_frames": 1,
|
||||
"max_frames": 32
|
||||
},
|
||||
"defaults": {
|
||||
"sampling_method": "euler",
|
||||
"video_frames": 8,
|
||||
"fps": 8,
|
||||
"cfg_scale": 7.5,
|
||||
"steps": 20,
|
||||
"width": 512,
|
||||
"height": 512
|
||||
},
|
||||
"features": [
|
||||
"cache_unet",
|
||||
"clip_skip",
|
||||
"init_image"
|
||||
],
|
||||
"disabled_features": [
|
||||
"cache_dit",
|
||||
"img2img",
|
||||
"mask_image"
|
||||
]
|
||||
}
|
||||