Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e071aa390b | ||
|
|
dff0e88265 | ||
|
|
c674225b38 | ||
|
|
0321ce17fb | ||
|
|
2abbc773ac | ||
|
|
da6db07c5c | ||
|
|
e9dee542c4 | ||
|
|
c60b36af0f | ||
|
|
38a51f8105 | ||
|
|
45714b1069 | ||
|
|
e790073e1c | ||
|
|
b11c95a41c | ||
|
|
68f3d6df9f | ||
|
|
7bcd189639 | ||
|
|
2574f59365 | ||
|
|
7dab3668d0 | ||
|
|
556f04bb3f | ||
|
|
3590aa8d62 | ||
|
|
1a13107bac | ||
|
|
484baa41e5 | ||
|
|
2bb0389683 | ||
|
|
ccda89e09c | ||
|
|
f0271076ad | ||
|
|
3b6c9ca97c | ||
|
|
57e19fa954 | ||
|
|
61a637bfd5 | ||
|
|
3ec374aecc | ||
|
|
0484600284 | ||
|
|
c1790754d3 | ||
|
|
9f855c933b | ||
|
|
7b5f34d93e | ||
|
|
d77b8f5ee8 | ||
|
|
03e9a22f4d | ||
|
|
f54e45e81c | ||
|
|
9956436c92 | ||
|
|
ec4cb8104b | ||
|
|
3973015ed7 | ||
|
|
9ee77fc227 | ||
|
|
39f7962d69 | ||
|
|
8caa3f908a | ||
|
|
2938272d82 | ||
|
|
f440ad9c29 | ||
|
|
41f7acbfb0 | ||
|
|
b395a6972d | ||
|
|
854bebfe02 | ||
|
|
787d229d84 | ||
|
|
b12098f5d0 | ||
|
|
2bd249c971 | ||
|
|
e9e952462f | ||
|
|
e8e012eef2 | ||
|
|
7f0e728b7d | ||
|
|
92a3b73cdb | ||
|
|
710bc91c8f | ||
|
|
5a34bc7f6e | ||
|
|
146b6cc49e | ||
|
|
93527fda74 | ||
|
|
6e66a1a4a4 | ||
|
|
bb90bfa00f | ||
|
|
517abc777d | ||
|
|
6f00939f75 | ||
|
|
c2df4e1228 | ||
|
|
9838264c49 | ||
|
|
17d70b91e6 | ||
|
|
5db680c2c7 | ||
|
|
749186c0eb | ||
|
|
bdb431ad95 | ||
|
|
276025e054 | ||
|
|
8d4c7af95b | ||
|
|
9b0fceb41b | ||
|
|
563137a592 | ||
|
|
3a54597776 | ||
|
|
1365008348 | ||
|
|
1fb6b22850 | ||
|
|
c20769b2c8 | ||
|
|
1b702a51e7 | ||
|
|
19bdfe22d2 | ||
|
|
138da14cc3 | ||
|
|
17a2b4a315 | ||
|
|
b3d56d0ba1 | ||
|
|
2a07540c2a | ||
|
|
81abfb2548 | ||
|
|
f3fd359b58 | ||
|
|
dfb2390dd4 | ||
|
|
cfbc19d186 | ||
|
|
b9254dda0d | ||
|
|
0648f4426b | ||
|
|
74f513d512 | ||
|
|
064001b524 | ||
|
|
1f9ee88e09 | ||
|
|
a7f2e03da4 | ||
|
|
4513e3fda9 | ||
|
|
2d40a8b2ad | ||
|
|
9c7f9a20b3 | ||
|
|
ed74577c40 | ||
|
|
7948df8ac1 | ||
|
|
02f06370a7 | ||
|
|
f8935d6f25 | ||
|
|
be65ac7511 | ||
|
|
20901f6d8e | ||
|
|
0982807139 | ||
|
|
d2797b8667 | ||
|
|
d3b2cb047e | ||
|
|
b4ba55d8d7 | ||
|
|
b54bd83a3f | ||
|
|
0e4ee04488 | ||
|
|
29ab511fc7 | ||
|
|
55c2aed52c | ||
|
|
8eded497e5 | ||
|
|
92dc7268fc | ||
|
|
07b2b18e70 | ||
|
|
1ceb5bd9df | ||
|
|
202c6154a2 | ||
|
|
a397e03488 | ||
|
|
72e512a0cc | ||
|
|
0baf721215 | ||
|
|
645e6e9089 | ||
|
|
cbf92191c3 | ||
|
|
8cf55a3b3b | ||
|
|
3a8788cb7d | ||
|
|
449165caf5 | ||
|
|
adaa599a3b | ||
|
|
2e3514625a | ||
|
|
47d8198b69 | ||
|
|
ef92a0027e | ||
|
|
b3374e6a71 | ||
|
|
bdd937f29a | ||
|
|
c51ec7cad9 | ||
|
|
5b0267e941 | ||
|
|
0045a72b96 | ||
|
|
99bd062546 | ||
|
|
9d8c9e4279 | ||
|
|
caa823a8c0 | ||
|
|
22c8c40b0d | ||
|
|
b706d682ad | ||
|
|
b758b7de13 | ||
|
|
f683c88a28 | ||
|
|
21fd4e6788 | ||
|
|
830804262b | ||
|
|
82e03ef137 | ||
|
|
baf7eda1e4 | ||
|
|
e7eb92fd84 | ||
|
|
50134e51dd | ||
|
|
e43b24cf48 | ||
|
|
06accf2b39 | ||
|
|
cde20d5ef0 | ||
|
|
67dda3f897 | ||
|
|
3b4d26f3d9 | ||
|
|
bd17f53b73 | ||
|
|
d7ecbe1d01 | ||
|
|
36330724bd | ||
|
|
0c1ca170ca | ||
|
|
839f6a94d2 | ||
|
|
38b14adb67 | ||
|
|
fd1a2794f3 | ||
|
|
db08b84607 | ||
|
|
686856edca | ||
|
|
0b8296915c | ||
|
|
381e0df50f | ||
|
|
0665a7f8bf |
@@ -0,0 +1,15 @@
|
||||
## Summary
|
||||
|
||||
<!-- Describe what changed and why. Keep the PR focused on one clear change. -->
|
||||
|
||||
## Related Issue / Discussion
|
||||
|
||||
<!-- Link related issues, discussions, or previous PRs if applicable. -->
|
||||
|
||||
## Additional Information
|
||||
|
||||
<!-- Add verification notes, screenshots, sample output, or other context when applicable. -->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have read and confirmed this PR follows the [contribution guidelines](https://github.com/leejet/stable-diffusion.cpp/blob/master/CONTRIBUTING.md).
|
||||
@@ -14,6 +14,8 @@ on:
|
||||
paths:
|
||||
[
|
||||
".github/workflows/**",
|
||||
".dockerignore",
|
||||
"docker/Dockerfile*",
|
||||
"**/CMakeLists.txt",
|
||||
"**/Makefile",
|
||||
"**/*.h",
|
||||
@@ -29,6 +31,8 @@ on:
|
||||
paths:
|
||||
[
|
||||
".github/workflows/**",
|
||||
".dockerignore",
|
||||
"docker/Dockerfile*",
|
||||
"**/CMakeLists.txt",
|
||||
"**/Makefile",
|
||||
"**/*.h",
|
||||
@@ -79,7 +83,7 @@ jobs:
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DGGML_AVX2=ON -DSD_BUILD_SHARED_LIBS=ON
|
||||
cmake .. -DSD_BUILD_SHARED_LIBS=ON -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_INSTALL_RPATH='$ORIGIN'
|
||||
cmake --build . --config Release
|
||||
|
||||
- name: Get commit hash
|
||||
@@ -135,14 +139,14 @@ jobs:
|
||||
id: depends
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install build-essential libvulkan-dev glslc
|
||||
sudo apt-get install build-essential libvulkan-dev glslc spirv-headers
|
||||
|
||||
- name: Build
|
||||
id: cmake_build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DSD_BUILD_SHARED_LIBS=ON -DSD_VULKAN=ON
|
||||
cmake .. -DSD_BUILD_SHARED_LIBS=ON -DSD_VULKAN=ON -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_INSTALL_RPATH='$ORIGIN'
|
||||
cmake --build . --config Release
|
||||
|
||||
- name: Get commit hash
|
||||
@@ -177,7 +181,7 @@ jobs:
|
||||
build-and-push-docker-images:
|
||||
name: Build and push container images
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.runner }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -189,6 +193,21 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
variant: [musa, sycl, vulkan, cuda]
|
||||
platform: [linux/amd64]
|
||||
runner: [ubuntu-latest]
|
||||
build-args: [""]
|
||||
tag-suffix: [""]
|
||||
include:
|
||||
- variant: cuda
|
||||
platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
tag-suffix: "-spark"
|
||||
build-args: |
|
||||
CUDA_VERSION=13.0.0
|
||||
UBUNTU_VERSION=24.04
|
||||
CUDA_ARCHITECTURES=121
|
||||
GGML_CUDA_FA_ALL_QUANTS=ON
|
||||
GGML_CUDA_ENABLE_DYNAMIC_CPU_BACKENDS=OFF
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
@@ -243,12 +262,13 @@ jobs:
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
platforms: ${{ matrix.platform }}
|
||||
push: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
file: Dockerfile.${{ matrix.variant }}
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.BRANCH_NAME }}-${{ matrix.variant }}
|
||||
file: docker/Dockerfile.${{ matrix.variant }}
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.BRANCH_NAME }}-${{ matrix.variant }}${{ matrix.tag-suffix }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
build-args: ${{ matrix.build-args }}
|
||||
|
||||
macOS-latest-cmake:
|
||||
runs-on: macos-latest
|
||||
@@ -322,18 +342,12 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- build: "noavx"
|
||||
defines: "-DGGML_NATIVE=OFF -DGGML_AVX=OFF -DGGML_AVX2=OFF -DGGML_FMA=OFF -DSD_BUILD_SHARED_LIBS=ON"
|
||||
- build: "avx2"
|
||||
defines: "-DGGML_NATIVE=OFF -DGGML_AVX2=ON -DSD_BUILD_SHARED_LIBS=ON"
|
||||
- build: "avx"
|
||||
defines: "-DGGML_NATIVE=OFF -DGGML_AVX=ON -DGGML_AVX2=OFF -DSD_BUILD_SHARED_LIBS=ON"
|
||||
- build: "avx512"
|
||||
defines: "-DGGML_NATIVE=OFF -DGGML_AVX512=ON -DGGML_AVX=ON -DGGML_AVX2=ON -DSD_BUILD_SHARED_LIBS=ON"
|
||||
- build: "cpu"
|
||||
defines: "-DGGML_NATIVE=OFF -DSD_BUILD_SHARED_LIBS=ON -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON"
|
||||
- build: "cuda12"
|
||||
defines: "-DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_CUDA_ARCHITECTURES='61;70;75;80;86;89;90;100;120' -DCMAKE_CUDA_FLAGS='-Xcudafe \"--diag_suppress=177\" -Xcudafe \"--diag_suppress=550\"'"
|
||||
defines: "-DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_CUDA_ARCHITECTURES='61;70;75;80;86;89;90;100;120' -DCMAKE_CUDA_FLAGS='-Xcudafe \"--diag_suppress=177\" -Xcudafe \"--diag_suppress=550\"' -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON"
|
||||
- build: "vulkan"
|
||||
defines: "-DSD_VULKAN=ON -DSD_BUILD_SHARED_LIBS=ON"
|
||||
defines: "-DSD_VULKAN=ON -DSD_BUILD_SHARED_LIBS=ON -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON"
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
@@ -381,19 +395,6 @@ jobs:
|
||||
cmake .. -DCMAKE_CXX_FLAGS='/bigobj' -G Ninja -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe -DCMAKE_BUILD_TYPE=Release ${{ matrix.defines }}
|
||||
cmake --build .
|
||||
|
||||
- name: Check AVX512F support
|
||||
id: check_avx512f
|
||||
if: ${{ matrix.build == 'avx512' }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cd build
|
||||
$vcdir = $(vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath)
|
||||
$msvc = $(join-path $vcdir $('VC\Tools\MSVC\'+$(gc -raw $(join-path $vcdir 'VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt')).Trim()))
|
||||
$cl = $(join-path $msvc 'bin\Hostx64\x64\cl.exe')
|
||||
echo 'int main(void){unsigned int a[4];__cpuid(a,7);return !(a[1]&65536);}' >> avx512f.c
|
||||
& $cl /O2 /GS- /kernel avx512f.c /link /nodefaultlib /entry:main
|
||||
.\avx512f.exe && echo "AVX512F: YES" && ( echo HAS_AVX512F=1 >> $env:GITHUB_ENV ) || echo "AVX512F: NO"
|
||||
|
||||
- name: Get commit hash
|
||||
id: commit
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
@@ -444,12 +445,129 @@ jobs:
|
||||
path: |
|
||||
sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-${{ matrix.build }}-x64.zip
|
||||
|
||||
windows-latest-rocm:
|
||||
runs-on: windows-2022
|
||||
|
||||
env:
|
||||
ROCM_VERSION: "7.13.0"
|
||||
GPU_TARGETS: "gfx906;gfx908;gfx90a;gfx942;gfx950;gfx1010;gfx1011;gfx1012;gfx1030;gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036;gfx1100;gfx1101;gfx1102;gfx1150;gfx1151;gfx1152;gfx1200;gfx1201"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.15.1
|
||||
|
||||
- name: Cache ROCm Installation
|
||||
id: cache-rocm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: C:\TheRock\build
|
||||
key: rocm-${{ env.ROCM_VERSION }}-gfx1151-${{ runner.os }}
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.16
|
||||
with:
|
||||
key: windows-latest-rocm-${{ env.ROCM_VERSION }}-x64
|
||||
evict-old-files: 1d
|
||||
|
||||
- name: Install ROCm
|
||||
if: steps.cache-rocm.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
write-host "Downloading AMD ROCm ${{ env.ROCM_VERSION }} tarball"
|
||||
Invoke-WebRequest -Uri "https://repo.amd.com/rocm/tarball/therock-dist-windows-gfx1151-${{ env.ROCM_VERSION }}.tar.gz" -OutFile "${env:RUNNER_TEMP}\rocm.tar.gz"
|
||||
write-host "Extracting ROCm tarball"
|
||||
mkdir C:\TheRock\build -Force
|
||||
tar -xzf "${env:RUNNER_TEMP}\rocm.tar.gz" -C C:\TheRock\build --strip-components=1
|
||||
write-host "Completed ROCm extraction"
|
||||
|
||||
- name: Setup ROCm Environment
|
||||
run: |
|
||||
$rocmPath = "C:\TheRock\build"
|
||||
echo "HIP_PATH=$rocmPath" >> $env:GITHUB_ENV
|
||||
echo "HIP_DEVICE_LIB_PATH=$rocmPath\lib\llvm\amdgcn\bitcode" >> $env:GITHUB_ENV
|
||||
echo "HIP_PLATFORM=amd" >> $env:GITHUB_ENV
|
||||
echo "LLVM_PATH=$rocmPath\lib\llvm" >> $env:GITHUB_ENV
|
||||
echo "$rocmPath\bin" >> $env:GITHUB_PATH
|
||||
echo "$rocmPath\lib\llvm\bin" >> $env:GITHUB_PATH
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. `
|
||||
-G "Unix Makefiles" `
|
||||
-DCMAKE_PREFIX_PATH="${env:HIP_PATH}" `
|
||||
-DSD_HIPBLAS=ON `
|
||||
-DSD_BUILD_SHARED_LIBS=ON `
|
||||
-DGGML_NATIVE=OFF `
|
||||
-DCMAKE_C_COMPILER="${env:HIP_PATH}\lib\llvm\bin\clang.exe" `
|
||||
-DCMAKE_CXX_COMPILER="${env:HIP_PATH}\lib\llvm\bin\clang++.exe" `
|
||||
-DCMAKE_HIP_COMPILER="${env:HIP_PATH}\lib\llvm\bin\clang.exe" `
|
||||
-DHIP_PATH="${env:HIP_PATH}" `
|
||||
-DCMAKE_BUILD_TYPE=Release `
|
||||
-DGPU_TARGETS="${{ env.GPU_TARGETS }}"
|
||||
cmake --build . --config Release --parallel ${env:NUMBER_OF_PROCESSORS}
|
||||
|
||||
- name: Get commit hash
|
||||
id: commit
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: pr-mpt/actions-commit-hash@v2
|
||||
|
||||
- name: Pack artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$dst = "build\bin"
|
||||
$rocmBin = Join-Path "${env:HIP_PATH}" "bin"
|
||||
$requiredRocmPaths = @(
|
||||
(Join-Path $rocmBin "rocblas.dll"),
|
||||
(Join-Path $rocmBin "rocblas\library")
|
||||
)
|
||||
foreach ($path in $requiredRocmPaths) {
|
||||
if (!(Test-Path $path)) {
|
||||
throw "Missing ROCm runtime dependency: $path"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pattern in @("rocblas*.dll", "hipblas*.dll", "libhipblas*.dll")) {
|
||||
Copy-Item -Path (Join-Path $rocmBin $pattern) -Destination $dst -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
foreach ($dir in @("rocblas", "hipblaslt")) {
|
||||
$src = Join-Path $rocmBin $dir
|
||||
if (Test-Path $src) {
|
||||
Copy-Item -Path $src -Destination $dst -Recurse -Force
|
||||
}
|
||||
}
|
||||
|
||||
7z a sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-${{ env.ROCM_VERSION }}-x64.zip .\build\bin\*
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-${{ env.ROCM_VERSION }}-x64.zip
|
||||
path: |
|
||||
sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-${{ env.ROCM_VERSION }}-x64.zip
|
||||
|
||||
windows-latest-cmake-hip:
|
||||
runs-on: windows-2022
|
||||
|
||||
env:
|
||||
HIPSDK_INSTALLER_VERSION: "25.Q3"
|
||||
GPU_TARGETS: "gfx1151;gfx1200;gfx1201;gfx1100;gfx1101;gfx1102;gfx1030;gfx1031;gfx1032"
|
||||
HIPSDK_INSTALLER_VERSION: "26.Q1"
|
||||
ROCM_VERSION: "7.1.1"
|
||||
GPU_TARGETS: "gfx1150;gfx1151;gfx1200;gfx1201;gfx1100;gfx1101;gfx1102;gfx1030;gfx1031;gfx1032"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -484,7 +602,7 @@ jobs:
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
write-host "Downloading AMD HIP SDK Installer"
|
||||
Invoke-WebRequest -Uri "https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-${{ env.HIPSDK_INSTALLER_VERSION }}-WinSvr2022-For-HIP.exe" -OutFile "${env:RUNNER_TEMP}\rocm-install.exe"
|
||||
Invoke-WebRequest -Uri "https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-${{ env.HIPSDK_INSTALLER_VERSION }}-Win11-For-HIP.exe" -OutFile "${env:RUNNER_TEMP}\rocm-install.exe"
|
||||
write-host "Installing AMD HIP SDK"
|
||||
$proc = Start-Process "${env:RUNNER_TEMP}\rocm-install.exe" -ArgumentList '-install' -NoNewWindow -PassThru
|
||||
$completed = $proc.WaitForExit(600000)
|
||||
@@ -537,38 +655,109 @@ jobs:
|
||||
run: |
|
||||
md "build\bin\rocblas\library\"
|
||||
md "build\bin\hipblaslt\library"
|
||||
cp "${env:HIP_PATH}\bin\hipblas.dll" "build\bin\"
|
||||
cp "${env:HIP_PATH}\bin\hipblaslt.dll" "build\bin\"
|
||||
cp "${env:HIP_PATH}\bin\libhipblas.dll" "build\bin\"
|
||||
cp "${env:HIP_PATH}\bin\libhipblaslt.dll" "build\bin\"
|
||||
cp "${env:HIP_PATH}\bin\rocblas.dll" "build\bin\"
|
||||
cp "${env:HIP_PATH}\bin\rocblas\library\*" "build\bin\rocblas\library\"
|
||||
cp "${env:HIP_PATH}\bin\hipblaslt\library\*" "build\bin\hipblaslt\library\"
|
||||
7z a sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-x64.zip .\build\bin\*
|
||||
7z a sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-${{ env.ROCM_VERSION }}-x64.zip .\build\bin\*
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-x64.zip
|
||||
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-${{ env.ROCM_VERSION }}-x64.zip
|
||||
path: |
|
||||
sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-x64.zip
|
||||
sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-${{ env.ROCM_VERSION }}-x64.zip
|
||||
|
||||
ubuntu-latest-rocm:
|
||||
runs-on: ubuntu-latest
|
||||
container: rocm/dev-ubuntu-24.04:7.2
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
env:
|
||||
ROCM_VERSION: "7.2"
|
||||
UBUNTU_VERSION: "24.04"
|
||||
GPU_TARGETS: "gfx1151;gfx1150;gfx1100;gfx1101;gfx1102;gfx1200;gfx1201"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- ROCM_VERSION: "7.2.1"
|
||||
gpu_targets: "gfx908;gfx90a;gfx942;gfx1030;gfx1031;gfx1032;gfx1100;gfx1101;gfx1102;gfx1151;gfx1150;gfx1200;gfx1201"
|
||||
build: 'x64'
|
||||
- ROCM_VERSION: "7.13.0"
|
||||
gpu_targets: "gfx906;gfx908;gfx90a;gfx942;gfx950;gfx1010;gfx1011;gfx1012;gfx1030;gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036;gfx1100;gfx1101;gfx1102;gfx1150;gfx1151;gfx1152;gfx1200;gfx1201"
|
||||
build: x64
|
||||
|
||||
steps:
|
||||
- run: apt-get update && apt-get install -y git
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.16
|
||||
with:
|
||||
key: ubuntu-rocm-cmake-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
|
||||
evict-old-files: 1d
|
||||
|
||||
- name: Dependencies
|
||||
id: depends
|
||||
run: |
|
||||
sudo apt install -y build-essential cmake wget zip ninja-build
|
||||
|
||||
- name: Free disk space
|
||||
run: |
|
||||
df -h
|
||||
|
||||
# Remove preinstalled SDKs and caches not needed for this job before
|
||||
# installing ROCm. The legacy ROCm apt packages are large enough to
|
||||
# exhaust ubuntu-latest if cleanup runs after installation.
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /usr/local/lib/android || true
|
||||
sudo rm -rf /opt/ghc || true
|
||||
sudo rm -rf /usr/local/.ghcup || true
|
||||
sudo rm -rf /opt/hostedtoolcache || true
|
||||
sudo rm -rf /usr/share/swift || true
|
||||
sudo rm -rf /usr/local/share/boost || true
|
||||
docker system prune -af || true
|
||||
|
||||
sudo apt clean
|
||||
df -h
|
||||
|
||||
- name: Setup Legacy ROCm
|
||||
if: matrix.ROCM_VERSION == '7.2.1'
|
||||
id: legacy_env
|
||||
run: |
|
||||
sudo mkdir --parents --mode=0755 /etc/apt/keyrings
|
||||
wget https://repo.radeon.com/rocm/rocm.gpg.key -O - | \
|
||||
gpg --dearmor | sudo tee /etc/apt/keyrings/rocm.gpg > /dev/null
|
||||
|
||||
sudo tee /etc/apt/sources.list.d/rocm.list << EOF
|
||||
deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/${{ matrix.ROCM_VERSION }} noble main
|
||||
EOF
|
||||
|
||||
sudo tee /etc/apt/preferences.d/rocm-pin-600 << EOF
|
||||
Package: *
|
||||
Pin: release o=repo.radeon.com
|
||||
Pin-Priority: 600
|
||||
EOF
|
||||
|
||||
sudo apt update
|
||||
sudo apt-get install -y libssl-dev rocm-hip-sdk
|
||||
|
||||
- name: Setup TheRock
|
||||
if: matrix.ROCM_VERSION != '7.2.1'
|
||||
id: therock_env
|
||||
run: |
|
||||
wget https://repo.amd.com/rocm/tarball/therock-dist-linux-gfx1151-${{ matrix.ROCM_VERSION }}.tar.gz
|
||||
mkdir install
|
||||
tar -xf *.tar.gz -C install
|
||||
export ROCM_PATH=$(pwd)/install
|
||||
echo ROCM_PATH=$ROCM_PATH >> $GITHUB_ENV
|
||||
echo PATH=$PATH:$ROCM_PATH/bin >> $GITHUB_ENV
|
||||
echo LD_LIBRARY_PATH=$ROCM_PATH/lib:$ROCM_PATH/llvm/lib:$ROCM_PATH/lib/rocprofiler-systems >> $GITHUB_ENV
|
||||
|
||||
# setup-node installs into /opt/hostedtoolcache, which is removed above.
|
||||
# Keep Node/pnpm setup after disk cleanup so the server frontend can be embedded.
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -579,79 +768,25 @@ jobs:
|
||||
with:
|
||||
version: 10.15.1
|
||||
|
||||
- name: Free disk space
|
||||
run: |
|
||||
# Remove preinstalled SDKs and caches not needed for this job
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /usr/local/lib/android || true
|
||||
sudo rm -rf /opt/ghc || true
|
||||
sudo rm -rf /usr/local/.ghcup || true
|
||||
sudo rm -rf /opt/hostedtoolcache || true
|
||||
|
||||
# Remove old package lists and caches
|
||||
sudo rm -rf /var/lib/apt/lists/* || true
|
||||
sudo apt clean
|
||||
|
||||
- name: Dependencies
|
||||
id: depends
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt install -y \
|
||||
cmake \
|
||||
hip-dev \
|
||||
hipblas-dev \
|
||||
ninja-build \
|
||||
rocm-dev \
|
||||
zip
|
||||
# Clean apt caches to recover disk space
|
||||
sudo apt clean
|
||||
sudo rm -rf /var/lib/apt/lists/* || true
|
||||
|
||||
- name: Setup ROCm Environment
|
||||
run: |
|
||||
# Add ROCm to PATH for current session
|
||||
echo "/opt/rocm/bin" >> $GITHUB_PATH
|
||||
|
||||
# Build regex pattern from ${{ env.GPU_TARGETS }} (match target as substring)
|
||||
TARGET_REGEX="($(printf '%s' "${{ env.GPU_TARGETS }}" | sed 's/;/|/g'))"
|
||||
|
||||
# Remove library files for architectures we're not building for to save disk space
|
||||
echo "Cleaning up unneeded architecture files..."
|
||||
cd /opt/rocm/lib/rocblas/library
|
||||
# Keep only our target architectures
|
||||
for file in *; do
|
||||
if printf '%s' "$file" | grep -q 'gfx'; then
|
||||
if ! printf '%s' "$file" | grep -Eq "$TARGET_REGEX"; then
|
||||
echo "Removing $file" &&
|
||||
sudo rm -f "$file";
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
cd /opt/rocm/lib/hipblaslt/library
|
||||
for file in *; do
|
||||
if printf '%s' "$file" | grep -q 'gfx'; then
|
||||
if ! printf '%s' "$file" | grep -Eq "$TARGET_REGEX"; then
|
||||
echo "Removing $file" &&
|
||||
sudo rm -f "$file";
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Build
|
||||
id: cmake_build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G Ninja \
|
||||
-DCMAKE_CXX_COMPILER=amdclang++ \
|
||||
-DCMAKE_C_COMPILER=amdclang \
|
||||
-DCMAKE_HIP_COMPILER="$(hipconfig -l)/clang" \
|
||||
-DCMAKE_HIP_FLAGS="-mllvm --amdgpu-unroll-threshold-local=600" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DSD_HIPBLAS=ON \
|
||||
-DGPU_TARGETS="${{ env.GPU_TARGETS }}" \
|
||||
-DAMDGPU_TARGETS="${{ env.GPU_TARGETS }}" \
|
||||
-DHIP_PLATFORM=amd \
|
||||
-DGPU_TARGETS="${{ matrix.gpu_targets }}" \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
|
||||
-DCMAKE_INSTALL_RPATH='$ORIGIN' \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DSD_BUILD_SHARED_GGML_LIB=ON \
|
||||
-DGGML_BACKEND_DL=ON \
|
||||
-DGGML_CPU_ALL_VARIANTS=ON \
|
||||
-DSD_BUILD_SHARED_LIBS=ON
|
||||
cmake --build . --config Release
|
||||
|
||||
@@ -668,16 +803,6 @@ jobs:
|
||||
cp ggml/LICENSE ./build/bin/ggml.txt
|
||||
cp LICENSE ./build/bin/stable-diffusion.cpp.txt
|
||||
|
||||
# Move ROCm runtime libraries (to avoid double space consumption)
|
||||
sudo mv /opt/rocm/lib/librocsparse.so* ./build/bin/
|
||||
sudo mv /opt/rocm/lib/libhsa-runtime64.so* ./build/bin/
|
||||
sudo mv /opt/rocm/lib/libamdhip64.so* ./build/bin/
|
||||
sudo mv /opt/rocm/lib/libhipblas.so* ./build/bin/
|
||||
sudo mv /opt/rocm/lib/libhipblaslt.so* ./build/bin/
|
||||
sudo mv /opt/rocm/lib/librocblas.so* ./build/bin/
|
||||
sudo mv /opt/rocm/lib/rocblas/ ./build/bin/
|
||||
sudo mv /opt/rocm/lib/hipblaslt/ ./build/bin/
|
||||
|
||||
- name: Fetch system info
|
||||
id: system-info
|
||||
run: |
|
||||
@@ -692,15 +817,15 @@ 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.zip ./build/bin
|
||||
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
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 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.zip
|
||||
name: 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
|
||||
path: |
|
||||
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.zip
|
||||
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
|
||||
|
||||
release:
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
@@ -715,6 +840,7 @@ jobs:
|
||||
- macOS-latest-cmake
|
||||
- windows-latest-cmake
|
||||
- windows-latest-cmake-hip
|
||||
- windows-latest-rocm
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Close inactive PRs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run daily. GitHub cron schedules use UTC.
|
||||
- cron: "30 1 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
debug_only:
|
||||
description: "Dry run: log intended actions without changing PRs"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
stale-prs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Mark and close inactive PRs
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
|
||||
days-before-pr-stale: 365
|
||||
days-before-pr-close: 7
|
||||
|
||||
stale-pr-label: pr:inactive
|
||||
close-pr-label: pr:auto-closed
|
||||
exempt-pr-labels: pr:keep-open
|
||||
|
||||
stale-pr-message: >
|
||||
This PR has been inactive for 365 days. If there is no new activity
|
||||
within 7 days, it will be closed automatically. Comment, push new
|
||||
commits, or remove the pr:inactive label to keep it open. Add
|
||||
pr:keep-open to exempt it from future inactive PR cleanup.
|
||||
|
||||
close-pr-message: >
|
||||
Closing this PR because it has had no activity for 7 days after
|
||||
being marked inactive. If this is still useful or ready to move
|
||||
forward, feel free to reopen it with fresh context or updated
|
||||
details. Sorry for any inconvenience.
|
||||
|
||||
remove-pr-stale-when-updated: true
|
||||
delete-branch: false
|
||||
operations-per-run: 100
|
||||
debug-only: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_only || false }}
|
||||
@@ -1,6 +1,6 @@
|
||||
[submodule "ggml"]
|
||||
path = ggml
|
||||
url = https://github.com/ggml-org/ggml.git
|
||||
url = https://github.com/leejet/ggml.git
|
||||
[submodule "examples/server/frontend"]
|
||||
path = examples/server/frontend
|
||||
url = https://github.com/leejet/sdcpp-webui.git
|
||||
|
||||
@@ -13,13 +13,40 @@ if (MSVC)
|
||||
add_compile_definitions(_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
|
||||
add_compile_options(
|
||||
$<$<COMPILE_LANGUAGE:C>:/MP>
|
||||
$<$<COMPILE_LANGUAGE:C>:/utf-8>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:/MP>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:/utf-8>
|
||||
)
|
||||
endif()
|
||||
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
if(APPLE)
|
||||
function(sd_set_macos_rpaths target)
|
||||
get_target_property(target_type ${target} TYPE)
|
||||
if(target_type STREQUAL "EXECUTABLE")
|
||||
set(runtime_paths "@executable_path" "@executable_path/../lib")
|
||||
elseif(target_type STREQUAL "SHARED_LIBRARY" OR target_type STREQUAL "MODULE_LIBRARY")
|
||||
set(runtime_paths "@loader_path" "@loader_path/../lib")
|
||||
set_target_properties(${target} PROPERTIES
|
||||
MACOSX_RPATH ON
|
||||
INSTALL_NAME_DIR "@rpath"
|
||||
BUILD_WITH_INSTALL_NAME_DIR ON
|
||||
)
|
||||
else()
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Release artifacts zip the build output directly, so keep macOS rpaths relocatable.
|
||||
set_target_properties(${target} PROPERTIES
|
||||
BUILD_RPATH "${runtime_paths}"
|
||||
INSTALL_RPATH "${runtime_paths}"
|
||||
BUILD_WITH_INSTALL_RPATH ON
|
||||
)
|
||||
endfunction()
|
||||
endif()
|
||||
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
set(SD_STANDALONE ON)
|
||||
else()
|
||||
@@ -69,6 +96,12 @@ option(SD_BUILD_SHARED_GGML_LIB "sd: build ggml as a separate shared lib" O
|
||||
option(SD_USE_SYSTEM_GGML "sd: use system-installed GGML library" OFF)
|
||||
#option(SD_BUILD_SERVER "sd: build server example" ON)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED true)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED true)
|
||||
|
||||
if(SD_CUDA)
|
||||
message("-- Use CUDA as backend stable-diffusion")
|
||||
set(GGML_CUDA ON)
|
||||
@@ -92,6 +125,12 @@ endif ()
|
||||
if (SD_HIPBLAS)
|
||||
message("-- Use HIPBLAS as backend stable-diffusion")
|
||||
set(GGML_HIP ON)
|
||||
# ggml-hip's device-stub objects must be position-independent, or the
|
||||
# default-PIE sd-cli link fails with `relocation R_X86_64_32 ... cannot be
|
||||
# used when making a PIE object` on distros that default to PIE
|
||||
# (Ubuntu 24.04, Fedora 40+, Debian 12+). The shared-library branch below
|
||||
# already sets this; the static build (the HIP default) did not.
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif ()
|
||||
|
||||
if(SD_MUSA)
|
||||
@@ -165,12 +204,33 @@ if(SD_WEBM)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (SD_RPC)
|
||||
message("-- Use RPC as backend stable-diffusion")
|
||||
set(GGML_RPC ON)
|
||||
add_definitions(-DSD_USE_RPC)
|
||||
endif ()
|
||||
|
||||
set(SD_LIB stable-diffusion)
|
||||
|
||||
file(GLOB SD_LIB_SOURCES CONFIGURE_DEPENDS
|
||||
"src/*.h"
|
||||
"src/*.cpp"
|
||||
"src/*.hpp"
|
||||
"src/conditioning/*.h"
|
||||
"src/conditioning/*.cpp"
|
||||
"src/conditioning/*.hpp"
|
||||
"src/core/*.h"
|
||||
"src/core/*.cpp"
|
||||
"src/core/*.hpp"
|
||||
"src/extensions/*.h"
|
||||
"src/extensions/*.cpp"
|
||||
"src/extensions/*.hpp"
|
||||
"src/model/*/*.h"
|
||||
"src/model/*/*.cpp"
|
||||
"src/model/*/*.hpp"
|
||||
"src/runtime/*.h"
|
||||
"src/runtime/*.cpp"
|
||||
"src/runtime/*.hpp"
|
||||
"src/model_io/*.h"
|
||||
"src/model_io/*.cpp"
|
||||
"src/tokenizers/*.h"
|
||||
@@ -229,6 +289,10 @@ else()
|
||||
add_library(${SD_LIB} STATIC ${SD_LIB_SOURCES})
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
sd_set_macos_rpaths(${SD_LIB})
|
||||
endif()
|
||||
|
||||
if(SD_SYCL)
|
||||
message("-- Use SYCL as backend stable-diffusion")
|
||||
set(GGML_SYCL ON)
|
||||
@@ -267,8 +331,10 @@ endif()
|
||||
|
||||
add_subdirectory(thirdparty)
|
||||
|
||||
target_link_libraries(${SD_LIB} PUBLIC ggml zip)
|
||||
target_sources(${SD_LIB} PRIVATE $<TARGET_OBJECTS:zip>)
|
||||
target_link_libraries(${SD_LIB} PUBLIC ggml)
|
||||
target_include_directories(${SD_LIB} PUBLIC . src include)
|
||||
target_include_directories(${SD_LIB} PRIVATE src/core)
|
||||
target_include_directories(${SD_LIB} PUBLIC . thirdparty)
|
||||
target_compile_features(${SD_LIB} PUBLIC c_std_11 cxx_std_17)
|
||||
|
||||
@@ -277,7 +343,58 @@ if (SD_BUILD_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
|
||||
set(SD_PUBLIC_HEADERS include/stable-diffusion.h)
|
||||
set_target_properties(${SD_LIB} PROPERTIES PUBLIC_HEADER "${SD_PUBLIC_HEADERS}")
|
||||
|
||||
install(TARGETS ${SD_LIB} LIBRARY PUBLIC_HEADER)
|
||||
|
||||
#
|
||||
# install
|
||||
#
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(SD_INSTALL_VERSION "${SDCPP_BUILD_VERSION}")
|
||||
set(SD_INSTALL_COMMIT "${SDCPP_BUILD_COMMIT}")
|
||||
set(SD_SHARED_LIB ${SD_BUILD_SHARED_LIBS})
|
||||
|
||||
set(SD_INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE PATH "Location of header files")
|
||||
set(SD_LIB_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR} CACHE PATH "Location of library files")
|
||||
set(SD_BIN_INSTALL_DIR ${CMAKE_INSTALL_BINDIR} CACHE PATH "Location of binary files")
|
||||
|
||||
set(SD_PUBLIC_HEADERS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/stable-diffusion.h)
|
||||
|
||||
set_target_properties(${SD_LIB}
|
||||
PROPERTIES
|
||||
PUBLIC_HEADER "${SD_PUBLIC_HEADERS}")
|
||||
|
||||
|
||||
install(TARGETS ${SD_LIB}
|
||||
ARCHIVE
|
||||
LIBRARY
|
||||
RUNTIME
|
||||
PUBLIC_HEADER)
|
||||
|
||||
|
||||
configure_package_config_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cmake/stable-diffusion-config.cmake.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/stable-diffusion-config.cmake
|
||||
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/stable-diffusion
|
||||
PATH_VARS SD_INCLUDE_INSTALL_DIR
|
||||
SD_LIB_INSTALL_DIR
|
||||
SD_BIN_INSTALL_DIR )
|
||||
|
||||
write_basic_package_version_file(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/stable-diffusion-version.cmake
|
||||
VERSION ${SD_INSTALL_VERSION}
|
||||
COMPATIBILITY SameMajorVersion)
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/stable-diffusion-config.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/stable-diffusion-version.cmake
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/stable-diffusion)
|
||||
|
||||
configure_file(cmake/stable-diffusion.pc.in
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/stable-diffusion.pc"
|
||||
@ONLY)
|
||||
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/stable-diffusion.pc"
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Contributing
|
||||
|
||||
This document collects general contribution conventions for this repository.
|
||||
|
||||
## Before You Start
|
||||
|
||||
Before opening a PR, please search existing PRs to avoid duplicating ongoing work.
|
||||
|
||||
For large-scale refactors or changes with broad impact, please open an issue first to discuss the approach before submitting a PR.
|
||||
|
||||
If you want to update a third-party dependency, please open an issue first instead of submitting a direct PR. See [Dependency Updates](#dependency-updates) for details.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Keep each PR focused on one clear change. Large or overly complex PRs are harder to review and may not be merged.
|
||||
|
||||
Follow Conventional Commit-style subjects seen in history: `feat:`, `fix:`, `refactor:`, `ci:`, `docs:`, `chore:`. Keep subjects imperative and scoped.
|
||||
|
||||
PRs should include:
|
||||
|
||||
- What changed and why (short problem/solution summary).
|
||||
- Verification evidence when applicable (commands and key outputs).
|
||||
- Linked issue/PR context when applicable.
|
||||
- Screenshots or sample outputs for UI/visual behavior changes.
|
||||
|
||||
## Code Style
|
||||
|
||||
Format code according to the repository style before submitting changes.
|
||||
|
||||
Formatting follows `.clang-format` (Chromium base, 4-space indent, no tabs). Run `scripts/format-code.sh` or `scripts/format-code.ps1` before opening a PR. Keep C++ standard at C++17-compatible patterns used in this repo.
|
||||
|
||||
Naming conventions:
|
||||
|
||||
- Use `PascalCase` for class/struct/type names.
|
||||
- In `PascalCase` names, preserve common abbreviations in uppercase, for example `SD`, `API`, `HTTP`, `JSON`, `RGB`, `VAE`, `TAE`, `LoRA`, and `WebP`.
|
||||
- Use `snake_case` for functions, methods, variables, and file names unless an existing API requires a different style.
|
||||
- Use a trailing underscore for private data member names, for example `hidden_size_` or `tokenizer_`.
|
||||
- Use `.h` for C and C++ header files. Do not introduce new `.hpp` headers.
|
||||
- Use macro-based header include guards instead of `#pragma once`.
|
||||
- Format header include guards as `__SD_{PATH}__`, where `{PATH}` is the header path in uppercase snake case without the file extension. For example, `src/sample.h` should use `__SD_SAMPLE_H__`.
|
||||
- Do not introduce anonymous namespaces in new or modified code; prefer `static` file-local functions/variables or an explicit named namespace when scoping is needed.
|
||||
- In `class`/`struct` definitions, place data members before member functions unless an existing type already clearly follows a different pattern.
|
||||
- Keep `test_*.cpp` / `test_*.py` naming for tests.
|
||||
|
||||
Some older code in the project may not fully follow the current conventions. Please do not submit PRs that only rewrite existing code to match style rules.
|
||||
|
||||
When adding or modifying model implementations, follow the model config and weight detection conventions in [docs/model_config.md](docs/model_config.md).
|
||||
|
||||
## AI-Assisted Contributions
|
||||
|
||||
AI tools may be used to assist development, but contributors are responsible for the quality and correctness of the submitted code.
|
||||
|
||||
If any part of a contribution was generated with AI assistance, the contributor must perform a thorough human review before submitting the PR and understand every changed line.
|
||||
|
||||
Do not list AI tools as co-authors. The human contributor is the sole responsible author of the submitted code.
|
||||
|
||||
Please do not submit AI-generated code that you do not understand, and do not include meaningless experiments, temporary test code, or unrelated generated output in a PR.
|
||||
|
||||
## Dependency Updates
|
||||
|
||||
Do not submit PRs that update `ggml`. `ggml` updates are performed only after local validation by the maintainer.
|
||||
|
||||
Other third-party dependencies are not updated unless necessary. If you want to update a dependency, please open an issue first instead of submitting a direct PR.
|
||||
|
||||
## Security & Configuration
|
||||
|
||||
Do not commit model weights, secrets, or local absolute paths. Keep large binaries out of git unless intentionally tracked release assets.
|
||||
@@ -1,23 +0,0 @@
|
||||
ARG UBUNTU_VERSION=24.04
|
||||
|
||||
FROM ubuntu:$UBUNTU_VERSION AS build
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends build-essential git cmake
|
||||
|
||||
WORKDIR /sd.cpp
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN cmake . -B ./build
|
||||
RUN cmake --build ./build --config Release --parallel
|
||||
|
||||
FROM ubuntu:$UBUNTU_VERSION AS runtime
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install --yes --no-install-recommends libgomp1 && \
|
||||
apt-get clean
|
||||
|
||||
COPY --from=build /sd.cpp/build/bin/sd-cli /sd-cli
|
||||
COPY --from=build /sd.cpp/build/bin/sd-server /sd-server
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
@@ -1,25 +0,0 @@
|
||||
ARG CUDA_VERSION=12.6.3
|
||||
ARG UBUNTU_VERSION=24.04
|
||||
|
||||
FROM nvidia/cuda:${CUDA_VERSION}-cudnn-devel-ubuntu${UBUNTU_VERSION} AS build
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends build-essential git ccache cmake
|
||||
|
||||
WORKDIR /sd.cpp
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG CUDACXX=/usr/local/cuda/bin/nvcc
|
||||
RUN cmake . -B ./build -DSD_CUDA=ON
|
||||
RUN cmake --build ./build --config Release -j$(nproc)
|
||||
|
||||
FROM nvidia/cuda:${CUDA_VERSION}-cudnn-runtime-ubuntu${UBUNTU_VERSION} AS runtime
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install --yes --no-install-recommends libgomp1 && \
|
||||
apt-get clean
|
||||
|
||||
COPY --from=build /sd.cpp/build/bin/sd-cli /sd-cli
|
||||
COPY --from=build /sd.cpp/build/bin/sd-server /sd-server
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
@@ -1,24 +0,0 @@
|
||||
ARG MUSA_VERSION=rc4.2.0
|
||||
ARG UBUNTU_VERSION=22.04
|
||||
|
||||
FROM mthreads/musa:${MUSA_VERSION}-devel-ubuntu${UBUNTU_VERSION}-amd64 as build
|
||||
|
||||
RUN apt-get update && apt-get install -y ccache cmake git
|
||||
|
||||
WORKDIR /sd.cpp
|
||||
|
||||
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 -DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build . --config Release
|
||||
|
||||
FROM mthreads/musa:${MUSA_VERSION}-runtime-ubuntu${UBUNTU_VERSION}-amd64 as runtime
|
||||
|
||||
COPY --from=build /sd.cpp/build/bin/sd-cli /sd-cli
|
||||
COPY --from=build /sd.cpp/build/bin/sd-server /sd-server
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
@@ -1,20 +0,0 @@
|
||||
ARG SYCL_VERSION=2025.1.0-0
|
||||
|
||||
FROM intel/oneapi-basekit:${SYCL_VERSION}-devel-ubuntu24.04 AS build
|
||||
|
||||
RUN apt-get update && apt-get install -y cmake
|
||||
|
||||
WORKDIR /sd.cpp
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN mkdir build && cd build && \
|
||||
cmake .. -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DSD_SYCL=ON -DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build . --config Release -j$(nproc)
|
||||
|
||||
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
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
@@ -1,23 +0,0 @@
|
||||
ARG UBUNTU_VERSION=24.04
|
||||
|
||||
FROM ubuntu:$UBUNTU_VERSION AS build
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends build-essential git cmake libvulkan-dev glslc
|
||||
|
||||
WORKDIR /sd.cpp
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN cmake . -B ./build -DSD_VULKAN=ON
|
||||
RUN cmake --build ./build --config Release --parallel
|
||||
|
||||
FROM ubuntu:$UBUNTU_VERSION AS runtime
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install --yes --no-install-recommends libgomp1 libvulkan1 mesa-vulkan-drivers && \
|
||||
apt-get clean
|
||||
|
||||
COPY --from=build /sd.cpp/build/bin/sd-cli /sd-cli
|
||||
COPY --from=build /sd.cpp/build/bin/sd-server /sd-server
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
@@ -15,29 +15,19 @@ API and command-line option may change frequently.***
|
||||
|
||||
## 🔥Important News
|
||||
|
||||
* **2026/06/25** 🚀 stable-diffusion.cpp now supports **Krea2**
|
||||
* **2026/06/04** 🚀 stable-diffusion.cpp now supports **Ideogram4**
|
||||
* **2026/05/31** 🚀 stable-diffusion.cpp now supports **PiD**
|
||||
* **2026/05/27** 🚀 stable-diffusion.cpp now supports **Lens**
|
||||
* **2026/05/17** 🚀 stable-diffusion.cpp now supports **LTX-2.3**
|
||||
* **2026/04/11** 🚀 stable-diffusion.cpp now uses a brand-new embedded web UI.
|
||||
👉 Details: [PR #1408](https://github.com/leejet/stable-diffusion.cpp/pull/1408)
|
||||
|
||||
* **2026/01/18** 🚀 stable-diffusion.cpp now supports **FLUX.2-klein**
|
||||
👉 Details: [PR #1193](https://github.com/leejet/stable-diffusion.cpp/pull/1193)
|
||||
|
||||
* **2025/12/01** 🚀 stable-diffusion.cpp now supports **Z-Image**
|
||||
👉 Details: [PR #1020](https://github.com/leejet/stable-diffusion.cpp/pull/1020)
|
||||
|
||||
* **2025/11/30** 🚀 stable-diffusion.cpp now supports **FLUX.2-dev**
|
||||
👉 Details: [PR #1016](https://github.com/leejet/stable-diffusion.cpp/pull/1016)
|
||||
|
||||
* **2025/10/13** 🚀 stable-diffusion.cpp now supports **Qwen-Image-Edit / Qwen-Image-Edit 2509**
|
||||
👉 Details: [PR #877](https://github.com/leejet/stable-diffusion.cpp/pull/877)
|
||||
|
||||
* **2025/10/12** 🚀 stable-diffusion.cpp now supports **Qwen-Image**
|
||||
👉 Details: [PR #851](https://github.com/leejet/stable-diffusion.cpp/pull/851)
|
||||
|
||||
* **2025/09/14** 🚀 stable-diffusion.cpp now supports **Wan2.1 Vace**
|
||||
👉 Details: [PR #819](https://github.com/leejet/stable-diffusion.cpp/pull/819)
|
||||
|
||||
* **2025/09/06** 🚀 stable-diffusion.cpp now supports **Wan2.1 / Wan2.2**
|
||||
👉 Details: [PR #778](https://github.com/leejet/stable-diffusion.cpp/pull/778)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -45,30 +35,42 @@ API and command-line option may change frequently.***
|
||||
- Super lightweight and without external dependencies
|
||||
- Supported models
|
||||
- Image Models
|
||||
- SD1.x, SD2.x, [SD-Turbo](https://huggingface.co/stabilityai/sd-turbo)
|
||||
- SDXL, [SDXL-Turbo](https://huggingface.co/stabilityai/sdxl-turbo)
|
||||
- [SD1.x, SD2.x, SD-Turbo](./docs/sd.md)
|
||||
- [SDXL, SDXL-Turbo](./docs/sd.md)
|
||||
- [Some SD1.x and SDXL distilled models](./docs/distilled_sd.md)
|
||||
- [SD3/SD3.5](./docs/sd3.md)
|
||||
- [FLUX.1-dev/FLUX.1-schnell](./docs/flux.md)
|
||||
- [FLUX.2-dev/FLUX.2-klein](./docs/flux2.md)
|
||||
- [Lens](./docs/lens.md)
|
||||
- [Chroma](./docs/chroma.md)
|
||||
- [Chroma1-Radiance](./docs/chroma_radiance.md)
|
||||
- [Qwen Image](./docs/qwen_image.md)
|
||||
- [PiD](./docs/pid.md)
|
||||
- [LongCat Image](./docs/longcat_image.md)
|
||||
- [Z-Image](./docs/z_image.md)
|
||||
- [MiniT2I](./docs/minit2i.md)
|
||||
- [Ovis-Image](./docs/ovis_image.md)
|
||||
- [Anima](./docs/anima.md)
|
||||
- [ERNIE-Image](./docs/ernie_image.md)
|
||||
- [Boogu Image](./docs/boogu_image.md)
|
||||
- [Krea2](./docs/krea2.md)
|
||||
- [SeFi-Image](./docs/sefi_image.md)
|
||||
- [HiDream-O1-Image](./docs/hidream_o1_image.md)
|
||||
- [Ideogram4](./docs/ideogram4.md)
|
||||
- Image Edit Models
|
||||
- [FLUX.1-Kontext-dev](./docs/kontext.md)
|
||||
- [Qwen Image Edit series](./docs/qwen_image_edit.md)
|
||||
- [LongCat Image Edit](./docs/longcat_image.md)
|
||||
- [Boogu Image Edit](./docs/boogu_image.md)
|
||||
- Video Models
|
||||
- [Wan2.1/Wan2.2](./docs/wan.md)
|
||||
- [PhotoMaker](https://github.com/TencentARC/PhotoMaker) support.
|
||||
- [LTX-2.3](./docs/ltx2.md)
|
||||
- [PhotoMaker](./docs/photo_maker.md) support.
|
||||
- Control Net support with SD 1.5
|
||||
- LoRA support, same as [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#lora)
|
||||
- Latent Consistency Models support (LCM/LCM-LoRA)
|
||||
- Faster and memory efficient latent decoding with [TAESD](https://github.com/madebyollin/taesd)
|
||||
- Upscale images generated with [ESRGAN](https://github.com/xinntao/Real-ESRGAN)
|
||||
- Faster and memory efficient latent decoding with [TAESD](./docs/taesd.md)
|
||||
- Upscale images generated with [ESRGAN](./docs/esrgan.md)
|
||||
- Supported backends
|
||||
- CPU (AVX, AVX2 and AVX512 support for x86 architectures)
|
||||
- CUDA
|
||||
@@ -132,27 +134,14 @@ API and command-line option may change frequently.***
|
||||
## Performance
|
||||
|
||||
If you want to improve performance or reduce VRAM/RAM usage, please refer to [performance guide](./docs/performance.md).
|
||||
For runtime and parameter backend placement, see the [backend selection guide](./docs/backend.md).
|
||||
|
||||
## More Guides
|
||||
|
||||
- [SD1.x/SD2.x/SDXL](./docs/sd.md)
|
||||
- [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.1-Kontext-dev](./docs/kontext.md)
|
||||
- [Chroma](./docs/chroma.md)
|
||||
- [🔥Qwen Image](./docs/qwen_image.md)
|
||||
- [🔥Qwen Image Edit series](./docs/qwen_image_edit.md)
|
||||
- [🔥Wan2.1/Wan2.2](./docs/wan.md)
|
||||
- [🔥Z-Image](./docs/z_image.md)
|
||||
- [Ovis-Image](./docs/ovis_image.md)
|
||||
- [Anima](./docs/anima.md)
|
||||
- [ERNIE-Image](./docs/ernie_image.md)
|
||||
- [Backend selection](./docs/backend.md)
|
||||
- [RPC](./docs/rpc.md)
|
||||
- [LoRA](./docs/lora.md)
|
||||
- [LCM/LCM-LoRA](./docs/lcm.md)
|
||||
- [Using PhotoMaker to personalize image generation](./docs/photo_maker.md)
|
||||
- [Using ESRGAN to upscale results](./docs/esrgan.md)
|
||||
- [Using TAESD to faster decoding](./docs/taesd.md)
|
||||
- [Docker](./docs/docker.md)
|
||||
- [Quantization and GGUF](./docs/quantization_and_gguf.md)
|
||||
- [Inference acceleration via caching](./docs/caching.md)
|
||||
@@ -191,19 +180,3 @@ Thank you to all the people who have already contributed to stable-diffusion.cpp
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#leejet/stable-diffusion.cpp&Date)
|
||||
|
||||
## References
|
||||
|
||||
- [ggml](https://github.com/ggml-org/ggml)
|
||||
- [diffusers](https://github.com/huggingface/diffusers)
|
||||
- [stable-diffusion](https://github.com/CompVis/stable-diffusion)
|
||||
- [sd3-ref](https://github.com/Stability-AI/sd3-ref)
|
||||
- [stable-diffusion-stability-ai](https://github.com/Stability-AI/stablediffusion)
|
||||
- [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui)
|
||||
- [ComfyUI](https://github.com/comfyanonymous/ComfyUI)
|
||||
- [k-diffusion](https://github.com/crowsonkb/k-diffusion)
|
||||
- [latent-consistency-model](https://github.com/luosiallen/latent-consistency-model)
|
||||
- [generative-models](https://github.com/Stability-AI/generative-models/)
|
||||
- [PhotoMaker](https://github.com/TencentARC/PhotoMaker)
|
||||
- [Wan2.1](https://github.com/Wan-Video/Wan2.1)
|
||||
- [Wan2.2](https://github.com/Wan-Video/Wan2.2)
|
||||
|
||||
|
After Width: | Height: | Size: 477 KiB |
|
After Width: | Height: | Size: 489 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 289 KiB |
|
After Width: | Height: | Size: 630 KiB |
|
After Width: | Height: | Size: 555 KiB |
|
After Width: | Height: | Size: 423 KiB |
|
After Width: | Height: | Size: 9.0 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
@@ -0,0 +1,37 @@
|
||||
set(SD_VERSION "@SD_INSTALL_VERSION@")
|
||||
set(SD_BUILD_COMMIT "@SD_INSTALL_COMMIT@")
|
||||
set(SD_SHARED_LIB @SD_SHARED_LIB@)
|
||||
|
||||
@PACKAGE_INIT@
|
||||
|
||||
set_and_check(SD_INCLUDE_DIR "@PACKAGE_SD_INCLUDE_INSTALL_DIR@")
|
||||
set_and_check(SD_LIB_DIR "@PACKAGE_SD_LIB_INSTALL_DIR@")
|
||||
set(SD_BIN_DIR "@PACKAGE_SD_BIN_INSTALL_DIR@")
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
find_dependency(ggml REQUIRED HINTS "${SD_LIB_DIR}/cmake")
|
||||
|
||||
if(NOT TARGET stable-diffusion)
|
||||
find_library(stable-diffusion_LIBRARY stable-diffusion
|
||||
REQUIRED
|
||||
HINTS "${SD_LIB_DIR}"
|
||||
NO_CMAKE_FIND_ROOT_PATH
|
||||
)
|
||||
|
||||
add_library(stable-diffusion UNKNOWN IMPORTED)
|
||||
set_target_properties(stable-diffusion
|
||||
PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${SD_INCLUDE_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "ggml::ggml"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
|
||||
IMPORTED_LOCATION "${stable-diffusion_LIBRARY}"
|
||||
INTERFACE_COMPILE_FEATURES "c_std_11;cxx_std_17"
|
||||
POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
if(SD_SHARED_LIB)
|
||||
target_compile_definitions(stable-diffusion
|
||||
INTERFACE SD_BUILD_SHARED_LIB)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
check_required_components(stable-diffusion)
|
||||
@@ -0,0 +1,11 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
|
||||
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
|
||||
|
||||
Name: stable-diffusion
|
||||
Description: Diffusion model(SD,Flux,Wan,Qwen Image,Z-Image,...) inference in pure C/C++
|
||||
Version: @SDCPP_BUILD_VERSION@
|
||||
Libs: -L${libdir} -lstable-diffusion
|
||||
Libs.private: -lggml -lggml-base
|
||||
Cflags: -I${includedir}
|
||||
@@ -0,0 +1,43 @@
|
||||
ARG UBUNTU_VERSION=24.04
|
||||
|
||||
FROM ubuntu:$UBUNTU_VERSION 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 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
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN cmake . -B ./build \
|
||||
-DSD_BUILD_SHARED_LIBS=ON \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DSD_BUILD_SHARED_GGML_LIB=ON \
|
||||
-DGGML_BACKEND_DL=ON \
|
||||
-DGGML_CPU_ALL_VARIANTS=ON \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DCMAKE_INSTALL_RPATH='$ORIGIN'
|
||||
RUN cmake --build ./build --config Release --parallel
|
||||
|
||||
FROM ubuntu:$UBUNTU_VERSION AS runtime
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install --yes --no-install-recommends libgomp1 && \
|
||||
apt-get clean
|
||||
|
||||
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" ]
|
||||
@@ -0,0 +1,60 @@
|
||||
ARG CUDA_VERSION=12.6.3
|
||||
ARG UBUNTU_VERSION=24.04
|
||||
|
||||
FROM nvidia/cuda:${CUDA_VERSION}-cudnn-devel-ubuntu${UBUNTU_VERSION} 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 ccache cmake 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
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG CUDACXX=/usr/local/cuda/bin/nvcc
|
||||
ARG CUDA_ARCHITECTURES=""
|
||||
ARG GGML_CUDA_FA_ALL_QUANTS=""
|
||||
ARG GGML_CUDA_ENABLE_DYNAMIC_CPU_BACKENDS=ON
|
||||
|
||||
RUN set -- \
|
||||
-DSD_CUDA=ON; \
|
||||
if [ "${GGML_CUDA_ENABLE_DYNAMIC_CPU_BACKENDS}" = "ON" ]; then \
|
||||
set -- "$@" \
|
||||
-DSD_BUILD_SHARED_LIBS=ON \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DSD_BUILD_SHARED_GGML_LIB=ON \
|
||||
-DGGML_BACKEND_DL=ON \
|
||||
-DGGML_CPU_ALL_VARIANTS=ON \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
"-DCMAKE_INSTALL_RPATH=\$ORIGIN"; \
|
||||
fi; \
|
||||
if [ -n "${CUDA_ARCHITECTURES}" ]; then \
|
||||
set -- "$@" "-DCMAKE_CUDA_ARCHITECTURES=${CUDA_ARCHITECTURES}"; \
|
||||
fi; \
|
||||
if [ -n "${GGML_CUDA_FA_ALL_QUANTS}" ]; then \
|
||||
set -- "$@" "-DGGML_CUDA_FA_ALL_QUANTS=${GGML_CUDA_FA_ALL_QUANTS}"; \
|
||||
fi; \
|
||||
cmake . -B ./build "$@"
|
||||
RUN cmake --build ./build --config Release -j$(nproc)
|
||||
|
||||
FROM nvidia/cuda:${CUDA_VERSION}-cudnn-runtime-ubuntu${UBUNTU_VERSION} AS runtime
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install --yes --no-install-recommends libgomp1 && \
|
||||
apt-get clean
|
||||
|
||||
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" ]
|
||||
@@ -0,0 +1,45 @@
|
||||
ARG MUSA_VERSION=rc4.2.0
|
||||
ARG UBUNTU_VERSION=22.04
|
||||
|
||||
FROM mthreads/musa:${MUSA_VERSION}-devel-ubuntu${UBUNTU_VERSION}-amd64 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 && \
|
||||
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
|
||||
|
||||
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 \
|
||||
-DSD_BUILD_SHARED_LIBS=ON \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DSD_BUILD_SHARED_GGML_LIB=ON \
|
||||
-DGGML_BACKEND_DL=ON \
|
||||
-DGGML_CPU_ALL_VARIANTS=ON \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DCMAKE_INSTALL_RPATH='$ORIGIN' \
|
||||
-DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build . --config Release
|
||||
|
||||
FROM mthreads/musa:${MUSA_VERSION}-runtime-ubuntu${UBUNTU_VERSION}-amd64 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
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
@@ -0,0 +1,32 @@
|
||||
# ggml SYCL hardware detection uses BMG G31/WCL architecture enums added in oneAPI 2025.3.
|
||||
ARG SYCL_VERSION=2025.3.2-0
|
||||
|
||||
FROM intel/oneapi-basekit:${SYCL_VERSION}-devel-ubuntu24.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 cmake 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
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN mkdir build && cd build && \
|
||||
cmake .. -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DSD_SYCL=ON -DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build . --config Release -j$(nproc)
|
||||
|
||||
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
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
@@ -0,0 +1,44 @@
|
||||
ARG UBUNTU_VERSION=24.04
|
||||
|
||||
FROM ubuntu:$UBUNTU_VERSION 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 libvulkan-dev glslc spirv-headers 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
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN cmake . -B ./build \
|
||||
-DSD_VULKAN=ON \
|
||||
-DSD_BUILD_SHARED_LIBS=ON \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DSD_BUILD_SHARED_GGML_LIB=ON \
|
||||
-DGGML_BACKEND_DL=ON \
|
||||
-DGGML_CPU_ALL_VARIANTS=ON \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DCMAKE_INSTALL_RPATH='$ORIGIN'
|
||||
RUN cmake --build ./build --config Release -j$(nproc)
|
||||
|
||||
FROM ubuntu:$UBUNTU_VERSION AS runtime
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install --yes --no-install-recommends libgomp1 libvulkan1 mesa-vulkan-drivers && \
|
||||
apt-get clean
|
||||
|
||||
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" ]
|
||||
@@ -15,7 +15,7 @@
|
||||
## Examples
|
||||
|
||||
```sh
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\..\ComfyUI\models\diffusion_models\anima-preview.safetensors --vae ..\..\ComfyUI\models\vae\qwen_image_vae.safetensors --llm ..\..\ComfyUI\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 --diffusion-fa
|
||||
```
|
||||
|
||||
<img alt="anima image example" src="../assets/anima/example.png" />
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
# Backend selection
|
||||
|
||||
`stable-diffusion.cpp` has two backend assignments:
|
||||
|
||||
- `--backend` selects the runtime backend used to execute model graphs.
|
||||
- `--params-backend` selects where model parameters are kept.
|
||||
|
||||
If `--params-backend` is not set, parameters use the same backend as their module runtime backend.
|
||||
|
||||
## Syntax
|
||||
|
||||
A backend assignment can be a single backend name:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend cpu
|
||||
```
|
||||
|
||||
This applies to every module that does not have a more specific assignment.
|
||||
|
||||
Assignments can also target individual modules:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend te=cpu,vae=cuda0,diffusion=vulkan0
|
||||
```
|
||||
|
||||
The same syntax is used for parameter placement:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend cuda0 --params-backend te=cpu,vae=cpu
|
||||
```
|
||||
|
||||
`--params-backend` also accepts the special value `disk`:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend cuda0 --params-backend disk
|
||||
```
|
||||
|
||||
`--max-vram` can target resolved backend/device names:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend diffusion=cuda0,vae=vulkan0 --max-vram cuda0=6,vulkan0=2
|
||||
```
|
||||
|
||||
The budget applies to every module running on that backend.
|
||||
|
||||
Module names are case-insensitive. Hyphens and underscores in module names are ignored, so `clip_vision`, `clip-vision`, and `clipvision` are equivalent.
|
||||
|
||||
`all=`, `default=`, and `*=` can be used to set the default backend inside a mixed assignment:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend all=cuda0,te=cpu
|
||||
```
|
||||
|
||||
## Multiple devices per module (layer split)
|
||||
|
||||
A `--backend` module assignment can list several devices separated by `&`:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend "diffusion=cuda0&cuda1"
|
||||
```
|
||||
|
||||
The module's transformer blocks are then distributed across the listed devices
|
||||
in contiguous ranges sized proportionally to each device's free memory (minus a
|
||||
compute-buffer headroom of about 2 GiB per device), and the
|
||||
module's graphs are executed with a `ggml_backend_sched` that runs each block
|
||||
on the device holding its weights, copying the residual stream at the range
|
||||
boundaries. The first device in the list is the module's main device: it also
|
||||
holds the non-block tensors (embeddings, final norms, small sub-runners such as
|
||||
CLIP models or projectors) and the graph inputs/outputs.
|
||||
|
||||
Layer split is supported for the `diffusion` and `te` modules. For `te` it
|
||||
applies to the dominant text encoder (`t5xxl` or the LLM); other modules accept
|
||||
only a single device. If the module has no recognizable transformer blocks, the
|
||||
assignment falls back to the first listed device.
|
||||
|
||||
`--params-backend` accepts no device lists. If the module has no explicit
|
||||
params assignment, each block range's parameters are loaded directly to (and,
|
||||
with `--params-backend diffusion=disk`, released directly from) its own device;
|
||||
an explicit assignment such as `te=cpu` keeps the parameters on that backend
|
||||
and stages each range to its device on demand.
|
||||
|
||||
Layer split cannot be combined with `--max-vram` graph-cut segmentation or
|
||||
`--stream-layers` for the split module; those are single-device mechanisms and
|
||||
are disabled for it.
|
||||
|
||||
Use `--list-devices` to see the device names available on the system.
|
||||
|
||||
### Row split (`--split-mode row`)
|
||||
|
||||
`--split-mode` selects how a multi-device module distributes its weights:
|
||||
`layer` (the default, described above) or `row`. It accepts a single mode or
|
||||
per-module assignments:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend "diffusion=cuda0&cuda1" --split-mode row
|
||||
sd-cli -m model.safetensors -p "a cat" --backend "diffusion=cuda0&cuda1,te=cuda0&cuda1" --split-mode diffusion=row,te=layer
|
||||
```
|
||||
|
||||
In row mode the module keeps executing on its main (first listed) device, but
|
||||
its transformer-block matmul weights are allocated in the backend's row-split
|
||||
buffer type, which slices each weight's rows across the listed devices in
|
||||
proportion to free memory and runs those matmuls on all devices in parallel.
|
||||
Compared to a layer split this uses all GPUs within every layer (instead of
|
||||
sequentially device by device) at the cost of a cross-device reduction per
|
||||
matmul - usually the faster option when the devices have fast interconnect.
|
||||
|
||||
Row split requires backend support for split buffers and is currently
|
||||
available on CUDA only; on other backends (or when the listed devices belong
|
||||
to different backend registries) the module falls back to a layer split.
|
||||
Embeddings, normalization weights, biases and other non-block tensors stay in
|
||||
regular buffers on the main device.
|
||||
|
||||
Direct ("immediately") LoRA application cannot patch row-split tensors; with
|
||||
`--split-mode row` the automatic LoRA mode selects runtime application, and an
|
||||
explicit `--lora-apply-mode immediately` skips the split tensors with a
|
||||
warning.
|
||||
|
||||
## Automatic placement (`--auto-fit`)
|
||||
|
||||
`--auto-fit` derives the `diffusion` / `te` / `vae` placements from the model
|
||||
metadata and the per-device memory budgets, then feeds them into the same
|
||||
backend assignment mechanism described above (the chosen specs are printed).
|
||||
`--backend` and `--params-backend` are ignored while auto-fit is enabled.
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --auto-fit
|
||||
sd-cli -m model.safetensors -p "a cat" --auto-fit --max-vram cuda0=8,cuda1=14
|
||||
sd-cli -m model.safetensors -p "a cat" --auto-fit --split-mode row
|
||||
```
|
||||
|
||||
Budgets reuse `--max-vram`: a positive per-device value caps what auto-fit
|
||||
plans with on that device, a negative value means "free memory minus that many
|
||||
GiB", and with no budget set each device's free memory minus a 512 MiB margin
|
||||
is used. (The same values still drive graph-cut segmented execution for
|
||||
modules that end up on a single device.)
|
||||
|
||||
When everything fits resident, components are simply spread across the
|
||||
available GPUs. When it does not, auto-fit switches to time-share mode: the
|
||||
heavy components get `disk` params residency (loaded for their phase, freed
|
||||
after), and a component too large for any single device is split across all
|
||||
GPUs with the layer/row split mechanism (`--split-mode` selects which, layer
|
||||
by default). Components that fit nowhere fall back to the CPU. If a VAE decode
|
||||
still runs out of memory, tiling is enabled and the decode retried once.
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Purpose | Accepted names |
|
||||
| --- | --- | --- |
|
||||
| `diffusion` | UNet, DiT, MMDiT, Flux, Wan, Qwen Image, and other diffusion models | `diffusion`, `model`, `unet`, `dit` |
|
||||
| `te` | Text encoders and conditioners | `te`, `clip`, `text`, `textencoder`, `textencoders`, `conditioner`, `cond`, `llm`, `t5`, `t5xxl` |
|
||||
| `clip_vision` | CLIP vision encoder | `clip_vision`, `clipvision`, `clip-vision`, `vision` |
|
||||
| `vae` | VAE and TAE | `vae`, `firststage`, `autoencoder`, `tae` |
|
||||
| `controlnet` | ControlNet | `controlnet`, `control` |
|
||||
| `photomaker` | PhotoMaker ID encoder and PhotoMaker LoRA | `photomaker`, `photomakerid`, `pmid`, `photo` |
|
||||
| `upscaler` | ESRGAN upscaler | `upscaler`, `esrgan`, `hires` |
|
||||
|
||||
`te` is the preferred module name for text encoders. `clip` is kept as an accepted alias because many existing commands and model names use CLIP terminology.
|
||||
|
||||
## Backend names
|
||||
|
||||
Backend names are resolved against the GGML backend device list. Matching is case-insensitive and accepts exact names or unique prefixes, so common values include names such as:
|
||||
|
||||
- `cpu`
|
||||
- `cuda0`
|
||||
- `vulkan0`
|
||||
- `metal`
|
||||
|
||||
The special values `auto`, `default`, and an empty backend name select the default backend. The default preference is GPU, then integrated GPU, then CPU.
|
||||
|
||||
The special value `gpu` selects the first GPU backend, falling back to the first integrated GPU backend.
|
||||
|
||||
The special value `disk` is accepted only by `--params-backend`. `--backend disk` is invalid because `disk` is a parameter residency mode, not a runtime compute backend.
|
||||
|
||||
## Runtime backend vs. parameter backend
|
||||
|
||||
The runtime backend controls where graph execution runs. The parameter backend controls where model weights are allocated or whether they are reloaded from disk on demand.
|
||||
|
||||
For example:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend cuda0 --params-backend cpu
|
||||
```
|
||||
|
||||
This runs all modules on `cuda0`, but stores parameters in CPU RAM. During execution, parameters are moved to the runtime backend as needed.
|
||||
|
||||
For example:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend cuda0 --params-backend disk
|
||||
```
|
||||
|
||||
This runs all modules on `cuda0`, reloads parameters from the model file as needed, and releases those parameter buffers after use.
|
||||
|
||||
`disk` is never selected implicitly. If `--params-backend` is not set, parameters use the runtime backend.
|
||||
|
||||
Per-module assignments can be mixed:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend diffusion=cuda0,te=cpu,vae=cpu --params-backend diffusion=cuda0,te=cpu,vae=cpu
|
||||
```
|
||||
|
||||
This keeps text encoding and VAE execution on CPU while the diffusion model runs on GPU.
|
||||
|
||||
## Backend sharing and lifetime
|
||||
|
||||
Backends are managed by `SDBackendManager`.
|
||||
|
||||
Within one manager, backend instances are cached by resolved backend device name. If multiple modules request the same backend, they share the same `ggml_backend_t`.
|
||||
|
||||
For example:
|
||||
|
||||
```shell
|
||||
--backend te=cpu,vae=cpu
|
||||
```
|
||||
|
||||
uses one shared CPU backend for both `te` and `vae` runtime execution.
|
||||
|
||||
Runtime and parameter assignments also share the same backend cache. If `--backend diffusion=cuda0` and `--params-backend diffusion=cuda0` resolve to the same device, both use the same backend instance.
|
||||
|
||||
`--params-backend disk` does not create a separate backend instance. Parameters are loaded lazily using the module runtime backend.
|
||||
|
||||
`SDBackendManager` owns the backend instances and frees them when the context or upscaler is destroyed. Model runners receive non-owning runtime and parameter backend pointers and do not free them.
|
||||
|
||||
## Compatibility flags
|
||||
|
||||
The example CLI/server still accepts these older CPU placement flags as compatibility aliases:
|
||||
|
||||
- `--clip-on-cpu`
|
||||
- `--vae-on-cpu`
|
||||
- `--control-net-cpu`
|
||||
- `--offload-to-cpu`
|
||||
|
||||
`--clip-on-cpu`, `--vae-on-cpu`, and `--control-net-cpu` are deprecated. The example argument layer prepends `te=cpu`, `vae=cpu`, and `controlnet=cpu` to `--backend` before creating the context.
|
||||
|
||||
`--offload-to-cpu` prepends a CPU default to the parameter assignment in the caller before creating the context:
|
||||
|
||||
```shell
|
||||
--params-backend '*=cpu'
|
||||
```
|
||||
|
||||
Because this default is inserted first, later explicit `--params-backend` entries can still override it, for example `--offload-to-cpu --params-backend te=disk` keeps non-TE parameters on CPU and reloads TE parameters from disk.
|
||||
|
||||
Library callers should set `backend` and `params_backend` directly. The old CPU/offload fields are no longer part of the C API. Explicit `--backend` and `--params-backend` assignments are preferred for new commands.
|
||||
@@ -0,0 +1,31 @@
|
||||
# How to Use
|
||||
|
||||
Boogu Image uses a Boogu diffusion transformer, the FLUX VAE, and Qwen3-VL as the LLM text and vision encoder.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download Boogu Image
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Boogu-Image/tree/main/diffusion_models
|
||||
- Download vae
|
||||
- safetensors: https://huggingface.co/black-forest-labs/FLUX.1-dev/blob/main/ae.safetensors
|
||||
- Download Qwen3-VL 8B
|
||||
- gguf: https://huggingface.co/unsloth/Qwen3-VL-8B-Instruct-GGUF/tree/main
|
||||
- For image editing with GGUF text encoders, also download the matching mmproj file and pass it with `--llm_vision`.
|
||||
|
||||
## Examples
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
<img width="256" alt="Boogu Image Base example" src="../assets/boogu/example.png" />
|
||||
|
||||
### 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'"
|
||||
```
|
||||
|
||||
<img width="256" alt="Boogu Image Edit example" src="../assets/boogu/edit_example.png" />
|
||||
@@ -102,6 +102,11 @@ cmake --build . --config Release
|
||||
## Build with Vulkan
|
||||
|
||||
Install Vulkan SDK from https://www.lunarg.com/vulkan-sdk/.
|
||||
On Ubuntu, install the Vulkan development packages and SPIR-V headers:
|
||||
|
||||
```shell
|
||||
sudo apt-get install build-essential libvulkan-dev glslc spirv-headers
|
||||
```
|
||||
|
||||
```shell
|
||||
mkdir build && cd build
|
||||
|
||||
@@ -15,7 +15,7 @@ You can run Chroma using stable-diffusion.cpp with a GPU that has 6GB or even 4G
|
||||
You can download the preconverted gguf weights from [silveroxides/Chroma-GGUF](https://huggingface.co/silveroxides/Chroma-GGUF), this way you don't have to do the conversion yourself.
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M convert -m ..\..\ComfyUI\models\unet\chroma-unlocked-v40.safetensors -o ..\models\chroma-unlocked-v40-q8_0.gguf -v --type q8_0
|
||||
.\bin\Release\sd-cli.exe -M convert -m ..\models\unet\chroma-unlocked-v40.safetensors -o ..\models\chroma-unlocked-v40-q8_0.gguf -v --type q8_0
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
## Examples
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\..\ComfyUI\models\diffusion_models\Chroma1-Radiance-v0.4-Q8_0.gguf --t5xxl ..\..\ComfyUI\models\clip\t5xxl_fp16.safetensors -p "a lovely cat holding a sign says 'chroma radiance cpp'" --cfg-scale 4.0 --sampling-method euler -v
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Chroma1-Radiance-v0.4-Q8_0.gguf --t5xxl ..\models\clip\t5xxl_fp16.safetensors -p "a lovely cat holding a sign says 'chroma radiance cpp'" --cfg-scale 4.0 --sampling-method euler -v
|
||||
```
|
||||
|
||||
<img alt="Chroma1-Radiance" src="../assets/flux/chroma1-radiance.png" />
|
||||
|
||||
@@ -19,7 +19,7 @@ docker run --rm --init -v /path/to/models:/models -v /path/to/output/:/output -p
|
||||
## Building using Docker
|
||||
|
||||
```shell
|
||||
docker build -t sd .
|
||||
docker build -f docker/Dockerfile -t sd .
|
||||
```
|
||||
|
||||
## Building variants using Docker
|
||||
@@ -27,7 +27,7 @@ docker build -t sd .
|
||||
Vulkan:
|
||||
|
||||
```shell
|
||||
docker build -f Dockerfile.vulkan -t sd .
|
||||
docker build -f docker/Dockerfile.vulkan -t sd .
|
||||
```
|
||||
|
||||
## Run locally built image's CLI
|
||||
|
||||
@@ -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 ..\..\ComfyUI\models\diffusion_models\ernie-image-turbo.safetensors --vae ..\..\ComfyUI\models\vae\flux2_ae.safetensors --llm ..\..\ComfyUI\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 --diffusion-fa
|
||||
```
|
||||
|
||||
<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 ..\..\ComfyUI\models\diffusion_models\ernie-image-UD-Q4_K_M.gguf --vae ..\..\ComfyUI\models\vae\flux2_ae.safetensors --llm ..\..\ComfyUI\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 --diffusion-fa
|
||||
```
|
||||
|
||||
<img width="256" alt="ERNIE-Image example" src="../assets/ernie_image/example.png" />
|
||||
|
||||
@@ -17,7 +17,7 @@ You can download the preconverted gguf weights from [FLUX.1-dev-gguf](https://hu
|
||||
|
||||
For example:
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M convert -m ..\..\ComfyUI\models\unet\flux1-dev.sft -o ..\models\flux1-dev-q8_0.gguf -v --type q8_0
|
||||
.\bin\Release\sd-cli.exe -M convert -m ..\models\unet\flux1-dev.sft -o ..\models\flux1-dev-q8_0.gguf -v --type q8_0
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
### Examples
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\..\ComfyUI\models\diffusion_models\flux2-dev-Q4_K_S.gguf --vae ..\..\ComfyUI\models\vae\flux2_ae.safetensors --llm ..\..\ComfyUI\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 --diffusion-fa --offload-to-cpu
|
||||
```
|
||||
|
||||
<img alt="flux2 example" src="../assets/flux2/example.png" />
|
||||
@@ -42,19 +42,19 @@
|
||||
### Examples
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\..\ComfyUI\models\diffusion_models\flux-2-klein-4b.safetensors --vae ..\..\ComfyUI\models\vae\flux2_ae.safetensors --llm ..\..\ComfyUI\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 --diffusion-fa
|
||||
```
|
||||
|
||||
<img alt="flux2-klein-4b" src="../assets/flux2/flux2-klein-4b.png" />
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\..\ComfyUI\models\diffusion_models\flux-2-klein-4b.safetensors --vae ..\..\ComfyUI\models\vae\flux2_ae.safetensors --llm ..\..\ComfyUI\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 --diffusion-fa --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 ..\..\ComfyUI\models\diffusion_models\flux-2-klein-base-4b.safetensors --vae ..\..\ComfyUI\models\vae\flux2_ae.safetensors --llm ..\..\ComfyUI\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 --diffusion-fa
|
||||
```
|
||||
|
||||
<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 ..\..\ComfyUI\models\diffusion_models\flux-2-klein-9b.safetensors --vae ..\..\ComfyUI\models\vae\flux2_ae.safetensors --llm ..\..\ComfyUI\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 --diffusion-fa
|
||||
```
|
||||
|
||||
<img alt="flux2-klein-9b" src="../assets/flux2/flux2-klein-9b.png" />
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\..\ComfyUI\models\diffusion_models\flux-2-klein-9b.safetensors --vae ..\..\ComfyUI\models\vae\flux2_ae.safetensors --llm ..\..\ComfyUI\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 --diffusion-fa --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 ..\..\ComfyUI\models\diffusion_models\flux-2-klein-base-9b.safetensors --vae ..\..\ComfyUI\models\vae\flux2_ae.safetensors --llm ..\..\ComfyUI\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 --diffusion-fa
|
||||
```
|
||||
|
||||
<img alt="flux2-klein-base-9b" src="../assets/flux2/flux2-klein-base-9b.png" />
|
||||
@@ -0,0 +1,20 @@
|
||||
# How to Use
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download HiDream-O1-Image-Dev
|
||||
- safetensors: https://huggingface.co/Comfy-Org/HiDream-O1-Image/tree/main/checkpoints
|
||||
- Download HiDream-O1-Image
|
||||
- safetensors: https://huggingface.co/Comfy-Org/HiDream-O1-Image/tree/main/checkpoints
|
||||
|
||||
## Examples
|
||||
|
||||
### HiDream-O1-Image-Dev
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -m ..\models\diffusion_models\hidream_o1_image_dev_bf16.safetensors -p "a lovely cat holding a sign says
|
||||
'hidream o1 cpp'" --cfg-scale 1.0 -v -H 1024 -W 1024
|
||||
```
|
||||
|
||||
<img width="256" alt="HiDream-O1-Image-Dev example" src="../assets/hidream-o1/dev_example.png" />
|
||||
|
||||
@@ -26,12 +26,12 @@ Fortunately, `AMD` provides complete help documentation, you can use the help do
|
||||
|
||||
Then we must set `ROCM` as environment variables before running cmake.
|
||||
|
||||
Usually if you install according to the official tutorial and do not modify the ROCM path, then there is a high probability that it is here `C:\Program Files\AMD\ROCm\5.5\bin`
|
||||
Usually if you install according to the official tutorial and do not modify the ROCM path, then there is a high probability that it is here `C:\Program Files\AMD\ROCm\7.1.1\bin`
|
||||
|
||||
This is what I use to set the clang:
|
||||
```Commandline
|
||||
set CC=C:\Program Files\AMD\ROCm\5.5\bin\clang.exe
|
||||
set CXX=C:\Program Files\AMD\ROCm\5.5\bin\clang++.exe
|
||||
set CC=C:\Program Files\AMD\ROCm\7.1.1\bin\clang.exe
|
||||
set CXX=C:\Program Files\AMD\ROCm\7.1.1\bin\clang++.exe
|
||||
```
|
||||
|
||||
## Ninja
|
||||
@@ -46,7 +46,7 @@ set ninja=C:\Program Files\ninja\ninja.exe
|
||||
## Building stable-diffusion.cpp
|
||||
|
||||
The thing different from the regular CPU build is `-DSD_HIPBLAS=ON` ,
|
||||
`-G "Ninja"`, `-DCMAKE_C_COMPILER=clang`, `-DCMAKE_CXX_COMPILER=clang++`, `-DAMDGPU_TARGETS=gfx1100`
|
||||
`-G "Ninja"`, `-DCMAKE_C_COMPILER=clang`, `-DCMAKE_CXX_COMPILER=clang++`, `-DAMDGPU_TARGETS=gfx1150;gfx1151;gfx1200;gfx1201;gfx1100;gfx1101;gfx1102;gfx1030;gfx1031;gfx1032`
|
||||
|
||||
>**Notice**: check the `clang` and `clang++` information:
|
||||
```Commandline
|
||||
@@ -59,26 +59,29 @@ If you see like this, we can continue:
|
||||
clang version 17.0.0 (git@github.amd.com:Compute-Mirrors/llvm-project e3201662d21c48894f2156d302276eb1cf47c7be)
|
||||
Target: x86_64-pc-windows-msvc
|
||||
Thread model: posix
|
||||
InstalledDir: C:\Program Files\AMD\ROCm\5.5\bin
|
||||
InstalledDir: C:\Program Files\AMD\ROCm\7.1.1\bin
|
||||
```
|
||||
|
||||
```
|
||||
clang version 17.0.0 (git@github.amd.com:Compute-Mirrors/llvm-project e3201662d21c48894f2156d302276eb1cf47c7be)
|
||||
Target: x86_64-pc-windows-msvc
|
||||
Thread model: posix
|
||||
InstalledDir: C:\Program Files\AMD\ROCm\5.5\bin
|
||||
InstalledDir: C:\Program Files\AMD\ROCm\7.1.1\bin
|
||||
```
|
||||
|
||||
>**Notice** that the `gfx1100` is the GPU architecture of my GPU, you can change it to your GPU architecture. Click here to see your architecture [LLVM Target](https://rocm.docs.amd.com/en/latest/release/windows_support.html#windows-supported-gpus)
|
||||
>**Notice** that the GPU targets are now compatible with multiple GPU architectures (ROCm 7.1.1 targets). You can change them to match your GPU architecture. Click here to see your architecture [LLVM Target](https://rocm.docs.amd.com/en/latest/release/windows_support.html#windows-supported-gpus)
|
||||
|
||||
My GPU is AMD Radeon™ RX 7900 XTX Graphics, so I set it to `gfx1100`.
|
||||
Examples:
|
||||
- AMD Radeon™ RX 7900 XTX Graphics: `gfx1100`
|
||||
- AMD Radeon™ RX 7900 XT Graphics: `gfx1101`
|
||||
- AMD Radeon™ RX 7900 GRE Graphics: `gfx1102`
|
||||
|
||||
option:
|
||||
|
||||
```commandline
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G "Ninja" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DSD_HIPBLAS=ON -DCMAKE_BUILD_TYPE=Release -DAMDGPU_TARGETS=gfx1100
|
||||
cmake .. -G "Ninja" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DSD_HIPBLAS=ON -DCMAKE_BUILD_TYPE=Release -DAMDGPU_TARGETS="gfx1150;gfx1151;gfx1200;gfx1201;gfx1100;gfx1101;gfx1102;gfx1030;gfx1031;gfx1032"
|
||||
cmake --build . --config Release
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# How to Use
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download Ideogram4
|
||||
- safetensors: https://huggingface.co/ideogram-ai/ideogram-4-fp8/tree/main/transformer
|
||||
- Download Ideogram4 uncond
|
||||
- safetensors: https://huggingface.co/ideogram-ai/ideogram-4-fp8/tree/main/unconditional_transformer
|
||||
- Download vae
|
||||
- safetensors: https://huggingface.co/black-forest-labs/FLUX.2-dev/tree/main
|
||||
- Download Qwen3-VL-8B-Instruct
|
||||
- gguf: https://huggingface.co/unsloth/Qwen3-VL-8B-Instruct-GGUF/tree/main
|
||||
|
||||
## Convert weights
|
||||
|
||||
fp8 scale -> bf16
|
||||
|
||||
```
|
||||
python .\convert_fp8_scale_to_bf16.py --input .\ideogram4_fp8.safetensors --output ideogram4_bf16.safetensors
|
||||
python .\convert_fp8_scale_to_bf16.py --input .\ideogram4_uncond_fp8.safetensors --output ideogram4_uncond_bf16.safetensors
|
||||
```
|
||||
|
||||
bf16 -> q8
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M convert -m ideogram4_bf16.safetensors -o ideogram4-Q8_0.gguf --tensor-type-rules "^layers.*adaln_modulation.*weight=q8_0,layers.*attention.o.*weight=q8_0,layers.*attention.qkv.*weight=q8_0,layers.*feed_forward.*weight=q8_0" -v
|
||||
|
||||
.\bin\Release\sd-cli.exe -M convert -m ideogram4_uncond_bf16.safetensors -o ideogram4_uncond-Q8_0.gguf --tensor-type-rules "^layers.*adaln_modulation.*weight=q8_0,layers.*attention.o.*weight=q8_0,layers.*attention.qkv.*weight=q8_0,layers.*feed_forward.*weight=q8_0" -v
|
||||
```
|
||||
|
||||
If you want lower VRAM usage, you can change the quantization from q8_0 to a lower-level quantization, such as q4_0.
|
||||
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
<img alt="ideogram4 image example" src="../assets/ideogram4/example.png" />
|
||||
@@ -0,0 +1,59 @@
|
||||
# Importance Matrix (imatrix) Quantization
|
||||
|
||||
## What is an Importance Matrix?
|
||||
|
||||
Quantization reduces the precision of a model's weights, decreasing its size and computational requirements. However, this can lead to a loss of quality. An importance matrix helps mitigate this by identifying which weights are *most* important for the model's performance. During quantization, these important weights are preserved with higher precision, while less important weights are quantized more aggressively. This allows for better overall quality at a given quantization level.
|
||||
|
||||
This originates from work done with language models in [llama.cpp](https://github.com/ggml-org/llama.cpp/blob/master/tools/imatrix/README.md).
|
||||
|
||||
## Usage
|
||||
|
||||
The imatrix feature involves two main steps: *training* the matrix and *using* it during quantization.
|
||||
|
||||
### Training the Importance Matrix
|
||||
|
||||
To generate an imatrix, run stable-diffusion.cpp with the `--imat-out` flag, specifying the output filename. This process runs alongside normal image generation.
|
||||
|
||||
```bash
|
||||
sd.exe [same exact parameters as normal generation] --imat-out imatrix.dat
|
||||
```
|
||||
|
||||
* **`[same exact parameters as normal generation]`**: Use the same command-line arguments you would normally use for image generation (e.g., prompt, dimensions, sampling method, etc.).
|
||||
* **`--imat-out imatrix.dat`**: Specifies the output file for the generated imatrix.
|
||||
|
||||
You can generate multiple images at once using the `-b` flag to speed up the training process.
|
||||
|
||||
### Continuing Training an Existing Matrix
|
||||
|
||||
If you want to refine an existing imatrix, use the `--imat-in` flag *in addition* to `--imat-out`. This will load the existing matrix and continue training it.
|
||||
|
||||
```bash
|
||||
sd.exe [same exact parameters as normal generation] --imat-out imatrix.dat --imat-in imatrix.dat
|
||||
```
|
||||
With that, you can train and refine the imatrix while generating images like you'd normally do.
|
||||
|
||||
### Using Multiple Matrices
|
||||
|
||||
You can load and merge multiple imatrices together:
|
||||
|
||||
```bash
|
||||
sd.exe [same exact parameters as normal generation] --imat-out imatrix.dat --imat-in imatrix.dat --imat-in imatrix2.dat
|
||||
```
|
||||
|
||||
### Quantizing with an Importance Matrix
|
||||
|
||||
To quantize a model using a trained imatrix, use the `-M convert` option (or equivalent quantization command) and the `--imat-in` flag, specifying the imatrix file.
|
||||
|
||||
```bash
|
||||
sd.exe -M convert [same exact parameters as normal quantization] --imat-in imatrix.dat
|
||||
```
|
||||
|
||||
* **`[same exact parameters as normal quantization]`**: Use the same command-line arguments you would normally use for quantization (e.g., target quantization method, input/output filenames).
|
||||
* **`--imat-in imatrix.dat`**: Specifies the imatrix file to use during quantization. You can specify multiple `--imat-in` flags to combine multiple matrices.
|
||||
|
||||
## Important Considerations
|
||||
|
||||
* The quality of the imatrix depends on the prompts and settings used during training. Use prompts and settings representative of the types of images you intend to generate for the best results.
|
||||
* Experiment with different training parameters (e.g., number of images, prompt variations) to optimize the imatrix for your specific use case.
|
||||
* The performance impact of training an imatrix during image generation or using an imatrix for quantization is negligible.
|
||||
* Using already quantized models to train the imatrix seems to be working fine.
|
||||
@@ -16,7 +16,7 @@ You can run Kontext using stable-diffusion.cpp with a GPU that has 6GB or even 4
|
||||
You can download the preconverted gguf weights from [FLUX.1-Kontext-dev-GGUF](https://huggingface.co/QuantStack/FLUX.1-Kontext-dev-GGUF), this way you don't have to do the conversion yourself.
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M convert -m ..\..\ComfyUI\models\unet\flux1-kontext-dev.safetensors -o ..\models\flux1-kontext-dev-q8_0.gguf -v --type q8_0
|
||||
.\bin\Release\sd-cli.exe -M convert -m ..\models\unet\flux1-kontext-dev.safetensors -o ..\models\flux1-kontext-dev-q8_0.gguf -v --type q8_0
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# How to Use
|
||||
|
||||
Krea2 uses a Krea2 diffusion transformer, the Wan2.1 VAE, and Qwen3-VL 4B as the LLM text encoder.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download Krea2 Raw
|
||||
- safetensors: https://huggingface.co/krea/Krea-2-Raw/tree/main
|
||||
- gguf: https://huggingface.co/realrebelai/KREA-2_GGUFs/tree/main/BASE
|
||||
- Download Krea2 Turbo
|
||||
- safetensors: https://huggingface.co/krea/Krea-2-Turbo/tree/main
|
||||
- gguf: https://huggingface.co/realrebelai/KREA-2_GGUFs/tree/main/TURBO
|
||||
- Download vae
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/blob/main/split_files/vae/wan_2.1_vae.safetensors
|
||||
- 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
|
||||
|
||||
## Examples
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
<img width="256" alt="Krea2 Raw example" src="../assets/krea2/example.png" />
|
||||
@@ -0,0 +1,32 @@
|
||||
# How to Use
|
||||
|
||||
Lens uses a Lens diffusion transformer, the FLUX.2 VAE, and GPT-OSS-20B as the LLM text encoder.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download Lens
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Lens/tree/main/diffusion_models
|
||||
- Download Lens Turbo
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Lens/tree/main/diffusion_models
|
||||
- Download vae
|
||||
- safetensors: https://huggingface.co/black-forest-labs/FLUX.2-dev/tree/main
|
||||
- Download GPT-OSS-20B
|
||||
- gguf: https://huggingface.co/unsloth/gpt-oss-20b-GGUF/tree/main
|
||||
|
||||
## Examples
|
||||
|
||||
### 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" --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
|
||||
```
|
||||
|
||||
<img width="256" alt="Lens example" src="../assets/lens/example.png" />
|
||||
|
||||
### 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" --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
|
||||
```
|
||||
|
||||
<img width="256" alt="Lens Turbo example" src="../assets/lens/turbo_example.png" />
|
||||
@@ -0,0 +1,30 @@
|
||||
# How to Use
|
||||
|
||||
LongCat-Image uses a LongCat diffusion transformer, the FLUX VAE, and Qwen2.5-VL as the LLM text encoder.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download LongCat Image
|
||||
- safetensors: https://huggingface.co/Comfy-Org/LongCat-Image/tree/main/split_files/diffusion_models
|
||||
- gguf: https://huggingface.co/vantagewithai/LongCat-Image-GGUF/tree/main/comfy
|
||||
- Download LongCat Image Edit
|
||||
- LongCat Image Edit Turbo: https://huggingface.co/meituan-longcat/LongCat-Image-Edit-Turbo
|
||||
- gguf: https://huggingface.co/vantagewithai/LongCat-Image-Edit-GGUF/tree/main
|
||||
- Download vae
|
||||
- safetensors: https://huggingface.co/black-forest-labs/FLUX.1-dev/blob/main/ae.safetensors
|
||||
- Download qwen_2.5_vl 7b
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/tree/main/split_files/text_encoders
|
||||
- gguf: https://huggingface.co/mradermacher/Qwen2.5-VL-7B-Instruct-GGUF/tree/main
|
||||
- For image editing with GGUF text encoders, also download the matching mmproj file and pass it with `--llm_vision`.
|
||||
|
||||
## Run
|
||||
|
||||
LongCat uses quoted text for character-level text rendering. Put target text inside single quotes, double quotes, or Chinese quotes.
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
<img alt="longcat example" src="../assets/longcat/example.png" />
|
||||
@@ -0,0 +1,77 @@
|
||||
# How to Use
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download LTX-2.3
|
||||
- safetensors: https://huggingface.co/Kijai/LTX2.3_comfy/tree/main/diffusion_models
|
||||
- gguf: https://huggingface.co/unsloth/LTX-2.3-GGUF/tree/main
|
||||
- Download gemma-3-12b-it
|
||||
- gguf: https://huggingface.co/unsloth/gemma-3-12b-it-GGUF/tree/main
|
||||
- Download embeddings connectors
|
||||
- safetensors: https://huggingface.co/unsloth/LTX-2.3-GGUF/tree/main/text_encoders
|
||||
- Download vae
|
||||
- safetensors: https://huggingface.co/unsloth/LTX-2.3-GGUF/tree/main/vae
|
||||
- Download audio vae
|
||||
- safetensors: https://huggingface.co/unsloth/LTX-2.3-GGUF/tree/main/vae
|
||||
- Download LTX spatial latent upscaler
|
||||
- safetensors: https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-spatial-upscaler-x2-1.1.safetensors
|
||||
|
||||
## Examples
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
<video
|
||||
src="../assets/ltx2/t2v.webm"
|
||||
controls
|
||||
muted
|
||||
style="max-width: 100%; height: auto;"></video>
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
<video
|
||||
src="../assets/ltx2/i2v.webm"
|
||||
controls
|
||||
muted
|
||||
style="max-width: 100%; height: auto;"></video>
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
<video
|
||||
src="../assets/ltx2/flf2v.webm"
|
||||
controls
|
||||
muted
|
||||
style="max-width: 100%; height: auto;"></video>
|
||||
|
||||
### LTX-2.3 spatial latent upscale
|
||||
|
||||
LTX spatial latent upscale runs a model-backed x2 latent upsampler between the low-resolution video pass and the high-resolution refine pass. `-W` and `-H` are the pre-upscale generation size; the spatial upsampler produces x2 latent dimensions.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
--hires-sigmas "0.85,0.725,0.421875,0.0"
|
||||
```
|
||||
|
||||
<video
|
||||
src="../assets/ltx2/hires_i2v.webm"
|
||||
controls
|
||||
muted
|
||||
style="max-width: 100%; height: auto;"></video>
|
||||
@@ -0,0 +1,48 @@
|
||||
# How to Use
|
||||
|
||||
MiniT2I uses a MiniT2I diffusion transformer and `google/flan-t5-large` as the text encoder.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download MiniT2I diffusion model
|
||||
- safetensors: https://huggingface.co/MiniT2I/MiniT2I/tree/main/minit2i-b-16/transformer (`diffusion_pytorch_model.safetensors`)
|
||||
- Download flan-t5-large text encoder
|
||||
- safetensors: https://huggingface.co/google/flan-t5-large/tree/main (`model.safetensors`)
|
||||
|
||||
## Examples
|
||||
|
||||
### Mac Metal
|
||||
|
||||
```
|
||||
./bin/sd-cli \
|
||||
--backend metal \
|
||||
--diffusion-model ../models/minit2i/diffusion_pytorch_model.safetensors \
|
||||
--t5xxl ../models/flan-t5-large/model.safetensors \
|
||||
--prompt "a cat" \
|
||||
--steps 100 \
|
||||
--cfg-scale 6 \
|
||||
--width 512 \
|
||||
--height 512 \
|
||||
--seed 42 \
|
||||
--sampling-method euler \
|
||||
--rng cpu \
|
||||
--output minit2i_metal.png \
|
||||
--threads 8
|
||||
```
|
||||
|
||||
### CUDA with diffusion flash attention
|
||||
|
||||
```
|
||||
./bin/sd-cli \
|
||||
--diffusion-model ../models/minit2i/diffusion_pytorch_model.safetensors \
|
||||
--t5xxl ../models/flan-t5-large/model.safetensors \
|
||||
--prompt "a cat" \
|
||||
--steps 100 \
|
||||
--cfg-scale 6 \
|
||||
--width 512 \
|
||||
--height 512 \
|
||||
--seed 42 \
|
||||
--sampling-method euler \
|
||||
--diffusion-fa \
|
||||
--output minit2i_cuda.png
|
||||
```
|
||||
@@ -0,0 +1,118 @@
|
||||
# Model Configuration Conventions
|
||||
|
||||
This document describes the conventions for model configuration structs and
|
||||
weight-based configuration detection.
|
||||
|
||||
## Config Types
|
||||
|
||||
Model configuration should live in a model-specific `*Config` struct.
|
||||
|
||||
Examples:
|
||||
|
||||
- `ZImageConfig`
|
||||
- `UNetConfig`
|
||||
- `MMDiTConfig`
|
||||
- `LLMConfig`
|
||||
|
||||
Preserve established acronym casing in type names, such as `UNet`, `MMDiT`,
|
||||
`LLM`, `VAE`, and `T5`.
|
||||
|
||||
Place the config struct near the top of the model header, before the main model
|
||||
blocks and runner types that consume it.
|
||||
|
||||
## Config Variables
|
||||
|
||||
Variables and members that hold a config should be named `config`.
|
||||
|
||||
Examples:
|
||||
|
||||
```cpp
|
||||
UNetConfig config;
|
||||
UnetModelBlock unet;
|
||||
|
||||
MMDiTRunner(...)
|
||||
: DiffusionModelRunner(backend, params_backend, prefix),
|
||||
config(MMDiTConfig::detect_from_weights(tensor_storage_map, prefix)),
|
||||
mmdit(config) {
|
||||
}
|
||||
```
|
||||
|
||||
Avoid alternate names such as `params`, `params_cfg`, `model_params`, or
|
||||
model-specific aliases unless an existing public API requires them.
|
||||
|
||||
## Weight Detection
|
||||
|
||||
If a model can derive configuration from loaded weight metadata, expose that
|
||||
logic as a static method on the config type:
|
||||
|
||||
```cpp
|
||||
static XxxConfig detect_from_weights(const String2TensorStorage& tensor_storage_map,
|
||||
const std::string& prefix);
|
||||
```
|
||||
|
||||
Additional selector arguments are allowed when required by an existing model
|
||||
family, for example `SDVersion version` or an architecture enum:
|
||||
|
||||
```cpp
|
||||
static UNetConfig detect_from_weights(const String2TensorStorage& tensor_storage_map,
|
||||
const std::string& prefix,
|
||||
SDVersion version = VERSION_SD1);
|
||||
```
|
||||
|
||||
Use `TensorStorage` metadata, especially `n_dims` and `ne`, to infer shapes.
|
||||
Do not load or parse tensor data for config detection.
|
||||
|
||||
Detection should respect `prefix`. For nested weights, construct full names from
|
||||
`prefix + "." + suffix` or filter entries with `starts_with(name, prefix)`.
|
||||
|
||||
Do not add persistent config fields such as `inferred_from_weights` only to
|
||||
record whether detection happened. If the function needs to decide whether to
|
||||
print a debug line, keep that as local control flow inside `detect_from_weights`.
|
||||
|
||||
## Logging
|
||||
|
||||
When config values are inferred from weights, print one `LOG_DEBUG` line at the
|
||||
end of `detect_from_weights`.
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
LOG_DEBUG("llm: num_layers = %" PRId64 ", vocab_size = %" PRId64 ", hidden_size = %" PRId64 ", intermediate_size = %" PRId64,
|
||||
config.num_layers,
|
||||
config.vocab_size,
|
||||
config.hidden_size,
|
||||
config.intermediate_size);
|
||||
```
|
||||
|
||||
Only print the config detection log when the function actually inferred values
|
||||
from weights. Do not duplicate the same config summary in runner constructors or
|
||||
model loading code.
|
||||
|
||||
Use the correct format specifiers for field types, such as `%" PRId64 "` for
|
||||
`int64_t` and `%d` for `int`.
|
||||
|
||||
## Runner And Model Responsibilities
|
||||
|
||||
Runners should detect the config once and pass it into the model block:
|
||||
|
||||
```cpp
|
||||
struct XxxRunner : public DiffusionModelRunner {
|
||||
XxxConfig config;
|
||||
XxxModel model;
|
||||
|
||||
XxxRunner(..., const String2TensorStorage& tensor_storage_map, const std::string prefix)
|
||||
: DiffusionModelRunner(backend, params_backend, prefix),
|
||||
config(XxxConfig::detect_from_weights(tensor_storage_map, prefix)),
|
||||
model(config) {
|
||||
model.init(params_ctx, tensor_storage_map, prefix);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Model blocks should consume `config` directly instead of re-scanning weights in
|
||||
their constructors. Keep config-derived behavior centralized in the config
|
||||
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.
|
||||
@@ -13,7 +13,7 @@
|
||||
## Examples
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ovis_image-Q4_0.gguf --vae ..\..\ComfyUI\models\vae\ae.sft --llm ..\..\ComfyUI\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 --diffusion-fa
|
||||
```
|
||||
|
||||
<img alt="ovis image example" src="../assets/ovis_image/example.png" />
|
||||
@@ -21,6 +21,38 @@ and the compute buffer shrink in the debug log:
|
||||
|
||||
Using `--offload-to-cpu` allows you to offload weights to the CPU, saving VRAM without reducing generation speed.
|
||||
|
||||
## Use params backend to reduce VRAM or RAM usage.
|
||||
|
||||
`--params-backend` controls where model parameters are kept. If it is not set, parameters use the same backend as `--backend`, so a GPU runtime backend also keeps parameters in VRAM.
|
||||
|
||||
Use CPU params to reduce VRAM usage:
|
||||
|
||||
```shell
|
||||
--backend cuda0 --params-backend cpu
|
||||
```
|
||||
|
||||
This keeps model weights in system RAM and moves them to the runtime backend when needed. In the example CLI/server, `--offload-to-cpu` is a compatibility shortcut that prepends `*=cpu` to `--params-backend` before creating the context, so explicit module assignments can still override it:
|
||||
|
||||
```shell
|
||||
--offload-to-cpu --params-backend te=disk
|
||||
```
|
||||
|
||||
Use disk params to reduce both VRAM and RAM usage:
|
||||
|
||||
```shell
|
||||
--backend cuda0 --params-backend disk
|
||||
```
|
||||
|
||||
This reloads parameters from the model file on demand and releases them after use. It has the lowest memory residency, but can be slower because weights must be read again. `disk` is never selected implicitly; set it explicitly when RAM usage matters more than reload cost.
|
||||
|
||||
Per-module assignments can target only the largest modules:
|
||||
|
||||
```shell
|
||||
--backend cuda0 --params-backend diffusion=disk,te=cpu,vae=cpu
|
||||
```
|
||||
|
||||
See [backend selection](./backend.md) for full syntax.
|
||||
|
||||
## Use quantization to reduce memory usage.
|
||||
|
||||
[quantization](./quantization_and_gguf.md)
|
||||
[quantization](./quantization_and_gguf.md)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# How to Use
|
||||
|
||||
PiD is NVIDIA's Pixel Diffusion Decoder. It replaces the usual VAE decode or decode-then-upscale path with a pixel-space diffusion decoder conditioned on a
|
||||
source latent and text prompt.
|
||||
|
||||
In stable-diffusion.cpp, PiD currently runs as an image edit pipeline: provide a reference image with `-r`/`--ref-image`, encode that image with a matching VAE, then let the PiD diffusion model decode/upscale directly to RGB.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download PiD
|
||||
- safetensors: https://huggingface.co/Comfy-Org/PixelDiT/tree/main/diffusion_models
|
||||
- Download Gemma 2 2B
|
||||
- safetensors: https://huggingface.co/Comfy-Org/PixelDiT/tree/main/text_encoders
|
||||
- Download the VAE that matches the PiD checkpoint backbone
|
||||
- safetensors: https://huggingface.co/nvidia/PiD/tree/main/checkpoints
|
||||
- Flux / Z-Image PiD: use the Flux VAE and pass `--vae-format flux`
|
||||
- SD3 PiD: use the SD3 VAE and pass `--vae-format sd3`
|
||||
- Flux.2 PiD: use the Flux.2 VAE and pass `--vae-format flux2`
|
||||
|
||||
The official PiD model card should be checked before use. At the time of the initial PiD release, the official weights are under the NSCLv1 non-commercial license.
|
||||
|
||||
## 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" --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
|
||||
```
|
||||
|
||||
Before:
|
||||
|
||||
<img width="256" alt="ERNIE-Image Turbo example" src="../assets/ernie_image/turbo_example.png" />
|
||||
|
||||
After:
|
||||
<img width="1024" alt="PiD example" src="../assets/pid/example.png" />
|
||||
|
||||
## Notes
|
||||
|
||||
- `-r`/`--ref-image` is required. PiD uses the first reference image as the source latent condition.
|
||||
- `--vae-format` should match the VAE latent layout used by the PiD checkpoint. This is important when using standalone VAE files because the PiD diffusion
|
||||
checkpoint alone does not identify the VAE format.
|
||||
@@ -0,0 +1,196 @@
|
||||
# PuLID-Flux face-identity preservation
|
||||
|
||||
stable-diffusion.cpp supports the [PuLID-Flux](https://github.com/ToTheBeginning/PuLID)
|
||||
identity-injection technique on top of Flux.1 (schnell or dev) models.
|
||||
Given a single source portrait, PuLID-Flux produces new generations that
|
||||
preserve the source person's face across arbitrary scenes, poses, and
|
||||
prompts.
|
||||
|
||||
Unlike PhotoMaker (which extracts the identity inside the inference
|
||||
process from a directory of images), PuLID-Flux's identity extractor is
|
||||
a heavy stack (insightface ArcFace + EVA-CLIP-L + IDFormer encoder) that
|
||||
is impractical to port to C++/ggml. To keep this implementation small and
|
||||
cross-vendor, **stable-diffusion.cpp consumes a precomputed identity
|
||||
embedding** produced by an external Python tool that runs once per source
|
||||
portrait. Everything downstream of that one-shot extraction is C++ and
|
||||
runs on any backend (Vulkan, CUDA, Metal, ROCm, CPU).
|
||||
|
||||
## Architecture summary
|
||||
|
||||
The PuLID-Flux contribution to the Flux denoise loop is a stack of 20
|
||||
small cross-attention modules (`PerceiverAttentionCA`) inserted between
|
||||
the Flux transformer blocks:
|
||||
|
||||
- After every 2nd of the 19 double-stream blocks (10 hook points)
|
||||
- After every 4th of the 38 single-stream blocks (10 hook points)
|
||||
|
||||
Each cross-attention layer takes the current image tokens as query, the
|
||||
32-token / 2048-dim identity embedding as key+value, and adds its output
|
||||
(scaled by `id_weight`, typically 1.0) back to the image tokens.
|
||||
|
||||
## Required weights
|
||||
|
||||
Three files in addition to the standard Flux weight set:
|
||||
|
||||
1. **Flux base** (transformer + VAE + clip_l + t5xxl) -- exactly as
|
||||
[docs/flux.md](flux.md) describes.
|
||||
2. **PuLID weights** -- download from
|
||||
[guozinan/PuLID](https://huggingface.co/guozinan/PuLID):
|
||||
- `pulid_flux_v0.9.0.safetensors` or `pulid_flux_v0.9.1.safetensors`
|
||||
(recommended; this implementation is verified against v0.9.1)
|
||||
- **v1.1 (`pulid_v1.1.safetensors`) is NOT yet supported** -- it uses
|
||||
renamed keys (`id_adapter_attn_layers.*` instead of `pulid_ca.*`)
|
||||
and possibly different module structure. Future PR.
|
||||
3. **Identity embedding (.pulidembd)** -- produced by the precompute
|
||||
tool below.
|
||||
|
||||
## Precompute the identity embedding
|
||||
|
||||
The precompute tool runs the PyTorch identity-extraction stack on a
|
||||
single portrait image and writes the resulting `(32, 2048)` embedding
|
||||
to a `.pulidembd` binary file (about 131 KB). Run it once per source
|
||||
person; the same file is reused for any number of generations.
|
||||
|
||||
A reference Python script is provided alongside this docs file at
|
||||
[`scripts/pulid_extract_id.py`](../scripts/pulid_extract_id.py). It
|
||||
requires:
|
||||
- A working CUDA / CPU PyTorch stack
|
||||
- `insightface`, `facexlib`, `eva-clip`, `torchvision`, `opencv-python`,
|
||||
`huggingface_hub`, `gguf`
|
||||
- The PuLID weights file (same one stable-diffusion.cpp will load below)
|
||||
- The ToTheBeginning/PuLID repo's `pulid/` package (including
|
||||
`pulid/pipeline_flux.py`) and `eva_clip/` package on `PYTHONPATH`; `flux/`
|
||||
is not needed for embedding extraction
|
||||
|
||||
Run it as:
|
||||
|
||||
```
|
||||
python pulid_extract_id.py \
|
||||
--portrait /path/to/source-photo.jpg \
|
||||
--pulid-weights /path/to/pulid_flux_v0.9.1.safetensors \
|
||||
--out /path/to/source.pulidembd
|
||||
```
|
||||
|
||||
## Format (gguf)
|
||||
|
||||
The embedding is a standard **gguf** container holding a single tensor:
|
||||
|
||||
```
|
||||
tensor name : "pulid_id"
|
||||
shape : [token_dim, num_tokens] (ggml order; typically [2048, 32])
|
||||
type : F16 (also accepts F32 / BF16)
|
||||
metadata : general.architecture = "pulid", pulid.version = 1
|
||||
```
|
||||
|
||||
stable-diffusion.cpp loads it with the normal gguf reader
|
||||
(`gguf_init_from_file`) and converts to fp32 at load time -- no bespoke
|
||||
parser. Total file size for the typical (32, 2048, fp16) case is ~131 KB.
|
||||
|
||||
## Command-line usage
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe \
|
||||
--diffusion-model models\flux1-schnell-Q4_K_S.gguf \
|
||||
--vae models\ae.safetensors \
|
||||
--clip_l models\clip_l.safetensors \
|
||||
--t5xxl models\t5xxl_fp16.safetensors \
|
||||
--pulid-weights models\pulid_flux_v0.9.1.safetensors \
|
||||
--pulid-id-embedding source.pulidembd \
|
||||
--pulid-id-weight 1.0 \
|
||||
-p "candid photograph of a young woman on a beach at sunset" \
|
||||
--cfg-scale 1.0 --sampling-method euler --steps 4 -W 512 -H 512 \
|
||||
--seed 42 --clip-on-cpu \
|
||||
-o out.png
|
||||
```
|
||||
|
||||
For Flux Dev (instead of Schnell), add `--guidance 3.5` and `--steps 20`.
|
||||
|
||||
## Flags
|
||||
|
||||
| Flag | Purpose |
|
||||
|----------------------------|-------------------------------------------------------------------|
|
||||
| `--pulid-weights <path>` | Path to `pulid_flux_v0.9.x.safetensors`. Loaded with the model. |
|
||||
| `--pulid-id-embedding <p>` | Path to a `.pulidembd` binary produced by the precompute tool. |
|
||||
| `--pulid-id-weight <f>` | Identity-injection strength. Typical 0.7-1.2; default 1.0. |
|
||||
|
||||
All three flags must be set together to activate PuLID. Setting only
|
||||
`--pulid-weights` (no embedding) loads the weights but disables injection
|
||||
at runtime. Setting `--pulid-id-weight 0` zeros out the contribution
|
||||
(useful for falsification testing: outputs should be byte-identical to
|
||||
a no-PuLID run with the same seed).
|
||||
|
||||
## Memory budget
|
||||
|
||||
At 512x512, 4 steps (Schnell), the 20 cross-attention layers add roughly
|
||||
10% to denoise time and almost nothing to peak VRAM. Tested on a 12 GB
|
||||
consumer card alongside Flux Schnell Q4 GGUF + CPU-offloaded clip_l and
|
||||
t5xxl + GPU-resident VAE.
|
||||
|
||||
At 1024x1024 with Flux Dev Q4 + 20 steps + PuLID, the VAE decode compute
|
||||
buffer doesn't fit on a 12 GB card even with `--vae-on-cpu`. Workaround:
|
||||
explicitly route VAE to the CPU backend instead of the offload flag:
|
||||
|
||||
```
|
||||
--backend "diffusion=vulkan0,vae=cpu"
|
||||
```
|
||||
|
||||
The `--vae-on-cpu` flag offloads VAE weights but leaves the compute graph
|
||||
on the default backend; this is existing stable-diffusion.cpp behavior,
|
||||
not a PuLID-specific issue. Documented here because anyone running PuLID
|
||||
at 1024 will hit it.
|
||||
|
||||
## Backend selection
|
||||
|
||||
The standard `--backend` flag works as documented. Common patterns:
|
||||
|
||||
```
|
||||
# AMD Vulkan
|
||||
--backend "diffusion=vulkan0,vae=cpu"
|
||||
|
||||
# NVIDIA Vulkan
|
||||
--backend "diffusion=vulkan1,vae=cpu"
|
||||
|
||||
# CUDA
|
||||
--backend "diffusion=cuda0,vae=cpu"
|
||||
```
|
||||
|
||||
The PuLID cross-attention layers run on the same backend as the main
|
||||
diffusion model. They have not yet been independently profiled on every
|
||||
backend; only Vulkan and CPU have been tested by the original contributor.
|
||||
|
||||
## Verification
|
||||
|
||||
A three-way SHA-256 check is the recommended sanity test when bringing up
|
||||
a new combination of model + backend + hardware:
|
||||
|
||||
| Run | Expected hash relation |
|
||||
|----------------------------------------------|------------------------------------|
|
||||
| A: no `--pulid-*` flags | baseline |
|
||||
| B: PuLID flags, `--pulid-id-weight 0.0` | **byte-identical to A** |
|
||||
| C: PuLID flags, `--pulid-id-weight 1.0` | **different from A,B**, preserves source identity |
|
||||
|
||||
If A and C differ but A and B differ too, the injection is allocating
|
||||
or computing something even at zero weight -- likely a bug.
|
||||
|
||||
## Limitations / not yet supported
|
||||
|
||||
- **`--skip-layers` (skip-layer-guidance / SLG) combined with PuLID** is not
|
||||
supported. The `pulid_ca` index advances per non-skipped block, so a
|
||||
skipped block silently misaligns the cross-attention weight assignment
|
||||
vs. the trained intervals. The reference PyTorch implementation does
|
||||
not have SLG either, so there is no well-defined behavior to emulate.
|
||||
Use either feature alone.
|
||||
- **PuLID v1.1 weights** (`pulid_v1.1.safetensors`, renamed key layout).
|
||||
- **Multiple ID images.** The reference PyTorch implementation can fuse
|
||||
several portraits into one embedding for stronger identity. This
|
||||
implementation accepts a single embedding produced from one or more
|
||||
images by the external precompute tool.
|
||||
- **Negative-prompt branch of CFG.** PuLID only injects on the positive
|
||||
conditioning path in the published reference, and the implementation
|
||||
here follows that. Flux's distilled guidance doesn't run a separate
|
||||
uncond branch in normal use, so this matters only for `--true-cfg`
|
||||
workflows that aren't standard for Flux.
|
||||
- **Backends other than Vulkan and CPU** are untested by the original
|
||||
contributor. The implementation is pure-ggml and should work on CUDA,
|
||||
ROCm, and Metal, but verification by users on those backends is
|
||||
welcomed.
|
||||
@@ -14,7 +14,7 @@
|
||||
## Examples
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\..\ComfyUI\models\diffusion_models\qwen-image-Q8_0.gguf --vae ..\..\ComfyUI\models\vae\qwen_image_vae.safetensors --llm ..\..\ComfyUI\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 --diffusion-fa --flow-shift 3
|
||||
```
|
||||
|
||||
<img alt="qwen example" src="../assets/qwen/example.png" />
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
### Qwen Image Edit
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\..\ComfyUI\models\diffusion_models\Qwen_Image_Edit-Q8_0.gguf --vae ..\..\ComfyUI\models\vae\qwen_image_vae.safetensors --llm ..\..\ComfyUI\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 --diffusion-fa --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 ..\..\ComfyUI\models\diffusion_models\Qwen-Image-Edit-2509-Q4_K_S.gguf --vae ..\..\ComfyUI\models\vae\qwen_image_vae.safetensors --llm ..\..\ComfyUI\models\text_encoders\Qwen2.5-VL-7B-Instruct-Q8_0.gguf --llm_vision ..\..\ComfyUI\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 --diffusion-fa --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, the `--qwen-image-zero-cond-t` flag must be enabled; otherwise, image editing quality will degrade significantly.
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\..\ComfyUI\models\diffusion_models\qwen-image-edit-2511-Q4_K_M.gguf --vae ..\..\ComfyUI\models\vae\qwen_image_vae.safetensors --llm ..\..\ComfyUI\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'" --qwen-image-zero-cond-t
|
||||
.\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'" --qwen-image-zero-cond-t
|
||||
```
|
||||
|
||||
<img alt="qwen_image_edit_2509" src="../assets/qwen/qwen_image_edit_2511.png" />
|
||||
@@ -0,0 +1,220 @@
|
||||
# Building and Using the RPC Server with `stable-diffusion.cpp`
|
||||
|
||||
This guide covers how to build a version of [the RPC server from `llama.cpp`](https://github.com/ggml-org/llama.cpp/blob/master/tools/rpc/README.md) that is compatible with your version of `stable-diffusion.cpp` to manage multi-backends setups. RPC allows you to offload specific model components to a remote server.
|
||||
|
||||
> **Note on Model Location:** The model files (e.g., `.safetensors` or `.gguf`) remain on the **Client** machine. The client parses the file and transmits the necessary tensor data and computational graphs to the server. The server does not need to store the model files locally.
|
||||
|
||||
## 1. Building `stable-diffusion.cpp` with RPC client
|
||||
|
||||
First, you should build the client application from source. It requires `SD_RPC=ON` to include the RPC backend to your client.
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. \
|
||||
-DSD_RPC=ON \
|
||||
# Add other build flags here (e.g., -DSD_VULKAN=ON)
|
||||
cmake --build . --config Release -j $(nproc)
|
||||
```
|
||||
|
||||
> **Note:** Ensure you add the other flags you would normally use (e.g., `-DSD_VULKAN=ON`, `-DSD_CUDA=ON`, `-DSD_HIPBLAS=ON`, or `-DGGML_METAL=ON`), for more information about building `stable-diffusion.cpp` from source, please refer to the [build.md](build.md) documentation.
|
||||
|
||||
## 2. Ensure `llama.cpp` is at the correct commit
|
||||
|
||||
`stable-diffusion.cpp`'s RPC client is designed to work with a specific version of `llama.cpp` (compatible with the `ggml` submodule) to ensure API compatibility. The commit hash for `llama.cpp` is stored in `ggml/scripts/sync-llama.last`.
|
||||
|
||||
> **Start from Root:** Perform these steps from the root of your `stable-diffusion.cpp` directory.
|
||||
|
||||
1. Read the target commit hash from the submodule tracker:
|
||||
|
||||
```bash
|
||||
# Linux / WSL / MacOS
|
||||
HASH=$(cat ggml/scripts/sync-llama.last)
|
||||
|
||||
# Windows (PowerShell)
|
||||
$HASH = Get-Content -Path "ggml\scripts\sync-llama.last"
|
||||
```
|
||||
|
||||
2. Clone `llama.cpp` at the target commit .
|
||||
```bash
|
||||
git clone https://github.com/ggml-org/llama.cpp.git
|
||||
cd llama.cpp
|
||||
git checkout $HASH
|
||||
```
|
||||
To save on download time and storage, you can use a shallow clone to download only the target commit:
|
||||
```bash
|
||||
mkdir -p llama.cpp
|
||||
cd llama.cpp
|
||||
git init
|
||||
git remote add origin https://github.com/ggml-org/llama.cpp.git
|
||||
git fetch --depth 1 origin $HASH
|
||||
git checkout FETCH_HEAD
|
||||
```
|
||||
|
||||
## 3. Build `llama.cpp` (RPC Server)
|
||||
|
||||
The RPC server acts as the worker. You must explicitly enable the **backend** (the hardware interface, such as CUDA for Nvidia, Metal for Apple Silicon, or Vulkan) when building, otherwise the server will default to using only the CPU.
|
||||
|
||||
To find the correct flags for your system, refer to the official documentation for the [`llama.cpp`](https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md) repository.
|
||||
|
||||
> **Crucial:** You must include the compiler flags required to satisfy the API compatibility with `stable-diffusion.cpp` (`-DGGML_MAX_NAME=128`). Without this flag, `GGML_MAX_NAME` will default to `64` for the server, and data transfers between the client and server will fail. Of course, `-DGGML_RPC` must also be enabled.
|
||||
>
|
||||
> I recommend disabling the `LLAMA_CURL` flag to avoid unnecessary dependencies, and disabling shared library builds to avoid potential conflicts.
|
||||
|
||||
> **Build Target:** We are specifically building the `rpc-server` target. This prevents the build system from compiling the entire `llama.cpp` suite (like `llama-server`), making the build significantly faster.
|
||||
|
||||
### Linux / WSL (Vulkan)
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DGGML_RPC=ON \
|
||||
-DGGML_VULKAN=ON \ # Ensure backend is enabled
|
||||
-DGGML_BUILD_SHARED_LIBS=OFF \
|
||||
-DLLAMA_CURL=OFF \
|
||||
-DCMAKE_C_FLAGS=-DGGML_MAX_NAME=128 \
|
||||
-DCMAKE_CXX_FLAGS=-DGGML_MAX_NAME=128
|
||||
cmake --build . --config Release --target rpc-server -j $(nproc)
|
||||
```
|
||||
|
||||
### macOS (Metal)
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DGGML_RPC=ON \
|
||||
-DGGML_METAL=ON \
|
||||
-DGGML_BUILD_SHARED_LIBS=OFF \
|
||||
-DLLAMA_CURL=OFF \
|
||||
-DCMAKE_C_FLAGS=-DGGML_MAX_NAME=128 \
|
||||
-DCMAKE_CXX_FLAGS=-DGGML_MAX_NAME=128
|
||||
cmake --build . --config Release --target rpc-server
|
||||
```
|
||||
|
||||
### Windows (Visual Studio 2022, Vulkan)
|
||||
|
||||
```powershell
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G "Visual Studio 17 2022" -A x64 `
|
||||
-DGGML_RPC=ON `
|
||||
-DGGML_VULKAN=ON `
|
||||
-DGGML_BUILD_SHARED_LIBS=OFF `
|
||||
-DLLAMA_CURL=OFF `
|
||||
-DCMAKE_C_FLAGS=-DGGML_MAX_NAME=128 `
|
||||
-DCMAKE_CXX_FLAGS=-DGGML_MAX_NAME=128
|
||||
cmake --build . --config Release --target rpc-server
|
||||
```
|
||||
|
||||
## 4. Usage
|
||||
|
||||
Once both applications are built, you can run the server and the client to manage your GPU allocation.
|
||||
|
||||
### Step A: Run the RPC Server
|
||||
|
||||
Start the server. It listens for connections on the default address (usually `localhost:50052`). If your server is on a different machine, ensure the server binds to the correct interface and your firewall allows the connection.
|
||||
|
||||
**On the Server :**
|
||||
If running on the same machine, you can use the default address:
|
||||
|
||||
```bash
|
||||
./rpc-server
|
||||
```
|
||||
|
||||
If you want to allow connections from other machines on the network:
|
||||
|
||||
```bash
|
||||
./rpc-server --host 0.0.0.0
|
||||
```
|
||||
|
||||
> **Security Warning:** The RPC server does not currently support authentication or encryption. **Only run the server on trusted local networks**. Never expose the RPC server directly to the open internet.
|
||||
|
||||
> **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
|
||||
|
||||
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.
|
||||
|
||||
**On the Client:**
|
||||
|
||||
```bash
|
||||
./sd-cli --rpc-servers localhost:50052 --list-devices
|
||||
```
|
||||
|
||||
If the server is running and the client is able to connect, you should see `RPC0 localhost:50052` in the list of devices.
|
||||
|
||||
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
|
||||
|
||||
If everything is working correctly, you can now run the client while offloading some or all of the work to the RPC server.
|
||||
|
||||
Example: Setting the main backend to the RPC0 device for doing all the work on the server.
|
||||
|
||||
```bash
|
||||
./sd-cli -m models/sd1.5.safetensors -p "A cat" --rpc-servers localhost:50052 --backend RPC0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Scaling: Multiple RPC Servers
|
||||
|
||||
You can connect the client to multiple RPC servers simultaneously to scale out your hardware usage.
|
||||
|
||||
Example: A main machine (192.168.1.10) with 3 GPUs, with one GPU running CUDA and the other two running Vulkan, and a second machine (192.168.1.11) only one GPU.
|
||||
|
||||
**On the first machine (Running two server instances):**
|
||||
|
||||
**Terminal 1 (CUDA):**
|
||||
|
||||
```bash
|
||||
# Linux / WSL
|
||||
export CUDA_VISIBLE_DEVICES=0
|
||||
cd ./build_cuda/bin/Release
|
||||
./rpc-server --host 0.0.0.0
|
||||
|
||||
# Windows PowerShell
|
||||
$env:CUDA_VISIBLE_DEVICES="0"
|
||||
cd .\build_cuda\bin\Release
|
||||
./rpc-server --host 0.0.0.0
|
||||
```
|
||||
|
||||
**Terminal 2 (Vulkan):**
|
||||
|
||||
```bash
|
||||
cd ./build_vulkan/bin/Release
|
||||
# ignore the first GPU (used by CUDA server)
|
||||
./rpc-server --host 0.0.0.0 --port 50053 -d Vulkan1,Vulkan2
|
||||
```
|
||||
|
||||
**On the second machine:**
|
||||
|
||||
```bash
|
||||
cd ./build/bin/Release
|
||||
./rpc-server --host 0.0.0.0
|
||||
```
|
||||
|
||||
**On the Client:**
|
||||
Pass multiple server addresses separated by commas.
|
||||
|
||||
```bash
|
||||
./sd-cli --rpc-servers 192.168.1.10:50052,192.168.1.10:50053,192.168.1.11:50052 [...]
|
||||
```
|
||||
|
||||
The client will map these servers to sequential device IDs (e.g., RPC0 from the first server, RPC2, RPC3 from the second, and RPC4 from the third). With this setup, you could for example use RPC0 for the main backend, RPC1 and RPC2 for the text encoders, and RPC3 for the VAE.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,50 @@
|
||||
# How to Use
|
||||
|
||||
SeFi-Image uses a Flux2-style dual-time transformer (semantic + texture streams), the standard Flux2 VAE, and Qwen3-VL as the LLM text encoder. Tech report: [arXiv:2606.22568](https://arxiv.org/abs/2606.22568).
|
||||
|
||||
## Download weights
|
||||
|
||||
The SeFi-Image family ships in three scales (1B / 2B / 5B) and three families (Base / RL / turbo), all gated on Hugging Face under https://huggingface.co/SeFi-Image.
|
||||
|
||||
- 1B and 2B variants pair with Qwen3-VL-2B-Instruct.
|
||||
- 5B variants pair with Qwen3-VL-4B-Instruct.
|
||||
- All variants use the standard Flux2 VAE (`flux2_ae.safetensors` from https://huggingface.co/black-forest-labs/FLUX.2-dev).
|
||||
|
||||
Convert the transformer and text encoder to sd.cpp safetensors:
|
||||
|
||||
```bash
|
||||
python3 scripts/convert_sefi.py <hf_repo_dir> <out_dir>/sefi_<scale>_<family>.safetensors
|
||||
python3 scripts/convert_qwen3_vl.py <hf_repo_dir>/Qwen3-VL-XB-Instruct <out_dir>/qwen3_vl_<X>b.safetensors
|
||||
```
|
||||
|
||||
## Variant defaults
|
||||
|
||||
| Family | timestep_shift_alpha | steps | cfg-scale |
|
||||
|---|---|---|---|
|
||||
| Base | 0.3 | 50 | 4.0 |
|
||||
| RL | 0.3 | 50 | 4.0 |
|
||||
| turbo | 1.0 | 4 | 1.0 |
|
||||
|
||||
The dispatcher picks `alpha` from the filename (`turbo` substring => 1.0, otherwise 0.3). Override via `--extra-sample-args sefi_alpha=<value>` or `sefi_delta_t=<value>`.
|
||||
|
||||
## Examples
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
### 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 --stream-layers --offload-to-cpu -o out.png
|
||||
```
|
||||
|
||||
<img alt="SeFi-Image 5B turbo example" src="../assets/sefi_image/example.png" />
|
||||
@@ -55,7 +55,7 @@
|
||||
### Wan2.1 T2V 1.3B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\..\ComfyUI\models\diffusion_models\wan2.1_t2v_1.3B_fp16.safetensors --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\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 --diffusion-fa --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>
|
||||
@@ -63,7 +63,7 @@
|
||||
### Wan2.1 T2V 14B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\..\ComfyUI\models\diffusion_models\wan2.1-t2v-14b-Q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\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 --diffusion-fa --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>
|
||||
@@ -73,7 +73,7 @@
|
||||
### Wan2.1 I2V 14B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\..\ComfyUI\models\diffusion_models\wan2.1-i2v-14b-480p-Q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --clip_vision ..\..\ComfyUI\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 --diffusion-fa --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>
|
||||
@@ -81,7 +81,7 @@
|
||||
### Wan2.2 T2V A14B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\..\ComfyUI\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\..\ComfyUI\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-Q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\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 --diffusion-fa --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>
|
||||
@@ -89,7 +89,7 @@
|
||||
### Wan2.2 I2V A14B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\..\ComfyUI\models\diffusion_models\Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\..\ComfyUI\models\diffusion_models\Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\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 --diffusion-fa --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>
|
||||
@@ -97,7 +97,7 @@
|
||||
### Wan2.2 T2V A14B T2I
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\..\ComfyUI\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\..\ComfyUI\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-Q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\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 --diffusion-fa --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" />
|
||||
@@ -105,7 +105,7 @@
|
||||
### Wan2.2 T2V 14B with Lora
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\..\ComfyUI\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\..\ComfyUI\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-Q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\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 ..\..\ComfyUI\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 --diffusion-fa --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>
|
||||
@@ -117,7 +117,7 @@
|
||||
#### T2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\..\ComfyUI\models\diffusion_models\wan2.2_ti2v_5B_fp16.safetensors --vae ..\..\ComfyUI\models\vae\wan2.2_vae.safetensors --t5xxl ..\..\ComfyUI\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 --diffusion-fa --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>
|
||||
@@ -125,7 +125,7 @@
|
||||
#### I2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\..\ComfyUI\models\diffusion_models\wan2.2_ti2v_5B_fp16.safetensors --vae ..\..\ComfyUI\models\vae\wan2.2_vae.safetensors --t5xxl ..\..\ComfyUI\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 --diffusion-fa --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>
|
||||
@@ -133,7 +133,7 @@
|
||||
### Wan2.1 FLF2V 14B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\..\ComfyUI\models\diffusion_models\wan2.1-flf2v-14b-720p-Q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --clip_vision ..\..\ComfyUI\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 --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
|
||||
```
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
### Wan2.2 FLF2V 14B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\..\ComfyUI\models\diffusion_models\Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\..\ComfyUI\models\diffusion_models\Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\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 --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
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.2_14B_flf2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -152,7 +152,7 @@
|
||||
#### T2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\..\ComfyUI\models\diffusion_models\wan2.1-vace-1.3b-q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\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 --diffusion-fa --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>
|
||||
@@ -161,7 +161,7 @@
|
||||
#### R2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\..\ComfyUI\models\diffusion_models\wan2.1-vace-1.3b-q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\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 --diffusion-fa -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>
|
||||
@@ -172,7 +172,7 @@
|
||||
```
|
||||
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 ..\..\ComfyUI\models\diffusion_models\wan2.1-vace-1.3b-q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\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 --diffusion-fa -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>
|
||||
@@ -182,7 +182,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 ..\..\ComfyUI\models\diffusion_models\Wan2.1_14B_VACE-Q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\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 --diffusion-fa --video-frames 33 --offload-to-cpu
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.1_14B_vace_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -191,7 +191,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 ..\..\ComfyUI\models\diffusion_models\Wan2.1_14B_VACE-Q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\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 --diffusion-fa -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>
|
||||
@@ -201,7 +201,7 @@ 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 ..\..\ComfyUI\models\diffusion_models\Wan2.1_14B_VACE-Q8_0.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors --t5xxl ..\..\ComfyUI\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 --diffusion-fa -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>
|
||||
|
||||
@@ -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 ..\..\ComfyUI\models\vae\ae.sft --llm ..\..\ComfyUI\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
|
||||
.\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
|
||||
```
|
||||
|
||||
<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 ..\..\ComfyUI\models\diffusion_models\z_image_bf16.safetensors --vae ..\..\ComfyUI\models\vae\ae.sft --llm ..\..\ComfyUI\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 --diffusion-fa -H 1024 -W 512
|
||||
```
|
||||
|
||||
<img width="256" alt="z-image example" src="../assets/z_image/base_bf16.png" />
|
||||
|
||||
@@ -7,6 +7,13 @@ add_executable(${TARGET}
|
||||
image_metadata.cpp
|
||||
main.cpp
|
||||
)
|
||||
if(APPLE)
|
||||
sd_set_macos_rpaths(${TARGET})
|
||||
endif()
|
||||
target_include_directories(${TARGET} PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/.."
|
||||
"${PROJECT_SOURCE_DIR}/src"
|
||||
)
|
||||
install(TARGETS ${TARGET} RUNTIME)
|
||||
target_link_libraries(${TARGET} PRIVATE stable-diffusion zip ${CMAKE_THREAD_LIBS_INIT})
|
||||
if(SD_WEBP)
|
||||
|
||||
@@ -1,192 +1,9 @@
|
||||
# Run
|
||||
# Usage
|
||||
|
||||
```
|
||||
usage: ./bin/sd-cli [options]
|
||||
For detailed command-line arguments, run:
|
||||
|
||||
CLI Options:
|
||||
-o, --output <string> path to write result image to. you can use printf-style %d format specifiers for image
|
||||
sequences (default: ./output.png) (eg. output_%03d.png). Single-file video outputs
|
||||
support .avi, .webm, and animated .webp
|
||||
--image <string> path to the image to inspect (for metadata mode)
|
||||
--metadata-format <string> metadata output format, one of [text, json] (default: text)
|
||||
--preview-path <string> path to write preview image to (default: ./preview.png). Multi-frame previews support
|
||||
.avi, .webm, and animated .webp
|
||||
--preview-interval <int> interval in denoising steps between consecutive updates of the image preview file
|
||||
(default is 1, meaning updating at every step)
|
||||
--output-begin-idx <int> starting index for output image sequence, must be non-negative (default 0 if specified
|
||||
%d in output path, 1 otherwise)
|
||||
--canny apply canny preprocessor (edge detection)
|
||||
--convert-name convert tensor name (for convert mode)
|
||||
-v, --verbose print extra info
|
||||
--color colors the logging tags according to level
|
||||
--taesd-preview-only prevents usage of taesd for decoding the final image. (for use with --preview tae)
|
||||
--preview-noisy enables previewing noisy inputs of the models rather than the denoised outputs
|
||||
--metadata-raw include raw hex previews for unparsed metadata payloads
|
||||
--metadata-brief truncate long metadata text values in text output
|
||||
--metadata-all include structural/container entries such as IHDR, IDAT, and non-metadata JPEG segments
|
||||
-M, --mode run mode, one of [img_gen, vid_gen, upscale, convert, metadata], default: img_gen
|
||||
--preview preview method. must be one of the following [none, proj, tae, vae] (default is none)
|
||||
-h, --help show this help message and exit
|
||||
|
||||
Context Options:
|
||||
-m, --model <string> path to full model
|
||||
--clip_l <string> path to the clip-l text encoder
|
||||
--clip_g <string> path to the clip-g text encoder
|
||||
--clip_vision <string> path to the clip-vision encoder
|
||||
--t5xxl <string> path to the t5xxl text encoder
|
||||
--llm <string> path to the llm text encoder. For example: (qwenvl2.5 for qwen-image,
|
||||
mistral-small3.2 for flux2, ...)
|
||||
--llm_vision <string> path to the llm vit
|
||||
--qwen2vl <string> alias of --llm. Deprecated.
|
||||
--qwen2vl_vision <string> alias of --llm_vision. Deprecated.
|
||||
--diffusion-model <string> path to the standalone diffusion model
|
||||
--high-noise-diffusion-model <string> path to the standalone high noise diffusion model
|
||||
--vae <string> path to standalone vae model
|
||||
--taesd <string> path to taesd. Using Tiny AutoEncoder for fast decoding (low quality)
|
||||
--tae <string> alias of --taesd
|
||||
--control-net <string> path to control net model
|
||||
--embd-dir <string> embeddings directory
|
||||
--lora-model-dir <string> lora model directory
|
||||
--hires-upscalers-dir <string> highres fix upscaler model directory
|
||||
--tensor-type-rules <string> weight type per tensor pattern (example: "^vae\.=f16,model\.=q8_0")
|
||||
--photo-maker <string> path to PHOTOMAKER model
|
||||
--upscale-model <string> path to esrgan model.
|
||||
-t, --threads <int> number of threads to use during computation (default: -1). If threads <= 0,
|
||||
then threads will be set to the number of CPU physical cores
|
||||
--chroma-t5-mask-pad <int> t5 mask pad size of chroma
|
||||
--max-vram <float> maximum VRAM budget in GiB for graph-cut segmented execution. 0 disables
|
||||
graph splitting
|
||||
--force-sdxl-vae-conv-scale force use of conv scale on sdxl vae
|
||||
--offload-to-cpu place the weights in RAM to save VRAM, and automatically load them into VRAM
|
||||
when needed
|
||||
--mmap whether to memory-map model
|
||||
--control-net-cpu keep controlnet in cpu (for low vram)
|
||||
--clip-on-cpu keep clip in cpu (for low vram)
|
||||
--vae-on-cpu keep vae in cpu (for low vram)
|
||||
--fa use flash attention
|
||||
--diffusion-fa use flash attention in the diffusion model only
|
||||
--diffusion-conv-direct use ggml_conv2d_direct in the diffusion model
|
||||
--vae-conv-direct use ggml_conv2d_direct in the vae model
|
||||
--circular enable circular padding for convolutions
|
||||
--circularx enable circular RoPE wrapping on x-axis (width) only
|
||||
--circulary enable circular RoPE wrapping on y-axis (height) only
|
||||
--chroma-disable-dit-mask disable dit mask for chroma
|
||||
--qwen-image-zero-cond-t enable zero_cond_t for qwen image
|
||||
--chroma-enable-t5-mask enable t5 mask for chroma
|
||||
--type weight type (examples: f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_K, q3_K,
|
||||
q4_K). If not specified, the default is the type of the weight file
|
||||
--rng RNG, one of [std_default, cuda, cpu], default: cuda(sd-webui), cpu(comfyui)
|
||||
--sampler-rng sampler RNG, one of [std_default, cuda, cpu]. If not specified, use --rng
|
||||
--prediction prediction type override, one of [eps, v, edm_v, sd3_flow, flux_flow,
|
||||
flux2_flow]
|
||||
--lora-apply-mode the way to apply LoRA, one of [auto, immediately, at_runtime], default is
|
||||
auto. In auto mode, if the model weights contain any quantized parameters,
|
||||
the at_runtime mode will be used; otherwise, immediately will be used.The
|
||||
immediately mode may have precision and compatibility issues with quantized
|
||||
parameters, but it usually offers faster inference speed and, in some cases,
|
||||
lower memory usage. The at_runtime mode, on the other hand, is exactly the
|
||||
opposite.
|
||||
|
||||
Generation Options:
|
||||
-p, --prompt <string> the prompt to render
|
||||
-n, --negative-prompt <string> the negative prompt (default: "")
|
||||
-i, --init-img <string> path to the init image
|
||||
--end-img <string> path to the end image, required by flf2v
|
||||
--mask <string> path to the mask image
|
||||
--control-image <string> path to control image, control net
|
||||
--control-video <string> path to control video frames, It must be a directory path. The video frames
|
||||
inside should be stored as images in lexicographical (character) order. For
|
||||
example, if the control video path is `frames`, the directory contain images
|
||||
such as 00.png, 01.png, ... etc.
|
||||
--pm-id-images-dir <string> path to PHOTOMAKER input id images dir
|
||||
--pm-id-embed-path <string> path to PHOTOMAKER v2 id embed
|
||||
--hires-upscaler <string> highres fix upscaler, Lanczos, Nearest, Latent, Latent (nearest), Latent
|
||||
(nearest-exact), Latent (antialiased), Latent (bicubic), Latent (bicubic
|
||||
antialiased), or a model name under --hires-upscalers-dir (default: Latent)
|
||||
-H, --height <int> image height, in pixel space (default: 512)
|
||||
-W, --width <int> image width, in pixel space (default: 512)
|
||||
--steps <int> number of sample steps (default: 20)
|
||||
--high-noise-steps <int> (high noise) number of sample steps (default: -1 = auto)
|
||||
--clip-skip <int> ignore last layers of CLIP network; 1 ignores none, 2 ignores one layer
|
||||
(default: -1). <= 0 represents unspecified, will be 1 for SD1.x, 2 for SD2.x
|
||||
-b, --batch-count <int> batch count
|
||||
--video-frames <int> video frames (default: 1)
|
||||
--fps <int> fps (default: 24)
|
||||
--timestep-shift <int> shift timestep for NitroFusion models (default: 0). recommended N for
|
||||
NitroSD-Realism around 250 and 500 for NitroSD-Vibrant
|
||||
--upscale-repeats <int> Run the ESRGAN upscaler this many times (default: 1)
|
||||
--upscale-tile-size <int> tile size for ESRGAN upscaling (default: 128)
|
||||
--hires-width <int> highres fix target width, 0 to use --hires-scale (default: 0)
|
||||
--hires-height <int> highres fix target height, 0 to use --hires-scale (default: 0)
|
||||
--hires-steps <int> highres fix second pass sample steps, 0 to reuse --steps (default: 0)
|
||||
--hires-upscale-tile-size <int> highres fix upscaler tile size, reserved for model-backed upscalers (default:
|
||||
128)
|
||||
--cfg-scale <float> unconditional guidance scale: (default: 7.0)
|
||||
--img-cfg-scale <float> image guidance scale for inpaint or instruct-pix2pix models: (default: same
|
||||
as --cfg-scale)
|
||||
--guidance <float> distilled guidance scale for models with guidance input (default: 3.5)
|
||||
--slg-scale <float> skip layer guidance (SLG) scale, only for DiT models: (default: 0). 0 means
|
||||
disabled, a value of 2.5 is nice for sd3.5 medium
|
||||
--skip-layer-start <float> SLG enabling point (default: 0.01)
|
||||
--skip-layer-end <float> SLG disabling point (default: 0.2)
|
||||
--eta <float> noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and
|
||||
res_2s; 1 for euler_a, er_sde and dpm++2s_a)
|
||||
--flow-shift <float> shift value for Flow models like SD3.x or WAN (default: auto)
|
||||
--high-noise-cfg-scale <float> (high noise) unconditional guidance scale: (default: 7.0)
|
||||
--high-noise-img-cfg-scale <float> (high noise) image guidance scale for inpaint or instruct-pix2pix models
|
||||
(default: same as --cfg-scale)
|
||||
--high-noise-guidance <float> (high noise) distilled guidance scale for models with guidance input
|
||||
(default: 3.5)
|
||||
--high-noise-slg-scale <float> (high noise) skip layer guidance (SLG) scale, only for DiT models: (default:
|
||||
0)
|
||||
--high-noise-skip-layer-start <float> (high noise) SLG enabling point (default: 0.01)
|
||||
--high-noise-skip-layer-end <float> (high noise) SLG disabling point (default: 0.2)
|
||||
--high-noise-eta <float> (high noise) noise multiplier (default: 0 for ddim_trailing, tcd,
|
||||
res_multistep and res_2s; 1 for euler_a, er_sde and dpm++2s_a)
|
||||
--strength <float> strength for noising/unnoising (default: 0.75)
|
||||
--pm-style-strength <float>
|
||||
--control-strength <float> strength to apply Control Net (default: 0.9). 1.0 corresponds to full
|
||||
destruction of information in init image
|
||||
--moe-boundary <float> timestep boundary for Wan2.2 MoE model. (default: 0.875). Only enabled if
|
||||
`--high-noise-steps` is set to -1
|
||||
--vace-strength <float> wan vace strength
|
||||
--vae-tile-overlap <float> tile overlap for vae tiling, in fraction of tile size (default: 0.5)
|
||||
--hires-scale <float> highres fix scale when target size is not set (default: 2.0)
|
||||
--hires-denoising-strength <float> highres fix second pass denoising strength (default: 0.7)
|
||||
--increase-ref-index automatically increase the indices of references images based on the order
|
||||
they are listed (starting with 1).
|
||||
--disable-auto-resize-ref-image disable auto resize of ref images
|
||||
--disable-image-metadata do not embed generation metadata on image files
|
||||
--vae-tiling process vae in tiles to reduce memory usage
|
||||
--hires enable highres fix
|
||||
-s, --seed RNG seed (default: 42, use random seed for < 0)
|
||||
--sampling-method sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m,
|
||||
dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep, res_2s,
|
||||
er_sde, euler_cfg_pp, euler_a_cfg_pp] (default: euler for Flux/SD3/Wan, euler_a otherwise)
|
||||
--high-noise-sampling-method (high noise) sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a,
|
||||
dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep,
|
||||
res_2s, er_sde, euler_cfg_pp, euler_a_cfg_pp] default: euler for Flux/SD3/Wan, euler_a otherwise
|
||||
--scheduler denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits,
|
||||
smoothstep, sgm_uniform, simple, kl_optimal, lcm, bong_tangent], default:
|
||||
discrete
|
||||
--sigmas custom sigma values for the sampler, comma-separated (e.g.,
|
||||
"14.61,7.8,3.5,0.0").
|
||||
--skip-layers layers to skip for SLG steps (default: [7,8,9])
|
||||
--high-noise-skip-layers (high noise) layers to skip for SLG steps (default: [7,8,9])
|
||||
-r, --ref-image reference image for Flux Kontext models (can be used multiple times)
|
||||
--cache-mode caching method: 'easycache' (DiT), 'ucache' (UNET),
|
||||
'dbcache'/'taylorseer'/'cache-dit' (DiT block-level), 'spectrum' (UNET/DiT
|
||||
Chebyshev+Taylor forecasting)
|
||||
--cache-option named cache params (key=value format, comma-separated). easycache/ucache:
|
||||
threshold=,start=,end=,decay=,relative=,reset=; dbcache/taylorseer/cache-dit:
|
||||
Fn=,Bn=,threshold=,warmup=; spectrum: w=,m=,lam=,window=,flex=,warmup=,stop=.
|
||||
Examples: "threshold=0.25" or "threshold=1.5,reset=0"
|
||||
--scm-mask SCM steps mask for cache-dit: comma-separated 0/1 (e.g.,
|
||||
"1,1,1,0,0,1,0,0,1,0") - 1=compute, 0=can cache
|
||||
--scm-policy SCM policy: 'dynamic' (default) or 'static'
|
||||
--vae-tile-size tile size for vae tiling, format [X]x[Y] (default: 32x32)
|
||||
--vae-relative-tile-size relative tile size for vae tiling, format [X]x[Y], in fraction of image size
|
||||
if < 1, in number of tiles per dim if >=1 (overrides --vae-tile-size)
|
||||
```bash
|
||||
./bin/sd-cli -h
|
||||
```
|
||||
|
||||
Metadata mode inspects PNG/JPEG container metadata without loading any model:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
@@ -53,6 +54,9 @@ struct SDCliParams {
|
||||
bool metadata_brief = false;
|
||||
bool metadata_all = false;
|
||||
|
||||
std::string imatrix_out;
|
||||
std::vector<std::string> imatrix_in;
|
||||
|
||||
bool normal_exit = false;
|
||||
|
||||
ArgOptions get_options() {
|
||||
@@ -62,19 +66,28 @@ struct SDCliParams {
|
||||
{"-o",
|
||||
"--output",
|
||||
"path to write result image to. you can use printf-style %d format specifiers for image sequences (default: ./output.png) (eg. output_%03d.png). Single-file video outputs support .avi, .webm, and animated .webp",
|
||||
0,
|
||||
&output_path},
|
||||
{"",
|
||||
"--image",
|
||||
"path to the image to inspect (for metadata mode)",
|
||||
0,
|
||||
&image_path},
|
||||
{"",
|
||||
"--metadata-format",
|
||||
"metadata output format, one of [text, json] (default: text)",
|
||||
0,
|
||||
&metadata_format},
|
||||
{"",
|
||||
"--preview-path",
|
||||
"path to write preview image to (default: ./preview.png). Multi-frame previews support .avi, .webm, and animated .webp",
|
||||
0,
|
||||
&preview_path},
|
||||
{"",
|
||||
"--imat-out",
|
||||
"compute the imatrix for this run and save it to the provided path",
|
||||
0,
|
||||
&imatrix_out},
|
||||
};
|
||||
|
||||
options.int_options = {
|
||||
@@ -169,11 +182,20 @@ struct SDCliParams {
|
||||
return 1;
|
||||
};
|
||||
|
||||
auto on_help_arg = [&](int argc, const char** argv, int index) {
|
||||
auto on_help_arg = [&](int argc, const char** argv, int index, bool& valid) {
|
||||
normal_exit = true;
|
||||
valid = true;
|
||||
return -1;
|
||||
};
|
||||
|
||||
auto on_imatrix_in_arg = [&](int argc, const char** argv, int index) {
|
||||
if (++index >= argc) {
|
||||
return -1;
|
||||
}
|
||||
imatrix_in.push_back(argv[index]);
|
||||
return 1;
|
||||
};
|
||||
|
||||
options.manual_options = {
|
||||
{"-M",
|
||||
"--mode",
|
||||
@@ -187,6 +209,10 @@ struct SDCliParams {
|
||||
"--help",
|
||||
"show this help message and exit",
|
||||
on_help_arg},
|
||||
{"",
|
||||
"--imat-in",
|
||||
"load an imatrix file for quantization or continued collection; can be specified multiple times",
|
||||
on_imatrix_in_arg},
|
||||
};
|
||||
|
||||
return options;
|
||||
@@ -248,6 +274,7 @@ struct SDCliParams {
|
||||
<< " 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"
|
||||
<< " metadata_brief: " << (metadata_brief ? "true" : "false") << ",\n"
|
||||
<< " metadata_all: " << (metadata_all ? "true" : "false") << "\n"
|
||||
@@ -385,11 +412,32 @@ std::string format_frame_idx(std::string pattern, int frame_idx) {
|
||||
return result;
|
||||
}
|
||||
|
||||
static fs::path get_video_audio_sidecar_path(const SDCliParams& cli_params) {
|
||||
fs::path out_path = 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::transform(ext_lower.begin(), ext_lower.end(), ext_lower.begin(), ::tolower);
|
||||
const EncodedImageFormat output_format = encoded_image_format_from_path(out_path.string());
|
||||
if (!ext.empty()) {
|
||||
if (output_format == EncodedImageFormat::JPEG ||
|
||||
output_format == EncodedImageFormat::PNG ||
|
||||
output_format == EncodedImageFormat::WEBP ||
|
||||
ext_lower == ".avi" ||
|
||||
ext_lower == ".webm") {
|
||||
base_path.replace_extension();
|
||||
}
|
||||
}
|
||||
base_path += ".wav";
|
||||
return base_path;
|
||||
}
|
||||
|
||||
bool save_results(const SDCliParams& cli_params,
|
||||
const SDContextParams& ctx_params,
|
||||
const SDGenerationParams& gen_params,
|
||||
sd_image_t* results,
|
||||
int num_results) {
|
||||
int num_results,
|
||||
const sd_audio_t* generated_audio = nullptr) {
|
||||
if (results == nullptr || num_results <= 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -433,7 +481,8 @@ bool save_results(const SDCliParams& cli_params,
|
||||
if (!img.data)
|
||||
return false;
|
||||
|
||||
const int64_t metadata_seed = cli_params.mode == VID_GEN ? gen_params.seed : gen_params.seed + idx;
|
||||
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)
|
||||
: "";
|
||||
@@ -442,6 +491,21 @@ bool save_results(const SDCliParams& cli_params,
|
||||
return ok;
|
||||
};
|
||||
|
||||
auto write_audio_sidecar = [&](const fs::path& wav_path) {
|
||||
if (generated_audio == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (write_wav_to_file(wav_path.string(),
|
||||
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());
|
||||
} else {
|
||||
LOG_WARN("failed to save result audio to '%s'", wav_path.string().c_str());
|
||||
}
|
||||
};
|
||||
|
||||
int sucessful_reults = 0;
|
||||
|
||||
if (std::regex_search(cli_params.output_path, format_specifier_regex)) {
|
||||
@@ -465,8 +529,16 @@ bool save_results(const SDCliParams& cli_params,
|
||||
ext = ".avi";
|
||||
fs::path video_path = base_path;
|
||||
video_path += ext;
|
||||
if (create_video_from_sd_images(video_path.string().c_str(), results, num_results, gen_params.fps) == 0) {
|
||||
std::string final_ext_lower = ext.string();
|
||||
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, 90, mux_audio ? generated_audio : nullptr) == 0) {
|
||||
LOG_INFO("save result video to '%s'", video_path.string().c_str());
|
||||
if (generated_audio != nullptr && !mux_audio) {
|
||||
fs::path wav_path = video_path;
|
||||
wav_path.replace_extension(".wav");
|
||||
write_audio_sidecar(wav_path);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
LOG_ERROR("Failed to save result video to '%s'", video_path.string().c_str());
|
||||
@@ -488,6 +560,9 @@ bool save_results(const SDCliParams& cli_params,
|
||||
}
|
||||
}
|
||||
LOG_INFO("%d/%d images saved", sucessful_reults, num_results);
|
||||
if (generated_audio != nullptr) {
|
||||
write_audio_sidecar(get_video_audio_sidecar_path(cli_params));
|
||||
}
|
||||
return sucessful_reults != 0;
|
||||
}
|
||||
|
||||
@@ -553,13 +628,33 @@ int main(int argc, const char* argv[]) {
|
||||
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) &&
|
||||
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());
|
||||
}
|
||||
enable_imatrix_collection();
|
||||
}
|
||||
|
||||
for (const auto& in_file : cli_params.imatrix_in) {
|
||||
LOG_INFO("loading imatrix from '%s'", in_file.c_str());
|
||||
if (!load_imatrix(in_file.c_str())) {
|
||||
LOG_WARN("failed to load imatrix from '%s'", in_file.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (cli_params.mode == CONVERT) {
|
||||
bool success = convert(ctx_params.model_path.c_str(),
|
||||
ctx_params.vae_path.c_str(),
|
||||
cli_params.output_path.c_str(),
|
||||
ctx_params.wtype,
|
||||
ctx_params.tensor_type_rules.c_str(),
|
||||
cli_params.convert_name);
|
||||
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(),
|
||||
ctx_params.t5xxl_path.c_str(),
|
||||
ctx_params.diffusion_model_path.c_str(),
|
||||
ctx_params.vae_path.c_str(),
|
||||
cli_params.output_path.c_str(),
|
||||
ctx_params.wtype,
|
||||
ctx_params.tensor_type_rules.c_str(),
|
||||
cli_params.convert_name,
|
||||
ctx_params.n_threads);
|
||||
if (!success) {
|
||||
LOG_ERROR("convert '%s'/'%s' to '%s' failed",
|
||||
ctx_params.model_path.c_str(),
|
||||
@@ -575,8 +670,6 @@ int main(int argc, const char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
bool vae_decode_only = true;
|
||||
|
||||
auto load_image_and_update_size = [&](const std::string& path,
|
||||
SDImageOwner& image,
|
||||
bool resize_image = true,
|
||||
@@ -598,21 +691,18 @@ int main(int argc, const char* argv[]) {
|
||||
};
|
||||
|
||||
if (gen_params.init_image_path.size() > 0) {
|
||||
vae_decode_only = false;
|
||||
if (!load_image_and_update_size(gen_params.init_image_path, gen_params.init_image)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (gen_params.end_image_path.size() > 0) {
|
||||
vae_decode_only = false;
|
||||
if (!load_image_and_update_size(gen_params.end_image_path, gen_params.end_image)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (gen_params.ref_image_paths.size() > 0) {
|
||||
vae_decode_only = false;
|
||||
gen_params.ref_images.clear();
|
||||
for (auto& path : gen_params.ref_image_paths) {
|
||||
SDImageOwner ref_image({0, 0, 3, nullptr});
|
||||
@@ -687,21 +777,11 @@ int main(int argc, const char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if (cli_params.mode == VID_GEN) {
|
||||
vae_decode_only = false;
|
||||
}
|
||||
|
||||
if (gen_params.hires_enabled &&
|
||||
(gen_params.resolved_hires_upscaler == SD_HIRES_UPSCALER_MODEL ||
|
||||
gen_params.resolved_hires_upscaler == SD_HIRES_UPSCALER_LANCZOS ||
|
||||
gen_params.resolved_hires_upscaler == SD_HIRES_UPSCALER_NEAREST)) {
|
||||
vae_decode_only = false;
|
||||
}
|
||||
|
||||
sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(vae_decode_only, true, cli_params.taesd_preview);
|
||||
sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(cli_params.taesd_preview);
|
||||
|
||||
SDImageVec results;
|
||||
int num_results = 0;
|
||||
int num_results = 0;
|
||||
sd_audio_t* generated_audio = nullptr;
|
||||
|
||||
if (cli_params.mode == UPSCALE) {
|
||||
num_results = 1;
|
||||
@@ -729,11 +809,18 @@ int main(int argc, const char* argv[]) {
|
||||
if (cli_params.mode == IMG_GEN) {
|
||||
sd_img_gen_params_t img_gen_params = gen_params.to_sd_img_gen_params_t();
|
||||
|
||||
num_results = gen_params.batch_count;
|
||||
results.adopt(generate_image(sd_ctx.get(), &img_gen_params), num_results);
|
||||
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);
|
||||
} 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 = generate_video(sd_ctx.get(), &vid_gen_params, &num_results);
|
||||
sd_image_t* generated_video = nullptr;
|
||||
if (!generate_video(sd_ctx.get(), &vid_gen_params, &generated_video, &num_results, &generated_audio)) {
|
||||
generated_video = nullptr;
|
||||
}
|
||||
results.adopt(generated_video, num_results);
|
||||
}
|
||||
|
||||
@@ -746,10 +833,11 @@ int main(int argc, const char* argv[]) {
|
||||
int upscale_factor = 4; // unused for RealESRGAN_x4plus_anime_6B.pth
|
||||
if (ctx_params.esrgan_path.size() > 0 && gen_params.upscale_repeats > 0) {
|
||||
UpscalerCtxPtr upscaler_ctx(new_upscaler_ctx(ctx_params.esrgan_path.c_str(),
|
||||
ctx_params.offload_params_to_cpu,
|
||||
ctx_params.diffusion_conv_direct,
|
||||
ctx_params.n_threads,
|
||||
gen_params.upscale_tile_size));
|
||||
gen_params.upscale_tile_size,
|
||||
sd_ctx_params.backend,
|
||||
sd_ctx_params.params_backend));
|
||||
|
||||
if (upscaler_ctx == nullptr) {
|
||||
LOG_ERROR("new_upscaler_ctx failed");
|
||||
@@ -761,21 +849,39 @@ 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) {
|
||||
SDImageOwner upscaled_image(upscale(upscaler_ctx.get(), current_image.get(), upscale_factor));
|
||||
if (upscaled_image.get().data == nullptr) {
|
||||
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);
|
||||
LOG_ERROR("upscale failed");
|
||||
break;
|
||||
}
|
||||
current_image = std::move(upscaled_image);
|
||||
sd_image_t upscaled_image = upscaled_images[0];
|
||||
upscaled_images[0] = {0, 0, 0, nullptr};
|
||||
free_sd_images(upscaled_images, upscaled_count);
|
||||
current_image.reset(upscaled_image);
|
||||
}
|
||||
results[i] = current_image.release(); // Set the final upscaled image as the result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!save_results(cli_params, ctx_params, gen_params, results.data(), num_results)) {
|
||||
if (!save_results(cli_params, ctx_params, gen_params, results.data(), num_results, generated_audio)) {
|
||||
free_sd_audio(generated_audio);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!cli_params.imatrix_out.empty()) {
|
||||
LOG_INFO("saving imatrix to '%s'", cli_params.imatrix_out.c_str());
|
||||
save_imatrix(cli_params.imatrix_out.c_str());
|
||||
}
|
||||
|
||||
free_sd_audio(generated_audio);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ struct StringOption {
|
||||
std::string short_name;
|
||||
std::string long_name;
|
||||
std::string desc;
|
||||
int concat;
|
||||
std::string* target;
|
||||
};
|
||||
|
||||
@@ -56,11 +57,42 @@ struct BoolOption {
|
||||
bool* target;
|
||||
};
|
||||
|
||||
struct ManualFunction {
|
||||
std::function<int(int, const char**, int, bool&)> _func;
|
||||
|
||||
ManualFunction() = default;
|
||||
|
||||
ManualFunction(std::function<int(int argc, const char** argv, int index, bool& valid)> func)
|
||||
: _func(std::move(func)) {
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
ManualFunction(F func)
|
||||
: _func(make_function(func)) {
|
||||
}
|
||||
|
||||
int operator()(int argc, const char** argv, int index, bool& valid) const {
|
||||
return _func(argc, argv, index, valid);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename F>
|
||||
static std::function<int(int, const char**, int, bool&)> make_function(F func) {
|
||||
if constexpr (std::is_invocable_v<F, int, const char**, int, bool&>) {
|
||||
return func;
|
||||
} else {
|
||||
return [func](int argc, const char** argv, int index, bool&) {
|
||||
return func(argc, argv, index);
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ManualOption {
|
||||
std::string short_name;
|
||||
std::string long_name;
|
||||
std::string desc;
|
||||
std::function<int(int argc, const char** argv, int index)> cb;
|
||||
ManualFunction cb;
|
||||
};
|
||||
|
||||
struct ArgOptions {
|
||||
@@ -92,12 +124,17 @@ struct SDContextParams {
|
||||
std::string llm_vision_path;
|
||||
std::string diffusion_model_path;
|
||||
std::string high_noise_diffusion_model_path;
|
||||
std::string uncond_diffusion_model_path;
|
||||
std::string embeddings_connectors_path;
|
||||
std::string vae_path;
|
||||
std::string vae_format = "auto";
|
||||
std::string audio_vae_path;
|
||||
std::string taesd_path;
|
||||
std::string esrgan_path;
|
||||
std::string control_net_path;
|
||||
std::string embedding_dir;
|
||||
std::string photo_maker_path;
|
||||
std::string pulid_weights_path;
|
||||
sd_type_t wtype = SD_TYPE_COUNT;
|
||||
std::string tensor_type_rules;
|
||||
std::string lora_model_dir = ".";
|
||||
@@ -109,19 +146,24 @@ struct SDContextParams {
|
||||
rng_type_t rng_type = CUDA_RNG;
|
||||
rng_type_t sampler_rng_type = RNG_TYPE_COUNT;
|
||||
bool offload_params_to_cpu = false;
|
||||
float max_vram = 0.f;
|
||||
bool enable_mmap = false;
|
||||
bool control_net_cpu = false;
|
||||
bool clip_on_cpu = false;
|
||||
bool vae_on_cpu = false;
|
||||
bool flash_attn = false;
|
||||
bool diffusion_flash_attn = false;
|
||||
bool diffusion_conv_direct = false;
|
||||
bool vae_conv_direct = false;
|
||||
|
||||
bool circular = false;
|
||||
bool circular_x = false;
|
||||
bool circular_y = false;
|
||||
std::string max_vram = "0";
|
||||
bool stream_layers = false;
|
||||
bool eager_load = false;
|
||||
std::string backend;
|
||||
std::string params_backend;
|
||||
std::string split_mode;
|
||||
bool auto_fit = false;
|
||||
std::string rpc_servers;
|
||||
std::string effective_backend;
|
||||
std::string effective_params_backend;
|
||||
bool enable_mmap = false;
|
||||
bool control_net_cpu = false;
|
||||
bool clip_on_cpu = false;
|
||||
bool vae_on_cpu = false;
|
||||
bool flash_attn = false;
|
||||
bool diffusion_flash_attn = false;
|
||||
bool diffusion_conv_direct = false;
|
||||
bool vae_conv_direct = false;
|
||||
|
||||
bool chroma_use_dit_mask = true;
|
||||
bool chroma_use_t5_mask = false;
|
||||
@@ -137,11 +179,12 @@ struct SDContextParams {
|
||||
float flow_shift = INFINITY;
|
||||
ArgOptions get_options();
|
||||
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 vae_decode_only, bool free_params_immediately, bool taesd_preview);
|
||||
sd_ctx_params_t to_sd_ctx_params_t(bool taesd_preview);
|
||||
};
|
||||
|
||||
struct SDGenerationParams {
|
||||
@@ -152,6 +195,7 @@ struct SDGenerationParams {
|
||||
int width = -1;
|
||||
int height = -1;
|
||||
int batch_count = 1;
|
||||
int qwen_image_layers = 3;
|
||||
int64_t seed = 42;
|
||||
float strength = 0.75f;
|
||||
float control_strength = 0.9f;
|
||||
@@ -168,6 +212,8 @@ struct SDGenerationParams {
|
||||
|
||||
sd_sample_params_t sample_params;
|
||||
sd_sample_params_t high_noise_sample_params;
|
||||
std::string extra_sample_args;
|
||||
std::string high_noise_extra_sample_args;
|
||||
std::vector<int> skip_layers = {7, 8, 9};
|
||||
std::vector<int> high_noise_skip_layers = {7, 8, 9};
|
||||
|
||||
@@ -183,15 +229,23 @@ struct SDGenerationParams {
|
||||
int video_frames = 1;
|
||||
int fps = 16;
|
||||
float vace_strength = 1.f;
|
||||
sd_tiling_params_t vae_tiling_params = {false, 0, 0, 0.5f, 0.0f, 0.0f};
|
||||
sd_tiling_params_t vae_tiling_params = {false, false, 0, 0, 0.5f, 0.0f, 0.0f, nullptr};
|
||||
std::string extra_tiling_args;
|
||||
|
||||
std::string pm_id_images_dir;
|
||||
std::string pm_id_embed_path;
|
||||
float pm_style_strength = 20.f;
|
||||
|
||||
std::string pulid_id_embedding_path;
|
||||
float pulid_id_weight = 1.0f;
|
||||
|
||||
int upscale_repeats = 1;
|
||||
int upscale_tile_size = 128;
|
||||
|
||||
bool circular = false;
|
||||
bool circular_x = false;
|
||||
bool circular_y = false;
|
||||
|
||||
bool hires_enabled = false;
|
||||
std::string hires_upscaler = "Latent";
|
||||
std::string hires_upscaler_model_path;
|
||||
@@ -201,6 +255,7 @@ struct SDGenerationParams {
|
||||
int hires_steps = 0;
|
||||
float hires_denoising_strength = 0.7f;
|
||||
int hires_upscale_tile_size = 128;
|
||||
std::vector<float> hires_custom_sigmas;
|
||||
|
||||
std::map<std::string, float> lora_map;
|
||||
std::map<std::string, float> high_noise_lora_map;
|
||||
|
||||
@@ -613,6 +613,13 @@ typedef struct {
|
||||
uint32_t size;
|
||||
} avi_index_entry;
|
||||
|
||||
typedef struct {
|
||||
char fourcc[4];
|
||||
uint32_t flags;
|
||||
uint32_t offset;
|
||||
uint32_t size;
|
||||
} avi_chunk_index_entry;
|
||||
|
||||
void write_u32_le(FILE* f, uint32_t val) {
|
||||
fwrite(&val, 4, 1, f);
|
||||
}
|
||||
@@ -647,6 +654,33 @@ void write_fourcc(std::vector<uint8_t>& data, const char* fourcc) {
|
||||
data.insert(data.end(), fourcc, fourcc + 4);
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> audio_to_pcm16_bytes(const sd_audio_t* audio) {
|
||||
if (audio == nullptr || audio->data == nullptr || audio->sample_count == 0 || audio->channels == 0 || audio->sample_rate == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const size_t pcm_samples = static_cast<size_t>(audio->sample_count) * static_cast<size_t>(audio->channels);
|
||||
std::vector<uint8_t> bytes(pcm_samples * sizeof(int16_t));
|
||||
auto* pcm = reinterpret_cast<int16_t*>(bytes.data());
|
||||
for (size_t i = 0; i < pcm_samples; ++i) {
|
||||
const float sample = std::clamp(audio->data[i], -1.0f, 1.0f);
|
||||
pcm[i] = static_cast<int16_t>(std::lrint(sample * 32767.0f));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static std::pair<uint64_t, uint64_t> audio_sample_range_for_video_frame(const sd_audio_t* audio, int frame_idx, int num_frames, int fps) {
|
||||
if (audio == nullptr || fps <= 0 || num_frames <= 0) {
|
||||
return {0, 0};
|
||||
}
|
||||
const uint64_t total = audio->sample_count;
|
||||
const uint64_t start = static_cast<uint64_t>((static_cast<long double>(frame_idx) * total) / num_frames);
|
||||
const uint64_t end = frame_idx + 1 == num_frames
|
||||
? total
|
||||
: static_cast<uint64_t>((static_cast<long double>(frame_idx + 1) * total) / num_frames);
|
||||
return {start, std::max(start, end)};
|
||||
}
|
||||
|
||||
EncodedImageFormat encoded_image_format_from_path(const std::string& path) {
|
||||
std::string ext = fs::path(path).extension().string();
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
|
||||
@@ -776,7 +810,7 @@ uint8_t* load_image_from_memory(const char* image_bytes,
|
||||
return load_image_common(true, image_bytes, len, width, height, expected_width, expected_height, expected_channel);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> create_mjpg_avi_from_sd_images_to_vector(sd_image_t* images, int num_images, int fps, int quality) {
|
||||
std::vector<uint8_t> create_mjpg_avi_from_sd_images_to_vector(sd_image_t* images, int num_images, int fps, int quality, const sd_audio_t* audio) {
|
||||
if (num_images == 0) {
|
||||
fprintf(stderr, "Error: Image array is empty.\n");
|
||||
return {};
|
||||
@@ -793,7 +827,13 @@ std::vector<uint8_t> create_mjpg_avi_from_sd_images_to_vector(sd_image_t* images
|
||||
// stb_image_write changes JPEG sampling behavior above quality 90.
|
||||
// MJPG AVI playback is more compatible when we keep the encoder on the
|
||||
// <= 90 path.
|
||||
const int mjpg_quality = std::clamp(quality, 1, 90);
|
||||
const int mjpg_quality = std::clamp(quality, 1, 90);
|
||||
const bool has_audio = audio != nullptr && audio->data != nullptr && audio->sample_count > 0 && audio->channels > 0 && audio->sample_rate > 0;
|
||||
const std::vector<uint8_t> audio_pcm = audio_to_pcm16_bytes(audio);
|
||||
const uint16_t audio_bits_per_sample = 16;
|
||||
const uint16_t audio_block_align = has_audio ? static_cast<uint16_t>(audio->channels * (audio_bits_per_sample / 8)) : 0;
|
||||
const uint32_t audio_byte_rate = has_audio ? static_cast<uint32_t>(audio->sample_rate * audio_block_align) : 0;
|
||||
const uint32_t audio_data_size = has_audio ? static_cast<uint32_t>(audio_pcm.size()) : 0;
|
||||
|
||||
std::vector<uint8_t> avi_data;
|
||||
avi_data.reserve(static_cast<size_t>(num_images) * 1024);
|
||||
@@ -804,7 +844,11 @@ std::vector<uint8_t> create_mjpg_avi_from_sd_images_to_vector(sd_image_t* images
|
||||
write_fourcc(avi_data, "AVI ");
|
||||
|
||||
write_fourcc(avi_data, "LIST");
|
||||
write_u32_le(avi_data, 4 + 8 + 56 + 8 + 4 + 8 + 56 + 8 + 40);
|
||||
uint32_t hdrl_size = 4 + 8 + 56 + 8 + 4 + 8 + 56 + 8 + 40;
|
||||
if (has_audio) {
|
||||
hdrl_size += 8 + (4 + 8 + 56 + 8 + 16);
|
||||
}
|
||||
write_u32_le(avi_data, hdrl_size);
|
||||
write_fourcc(avi_data, "hdrl");
|
||||
|
||||
write_fourcc(avi_data, "avih");
|
||||
@@ -815,7 +859,7 @@ std::vector<uint8_t> create_mjpg_avi_from_sd_images_to_vector(sd_image_t* images
|
||||
write_u32_le(avi_data, 0x110);
|
||||
write_u32_le(avi_data, num_images);
|
||||
write_u32_le(avi_data, 0);
|
||||
write_u32_le(avi_data, 1);
|
||||
write_u32_le(avi_data, has_audio ? 2 : 1);
|
||||
write_u32_le(avi_data, width * height * 3);
|
||||
write_u32_le(avi_data, width);
|
||||
write_u32_le(avi_data, height);
|
||||
@@ -862,12 +906,48 @@ std::vector<uint8_t> create_mjpg_avi_from_sd_images_to_vector(sd_image_t* images
|
||||
write_u32_le(avi_data, 0);
|
||||
write_u32_le(avi_data, 0);
|
||||
|
||||
if (has_audio) {
|
||||
write_fourcc(avi_data, "LIST");
|
||||
write_u32_le(avi_data, 4 + 8 + 56 + 8 + 16);
|
||||
write_fourcc(avi_data, "strl");
|
||||
|
||||
write_fourcc(avi_data, "strh");
|
||||
write_u32_le(avi_data, 56);
|
||||
write_fourcc(avi_data, "auds");
|
||||
write_u32_le(avi_data, 0);
|
||||
write_u32_le(avi_data, 0);
|
||||
write_u16_le(avi_data, 0);
|
||||
write_u16_le(avi_data, 0);
|
||||
write_u32_le(avi_data, 0);
|
||||
write_u32_le(avi_data, audio_block_align);
|
||||
write_u32_le(avi_data, audio_byte_rate);
|
||||
write_u32_le(avi_data, 0);
|
||||
write_u32_le(avi_data, static_cast<uint32_t>(audio->sample_count));
|
||||
write_u32_le(avi_data, audio_data_size);
|
||||
write_u32_le(avi_data, static_cast<uint32_t>(-1));
|
||||
write_u32_le(avi_data, audio_block_align);
|
||||
write_u16_le(avi_data, 0);
|
||||
write_u16_le(avi_data, 0);
|
||||
write_u16_le(avi_data, 0);
|
||||
write_u16_le(avi_data, 0);
|
||||
|
||||
write_fourcc(avi_data, "strf");
|
||||
write_u32_le(avi_data, 16);
|
||||
write_u16_le(avi_data, 1);
|
||||
write_u16_le(avi_data, static_cast<uint16_t>(audio->channels));
|
||||
write_u32_le(avi_data, audio->sample_rate);
|
||||
write_u32_le(avi_data, audio_byte_rate);
|
||||
write_u16_le(avi_data, audio_block_align);
|
||||
write_u16_le(avi_data, audio_bits_per_sample);
|
||||
}
|
||||
|
||||
write_fourcc(avi_data, "LIST");
|
||||
const size_t movi_size_pos = avi_data.size();
|
||||
write_u32_le(avi_data, 0);
|
||||
write_fourcc(avi_data, "movi");
|
||||
|
||||
std::vector<avi_index_entry> index(static_cast<size_t>(num_images));
|
||||
std::vector<avi_chunk_index_entry> index;
|
||||
index.reserve(static_cast<size_t>(num_images) + (has_audio ? 1 : 0));
|
||||
std::vector<uint8_t> jpeg_data;
|
||||
|
||||
for (int i = 0; i < num_images; i++) {
|
||||
@@ -884,27 +964,46 @@ std::vector<uint8_t> create_mjpg_avi_from_sd_images_to_vector(sd_image_t* images
|
||||
return {};
|
||||
}
|
||||
|
||||
index[i].offset = static_cast<uint32_t>(avi_data.size());
|
||||
avi_chunk_index_entry video_entry = {};
|
||||
memcpy(video_entry.fourcc, "00dc", 4);
|
||||
video_entry.flags = 0x10;
|
||||
video_entry.offset = static_cast<uint32_t>(avi_data.size());
|
||||
write_fourcc(avi_data, "00dc");
|
||||
write_u32_le(avi_data, static_cast<uint32_t>(jpeg_data.size()));
|
||||
index[i].size = (uint32_t)jpeg_data.size();
|
||||
video_entry.size = static_cast<uint32_t>(jpeg_data.size());
|
||||
avi_data.insert(avi_data.end(), jpeg_data.begin(), jpeg_data.end());
|
||||
index.push_back(video_entry);
|
||||
|
||||
if (jpeg_data.size() % 2) {
|
||||
avi_data.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (has_audio && !audio_pcm.empty()) {
|
||||
avi_chunk_index_entry audio_entry = {};
|
||||
memcpy(audio_entry.fourcc, "01wb", 4);
|
||||
audio_entry.flags = 0;
|
||||
audio_entry.offset = static_cast<uint32_t>(avi_data.size());
|
||||
audio_entry.size = static_cast<uint32_t>(audio_pcm.size());
|
||||
write_fourcc(avi_data, "01wb");
|
||||
write_u32_le(avi_data, static_cast<uint32_t>(audio_pcm.size()));
|
||||
avi_data.insert(avi_data.end(), audio_pcm.begin(), audio_pcm.end());
|
||||
index.push_back(audio_entry);
|
||||
if (audio_pcm.size() % 2 != 0) {
|
||||
avi_data.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
const size_t movi_size = avi_data.size() - movi_size_pos - 4;
|
||||
patch_u32_le(avi_data, movi_size_pos, static_cast<uint32_t>(movi_size));
|
||||
|
||||
write_fourcc(avi_data, "idx1");
|
||||
write_u32_le(avi_data, num_images * 16);
|
||||
for (int i = 0; i < num_images; i++) {
|
||||
write_fourcc(avi_data, "00dc");
|
||||
write_u32_le(avi_data, 0x10);
|
||||
write_u32_le(avi_data, index[i].offset);
|
||||
write_u32_le(avi_data, index[i].size);
|
||||
write_u32_le(avi_data, static_cast<uint32_t>(index.size() * 16));
|
||||
for (const auto& entry : index) {
|
||||
write_fourcc(avi_data, entry.fourcc);
|
||||
write_u32_le(avi_data, entry.flags);
|
||||
write_u32_le(avi_data, entry.offset);
|
||||
write_u32_le(avi_data, entry.size);
|
||||
}
|
||||
|
||||
const size_t file_size = avi_data.size() - riff_size_pos - 4;
|
||||
@@ -913,8 +1012,8 @@ std::vector<uint8_t> create_mjpg_avi_from_sd_images_to_vector(sd_image_t* images
|
||||
return avi_data;
|
||||
}
|
||||
|
||||
int create_mjpg_avi_from_sd_images(const char* filename, sd_image_t* images, int num_images, int fps, int quality) {
|
||||
std::vector<uint8_t> avi_data = create_mjpg_avi_from_sd_images_to_vector(images, num_images, fps, quality);
|
||||
int create_mjpg_avi_from_sd_images(const char* filename, sd_image_t* images, int num_images, int fps, int quality, const sd_audio_t* audio) {
|
||||
std::vector<uint8_t> avi_data = create_mjpg_avi_from_sd_images_to_vector(images, num_images, fps, quality, audio);
|
||||
if (avi_data.empty()) {
|
||||
return -1;
|
||||
}
|
||||
@@ -1044,7 +1143,7 @@ int create_animated_webp_from_sd_images(const char* filename, sd_image_t* images
|
||||
#endif
|
||||
|
||||
#ifdef SD_USE_WEBM
|
||||
std::vector<uint8_t> create_webm_from_sd_images_to_vector(sd_image_t* images, int num_images, int fps, int quality) {
|
||||
std::vector<uint8_t> create_webm_from_sd_images_to_vector(sd_image_t* images, int num_images, int fps, int quality, const sd_audio_t* audio) {
|
||||
if (num_images == 0) {
|
||||
fprintf(stderr, "Error: Image array is empty.\n");
|
||||
return {};
|
||||
@@ -1089,6 +1188,25 @@ std::vector<uint8_t> create_webm_from_sd_images_to_vector(sd_image_t* images, in
|
||||
video_track->set_display_height(static_cast<uint64_t>(height));
|
||||
video_track->set_frame_rate(static_cast<double>(fps));
|
||||
}
|
||||
|
||||
uint64_t audio_track_number = 0;
|
||||
std::vector<uint8_t> audio_pcm = audio_to_pcm16_bytes(audio);
|
||||
if (audio != nullptr && !audio_pcm.empty()) {
|
||||
audio_track_number = segment.AddAudioTrack(static_cast<int32_t>(audio->sample_rate), static_cast<int32_t>(audio->channels), 0);
|
||||
if (audio_track_number == 0) {
|
||||
fprintf(stderr, "Error: Failed to add audio track.\n");
|
||||
return -1;
|
||||
}
|
||||
auto* audio_track = static_cast<mkvmuxer::AudioTrack*>(segment.GetTrackByNumber(audio_track_number));
|
||||
if (audio_track == nullptr) {
|
||||
fprintf(stderr, "Error: Failed to get audio track.\n");
|
||||
return -1;
|
||||
}
|
||||
audio_track->set_codec_id("A_PCM/INT/LIT");
|
||||
audio_track->set_bit_depth(16);
|
||||
audio_track->set_sample_rate(static_cast<double>(audio->sample_rate));
|
||||
audio_track->set_channels(audio->channels);
|
||||
}
|
||||
segment.GetSegmentInfo()->set_writing_app("stable-diffusion.cpp");
|
||||
segment.GetSegmentInfo()->set_muxing_app("stable-diffusion.cpp");
|
||||
|
||||
@@ -1118,6 +1236,23 @@ std::vector<uint8_t> create_webm_from_sd_images_to_vector(sd_image_t* images, in
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (audio_track_number != 0) {
|
||||
auto [audio_begin, audio_end] = audio_sample_range_for_video_frame(audio, i, num_images, fps);
|
||||
const uint64_t frame_samples = audio_end - audio_begin;
|
||||
if (frame_samples > 0) {
|
||||
const uint64_t frame_bytes = frame_samples * audio->channels * sizeof(int16_t);
|
||||
const uint8_t* frame_ptr = audio_pcm.data() + audio_begin * audio->channels * sizeof(int16_t);
|
||||
if (!segment.AddFrame(frame_ptr,
|
||||
frame_bytes,
|
||||
audio_track_number,
|
||||
timestamp_ns,
|
||||
true)) {
|
||||
fprintf(stderr, "Error: Failed to mux audio chunk %d into WebM.\n", i);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timestamp_ns += frame_duration_ns;
|
||||
}
|
||||
|
||||
@@ -1133,8 +1268,8 @@ std::vector<uint8_t> create_webm_from_sd_images_to_vector(sd_image_t* images, in
|
||||
return writer.data();
|
||||
}
|
||||
|
||||
int create_webm_from_sd_images(const char* filename, sd_image_t* images, int num_images, int fps, int quality) {
|
||||
std::vector<uint8_t> webm_data = create_webm_from_sd_images_to_vector(images, num_images, fps, quality);
|
||||
int create_webm_from_sd_images(const char* filename, sd_image_t* images, int num_images, int fps, int quality, const sd_audio_t* audio) {
|
||||
std::vector<uint8_t> webm_data = create_webm_from_sd_images_to_vector(images, num_images, fps, quality, audio);
|
||||
if (webm_data.empty()) {
|
||||
return -1;
|
||||
}
|
||||
@@ -1150,7 +1285,8 @@ std::vector<uint8_t> create_video_from_sd_images_to_vector(const std::string& ou
|
||||
sd_image_t* images,
|
||||
int num_images,
|
||||
int fps,
|
||||
int quality) {
|
||||
int quality,
|
||||
const sd_audio_t* audio) {
|
||||
std::string format = output_format;
|
||||
std::transform(format.begin(), format.end(), format.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(tolower(c)); });
|
||||
@@ -1160,7 +1296,7 @@ std::vector<uint8_t> create_video_from_sd_images_to_vector(const std::string& ou
|
||||
|
||||
#ifdef SD_USE_WEBM
|
||||
if (format == "webm") {
|
||||
return create_webm_from_sd_images_to_vector(images, num_images, fps, quality);
|
||||
return create_webm_from_sd_images_to_vector(images, num_images, fps, quality, audio);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1170,14 +1306,14 @@ std::vector<uint8_t> create_video_from_sd_images_to_vector(const std::string& ou
|
||||
}
|
||||
#endif
|
||||
|
||||
return create_mjpg_avi_from_sd_images_to_vector(images, num_images, fps, quality);
|
||||
return create_mjpg_avi_from_sd_images_to_vector(images, num_images, fps, quality, audio);
|
||||
}
|
||||
|
||||
int create_video_from_sd_images(const char* filename, sd_image_t* images, int num_images, int fps, int quality) {
|
||||
int create_video_from_sd_images(const char* filename, sd_image_t* images, int num_images, int fps, int quality, const sd_audio_t* audio) {
|
||||
std::string path = filename ? filename : "";
|
||||
auto pos = path.find_last_of('.');
|
||||
std::string ext = pos == std::string::npos ? "" : path.substr(pos);
|
||||
std::vector<uint8_t> video_data = create_video_from_sd_images_to_vector(ext, images, num_images, fps, quality);
|
||||
std::vector<uint8_t> video_data = create_video_from_sd_images_to_vector(ext, images, num_images, fps, quality, audio);
|
||||
if (video_data.empty()) {
|
||||
return -1;
|
||||
}
|
||||
@@ -1187,3 +1323,54 @@ int create_video_from_sd_images(const char* filename, sd_image_t* images, int nu
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool write_wav_to_file(const std::string& path,
|
||||
const float* interleaved_samples,
|
||||
uint64_t sample_count,
|
||||
uint32_t channels,
|
||||
uint32_t sample_rate) {
|
||||
if (interleaved_samples == nullptr || sample_count == 0 || channels == 0 || sample_rate == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream file(path, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t bits_per_sample = 16;
|
||||
uint32_t bytes_per_sample = bits_per_sample / 8;
|
||||
uint32_t block_align = channels * bytes_per_sample;
|
||||
uint32_t byte_rate = sample_rate * block_align;
|
||||
uint32_t data_size = static_cast<uint32_t>(sample_count * channels * bytes_per_sample);
|
||||
uint32_t riff_size = 36 + data_size;
|
||||
|
||||
file.write("RIFF", 4);
|
||||
file.write(reinterpret_cast<const char*>(&riff_size), sizeof(riff_size));
|
||||
file.write("WAVE", 4);
|
||||
file.write("fmt ", 4);
|
||||
|
||||
uint32_t fmt_size = 16;
|
||||
uint16_t audio_format = 1;
|
||||
uint16_t wav_channels = static_cast<uint16_t>(channels);
|
||||
uint16_t wav_block_align = static_cast<uint16_t>(block_align);
|
||||
uint16_t wav_bits_per_sample = static_cast<uint16_t>(bits_per_sample);
|
||||
file.write(reinterpret_cast<const char*>(&fmt_size), sizeof(fmt_size));
|
||||
file.write(reinterpret_cast<const char*>(&audio_format), sizeof(audio_format));
|
||||
file.write(reinterpret_cast<const char*>(&wav_channels), sizeof(wav_channels));
|
||||
file.write(reinterpret_cast<const char*>(&sample_rate), sizeof(sample_rate));
|
||||
file.write(reinterpret_cast<const char*>(&byte_rate), sizeof(byte_rate));
|
||||
file.write(reinterpret_cast<const char*>(&wav_block_align), sizeof(wav_block_align));
|
||||
file.write(reinterpret_cast<const char*>(&wav_bits_per_sample), sizeof(wav_bits_per_sample));
|
||||
|
||||
file.write("data", 4);
|
||||
file.write(reinterpret_cast<const char*>(&data_size), sizeof(data_size));
|
||||
|
||||
std::vector<int16_t> pcm(sample_count * channels);
|
||||
for (size_t i = 0; i < pcm.size(); ++i) {
|
||||
float sample = std::max(-1.0f, std::min(1.0f, interleaved_samples[i]));
|
||||
pcm[i] = static_cast<int16_t>(std::lrint(sample * 32767.0f));
|
||||
}
|
||||
file.write(reinterpret_cast<const char*>(pcm.data()), static_cast<std::streamsize>(pcm.size() * sizeof(int16_t)));
|
||||
return file.good();
|
||||
}
|
||||
|
||||
@@ -57,11 +57,13 @@ int create_mjpg_avi_from_sd_images(const char* filename,
|
||||
sd_image_t* images,
|
||||
int num_images,
|
||||
int fps,
|
||||
int quality = 90);
|
||||
int quality = 90,
|
||||
const sd_audio_t* audio = nullptr);
|
||||
std::vector<uint8_t> create_mjpg_avi_from_sd_images_to_vector(sd_image_t* images,
|
||||
int num_images,
|
||||
int fps,
|
||||
int quality = 90);
|
||||
int quality = 90,
|
||||
const sd_audio_t* audio = nullptr);
|
||||
|
||||
#ifdef SD_USE_WEBP
|
||||
int create_animated_webp_from_sd_images(const char* filename,
|
||||
@@ -80,22 +82,32 @@ int create_webm_from_sd_images(const char* filename,
|
||||
sd_image_t* images,
|
||||
int num_images,
|
||||
int fps,
|
||||
int quality = 90);
|
||||
int quality = 90,
|
||||
const sd_audio_t* audio = nullptr);
|
||||
std::vector<uint8_t> create_webm_from_sd_images_to_vector(sd_image_t* images,
|
||||
int num_images,
|
||||
int fps,
|
||||
int quality = 90);
|
||||
int quality = 90,
|
||||
const sd_audio_t* audio = nullptr);
|
||||
#endif
|
||||
|
||||
int create_video_from_sd_images(const char* filename,
|
||||
sd_image_t* images,
|
||||
int num_images,
|
||||
int fps,
|
||||
int quality = 90);
|
||||
int quality = 90,
|
||||
const sd_audio_t* audio = nullptr);
|
||||
std::vector<uint8_t> create_video_from_sd_images_to_vector(const std::string& output_format,
|
||||
sd_image_t* images,
|
||||
int num_images,
|
||||
int fps,
|
||||
int quality = 90);
|
||||
int quality = 90,
|
||||
const sd_audio_t* audio = nullptr);
|
||||
|
||||
bool write_wav_to_file(const std::string& path,
|
||||
const float* interleaved_samples,
|
||||
uint64_t sample_count,
|
||||
uint32_t channels,
|
||||
uint32_t sample_rate);
|
||||
|
||||
#endif // __MEDIA_IO_H__
|
||||
|
||||
@@ -74,6 +74,9 @@ add_executable(${TARGET}
|
||||
routes_sdapi.cpp
|
||||
routes_sdcpp.cpp
|
||||
)
|
||||
if(APPLE)
|
||||
sd_set_macos_rpaths(${TARGET})
|
||||
endif()
|
||||
|
||||
if(HAVE_FRONTEND_BUILD)
|
||||
add_dependencies(${TARGET} ${TARGET}_frontend)
|
||||
|
||||
@@ -117,176 +117,10 @@ In this case, the server will load and serve the specified `index.html` file ins
|
||||
* using a custom UI
|
||||
* avoiding rebuilding the binary after frontend modifications
|
||||
|
||||
# Run
|
||||
# Usage
|
||||
|
||||
```
|
||||
usage: ./bin/sd-server [options]
|
||||
For detailed command-line arguments, run:
|
||||
|
||||
Svr Options:
|
||||
-l, --listen-ip <string> server listen ip (default: 127.0.0.1)
|
||||
--serve-html-path <string> path to HTML file to serve at root (optional)
|
||||
--listen-port <int> server listen port (default: 1234)
|
||||
-v, --verbose print extra info
|
||||
--color colors the logging tags according to level
|
||||
-h, --help show this help message and exit
|
||||
|
||||
Context Options:
|
||||
-m, --model <string> path to full model
|
||||
--clip_l <string> path to the clip-l text encoder
|
||||
--clip_g <string> path to the clip-g text encoder
|
||||
--clip_vision <string> path to the clip-vision encoder
|
||||
--t5xxl <string> path to the t5xxl text encoder
|
||||
--llm <string> path to the llm text encoder. For example: (qwenvl2.5 for qwen-image,
|
||||
mistral-small3.2 for flux2, ...)
|
||||
--llm_vision <string> path to the llm vit
|
||||
--qwen2vl <string> alias of --llm. Deprecated.
|
||||
--qwen2vl_vision <string> alias of --llm_vision. Deprecated.
|
||||
--diffusion-model <string> path to the standalone diffusion model
|
||||
--high-noise-diffusion-model <string> path to the standalone high noise diffusion model
|
||||
--vae <string> path to standalone vae model
|
||||
--taesd <string> path to taesd. Using Tiny AutoEncoder for fast decoding (low quality)
|
||||
--tae <string> alias of --taesd
|
||||
--control-net <string> path to control net model
|
||||
--embd-dir <string> embeddings directory
|
||||
--lora-model-dir <string> lora model directory
|
||||
--hires-upscalers-dir <string> highres fix upscaler model directory
|
||||
--tensor-type-rules <string> weight type per tensor pattern (example: "^vae\.=f16,model\.=q8_0")
|
||||
--photo-maker <string> path to PHOTOMAKER model
|
||||
--upscale-model <string> path to esrgan model.
|
||||
-t, --threads <int> number of threads to use during computation (default: -1). If threads <= 0,
|
||||
then threads will be set to the number of CPU physical cores
|
||||
--chroma-t5-mask-pad <int> t5 mask pad size of chroma
|
||||
--max-vram <float> maximum VRAM budget in GiB for graph-cut segmented execution. 0 disables
|
||||
graph splitting
|
||||
--force-sdxl-vae-conv-scale force use of conv scale on sdxl vae
|
||||
--offload-to-cpu place the weights in RAM to save VRAM, and automatically load them into VRAM
|
||||
when needed
|
||||
--mmap whether to memory-map model
|
||||
--control-net-cpu keep controlnet in cpu (for low vram)
|
||||
--clip-on-cpu keep clip in cpu (for low vram)
|
||||
--vae-on-cpu keep vae in cpu (for low vram)
|
||||
--fa use flash attention
|
||||
--diffusion-fa use flash attention in the diffusion model only
|
||||
--diffusion-conv-direct use ggml_conv2d_direct in the diffusion model
|
||||
--vae-conv-direct use ggml_conv2d_direct in the vae model
|
||||
--circular enable circular padding for convolutions
|
||||
--circularx enable circular RoPE wrapping on x-axis (width) only
|
||||
--circulary enable circular RoPE wrapping on y-axis (height) only
|
||||
--chroma-disable-dit-mask disable dit mask for chroma
|
||||
--qwen-image-zero-cond-t enable zero_cond_t for qwen image
|
||||
--chroma-enable-t5-mask enable t5 mask for chroma
|
||||
--type weight type (examples: f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_K, q3_K,
|
||||
q4_K). If not specified, the default is the type of the weight file
|
||||
--rng RNG, one of [std_default, cuda, cpu], default: cuda(sd-webui), cpu(comfyui)
|
||||
--sampler-rng sampler RNG, one of [std_default, cuda, cpu]. If not specified, use --rng
|
||||
--prediction prediction type override, one of [eps, v, edm_v, sd3_flow, flux_flow,
|
||||
flux2_flow]
|
||||
--lora-apply-mode the way to apply LoRA, one of [auto, immediately, at_runtime], default is
|
||||
auto. In auto mode, if the model weights contain any quantized parameters,
|
||||
the at_runtime mode will be used; otherwise, immediately will be used.The
|
||||
immediately mode may have precision and compatibility issues with quantized
|
||||
parameters, but it usually offers faster inference speed and, in some cases,
|
||||
lower memory usage. The at_runtime mode, on the other hand, is exactly the
|
||||
opposite.
|
||||
|
||||
Default Generation Options:
|
||||
-p, --prompt <string> the prompt to render
|
||||
-n, --negative-prompt <string> the negative prompt (default: "")
|
||||
-i, --init-img <string> path to the init image
|
||||
--end-img <string> path to the end image, required by flf2v
|
||||
--mask <string> path to the mask image
|
||||
--control-image <string> path to control image, control net
|
||||
--control-video <string> path to control video frames, It must be a directory path. The video frames
|
||||
inside should be stored as images in lexicographical (character) order. For
|
||||
example, if the control video path is `frames`, the directory contain images
|
||||
such as 00.png, 01.png, ... etc.
|
||||
--pm-id-images-dir <string> path to PHOTOMAKER input id images dir
|
||||
--pm-id-embed-path <string> path to PHOTOMAKER v2 id embed
|
||||
--hires-upscaler <string> highres fix upscaler, Lanczos, Nearest, Latent, Latent (nearest), Latent
|
||||
(nearest-exact), Latent (antialiased), Latent (bicubic), Latent (bicubic
|
||||
antialiased), or a model name under --hires-upscalers-dir (default: Latent)
|
||||
-H, --height <int> image height, in pixel space (default: 512)
|
||||
-W, --width <int> image width, in pixel space (default: 512)
|
||||
--steps <int> number of sample steps (default: 20)
|
||||
--high-noise-steps <int> (high noise) number of sample steps (default: -1 = auto)
|
||||
--clip-skip <int> ignore last layers of CLIP network; 1 ignores none, 2 ignores one layer
|
||||
(default: -1). <= 0 represents unspecified, will be 1 for SD1.x, 2 for SD2.x
|
||||
-b, --batch-count <int> batch count
|
||||
--video-frames <int> video frames (default: 1)
|
||||
--fps <int> fps (default: 24)
|
||||
--timestep-shift <int> shift timestep for NitroFusion models (default: 0). recommended N for
|
||||
NitroSD-Realism around 250 and 500 for NitroSD-Vibrant
|
||||
--upscale-repeats <int> Run the ESRGAN upscaler this many times (default: 1)
|
||||
--upscale-tile-size <int> tile size for ESRGAN upscaling (default: 128)
|
||||
--hires-width <int> highres fix target width, 0 to use --hires-scale (default: 0)
|
||||
--hires-height <int> highres fix target height, 0 to use --hires-scale (default: 0)
|
||||
--hires-steps <int> highres fix second pass sample steps, 0 to reuse --steps (default: 0)
|
||||
--hires-upscale-tile-size <int> highres fix upscaler tile size, reserved for model-backed upscalers (default:
|
||||
128)
|
||||
--cfg-scale <float> unconditional guidance scale: (default: 7.0)
|
||||
--img-cfg-scale <float> image guidance scale for inpaint or instruct-pix2pix models: (default: same
|
||||
as --cfg-scale)
|
||||
--guidance <float> distilled guidance scale for models with guidance input (default: 3.5)
|
||||
--slg-scale <float> skip layer guidance (SLG) scale, only for DiT models: (default: 0). 0 means
|
||||
disabled, a value of 2.5 is nice for sd3.5 medium
|
||||
--skip-layer-start <float> SLG enabling point (default: 0.01)
|
||||
--skip-layer-end <float> SLG disabling point (default: 0.2)
|
||||
--eta <float> noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and
|
||||
res_2s; 1 for euler_a, er_sde and dpm++2s_a)
|
||||
--flow-shift <float> shift value for Flow models like SD3.x or WAN (default: auto)
|
||||
--high-noise-cfg-scale <float> (high noise) unconditional guidance scale: (default: 7.0)
|
||||
--high-noise-img-cfg-scale <float> (high noise) image guidance scale for inpaint or instruct-pix2pix models
|
||||
(default: same as --cfg-scale)
|
||||
--high-noise-guidance <float> (high noise) distilled guidance scale for models with guidance input
|
||||
(default: 3.5)
|
||||
--high-noise-slg-scale <float> (high noise) skip layer guidance (SLG) scale, only for DiT models: (default:
|
||||
0)
|
||||
--high-noise-skip-layer-start <float> (high noise) SLG enabling point (default: 0.01)
|
||||
--high-noise-skip-layer-end <float> (high noise) SLG disabling point (default: 0.2)
|
||||
--high-noise-eta <float> (high noise) noise multiplier (default: 0 for ddim_trailing, tcd,
|
||||
res_multistep and res_2s; 1 for euler_a, er_sde and dpm++2s_a)
|
||||
--strength <float> strength for noising/unnoising (default: 0.75)
|
||||
--pm-style-strength <float>
|
||||
--control-strength <float> strength to apply Control Net (default: 0.9). 1.0 corresponds to full
|
||||
destruction of information in init image
|
||||
--moe-boundary <float> timestep boundary for Wan2.2 MoE model. (default: 0.875). Only enabled if
|
||||
`--high-noise-steps` is set to -1
|
||||
--vace-strength <float> wan vace strength
|
||||
--vae-tile-overlap <float> tile overlap for vae tiling, in fraction of tile size (default: 0.5)
|
||||
--hires-scale <float> highres fix scale when target size is not set (default: 2.0)
|
||||
--hires-denoising-strength <float> highres fix second pass denoising strength (default: 0.7)
|
||||
--increase-ref-index automatically increase the indices of references images based on the order
|
||||
they are listed (starting with 1).
|
||||
--disable-auto-resize-ref-image disable auto resize of ref images
|
||||
--disable-image-metadata do not embed generation metadata on image files
|
||||
--vae-tiling process vae in tiles to reduce memory usage
|
||||
--hires enable highres fix
|
||||
-s, --seed RNG seed (default: 42, use random seed for < 0)
|
||||
--sampling-method sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m,
|
||||
dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep, res_2s,
|
||||
er_sde, euler_cfg_pp, euler_a_cfg_pp] (default: euler for Flux/SD3/Wan, euler_a otherwise)
|
||||
--high-noise-sampling-method (high noise) sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a,
|
||||
dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep,
|
||||
res_2s, er_sde, euler_cfg_pp, euler_a_cfg_pp] default: euler for Flux/SD3/Wan, euler_a otherwise
|
||||
--scheduler denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits,
|
||||
smoothstep, sgm_uniform, simple, kl_optimal, lcm, bong_tangent], default:
|
||||
discrete
|
||||
--sigmas custom sigma values for the sampler, comma-separated (e.g.,
|
||||
"14.61,7.8,3.5,0.0").
|
||||
--skip-layers layers to skip for SLG steps (default: [7,8,9])
|
||||
--high-noise-skip-layers (high noise) layers to skip for SLG steps (default: [7,8,9])
|
||||
-r, --ref-image reference image for Flux Kontext models (can be used multiple times)
|
||||
--cache-mode caching method: 'easycache' (DiT), 'ucache' (UNET),
|
||||
'dbcache'/'taylorseer'/'cache-dit' (DiT block-level), 'spectrum' (UNET/DiT
|
||||
Chebyshev+Taylor forecasting)
|
||||
--cache-option named cache params (key=value format, comma-separated). easycache/ucache:
|
||||
threshold=,start=,end=,decay=,relative=,reset=; dbcache/taylorseer/cache-dit:
|
||||
Fn=,Bn=,threshold=,warmup=; spectrum: w=,m=,lam=,window=,flex=,warmup=,stop=.
|
||||
Examples: "threshold=0.25" or "threshold=1.5,reset=0"
|
||||
--scm-mask SCM steps mask for cache-dit: comma-separated 0/1 (e.g.,
|
||||
"1,1,1,0,0,1,0,0,1,0") - 1=compute, 0=can cache
|
||||
--scm-policy SCM policy: 'dynamic' (default) or 'static'
|
||||
--vae-tile-size tile size for vae tiling, format [X]x[Y] (default: 32x32)
|
||||
--vae-relative-tile-size relative tile size for vae tiling, format [X]x[Y], in fraction of image size
|
||||
if < 1, in number of tiles per dim if >=1 (overrides --vae-tile-size)
|
||||
```bash
|
||||
./bin/sd-server -h
|
||||
```
|
||||
|
||||
@@ -504,11 +504,13 @@ Shared default fields used by both `img_gen` and `vid_gen`:
|
||||
| `sample_params.guidance.slg.scale` | `number` |
|
||||
| `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.target_overlap` | `number` |
|
||||
| `vae_tiling_params.rel_size_x` | `number` |
|
||||
| `vae_tiling_params.rel_size_y` | `number` |
|
||||
| `vae_tiling_params.extra_tiling_args` | `string` |
|
||||
| `cache_mode` | `string` |
|
||||
| `cache_option` | `string` |
|
||||
| `scm_mask` | `string` |
|
||||
@@ -516,6 +518,8 @@ 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. For LTX video VAE temporal tiling, `temporal_tile_frames` defaults to `4` and `temporal_tile_overlap` defaults to `1`.
|
||||
|
||||
`img_gen`-specific default fields:
|
||||
|
||||
| Field | Type |
|
||||
@@ -532,6 +536,7 @@ Shared default fields used by both `img_gen` and `vid_gen`:
|
||||
| `hires.target_height` | `integer` |
|
||||
| `hires.steps` | `integer` |
|
||||
| `hires.denoising_strength` | `number` |
|
||||
| `hires.custom_sigmas` | `array<number>` |
|
||||
| `hires.upscale_tile_size` | `integer` |
|
||||
|
||||
`vid_gen`-specific default fields:
|
||||
@@ -685,16 +690,19 @@ Example:
|
||||
"target_height": 0,
|
||||
"steps": 0,
|
||||
"denoising_strength": 0.7,
|
||||
"custom_sigmas": [],
|
||||
"upscale_tile_size": 128
|
||||
},
|
||||
|
||||
"vae_tiling_params": {
|
||||
"enabled": false,
|
||||
"temporal_tiling": false,
|
||||
"tile_size_x": 0,
|
||||
"tile_size_y": 0,
|
||||
"target_overlap": 0.5,
|
||||
"rel_size_x": 0.0,
|
||||
"rel_size_y": 0.0
|
||||
"rel_size_y": 0.0,
|
||||
"extra_tiling_args": ""
|
||||
},
|
||||
|
||||
"cache_mode": "disabled",
|
||||
@@ -799,14 +807,23 @@ Other native fields:
|
||||
| `hires.target_height` | `integer` |
|
||||
| `hires.steps` | `integer` |
|
||||
| `hires.denoising_strength` | `number` |
|
||||
| `hires.custom_sigmas` | `array<number>` |
|
||||
| `hires.upscale_tile_size` | `integer` |
|
||||
| `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.target_overlap` | `number` |
|
||||
| `vae_tiling_params.rel_size_x` | `number` |
|
||||
| `vae_tiling_params.rel_size_y` | `number` |
|
||||
| `vae_tiling_params.extra_tiling_args` | `string` |
|
||||
| `cache_mode` | `string` |
|
||||
| `cache_option` | `string` |
|
||||
| `scm_mask` | `string` |
|
||||
| `scm_policy_dynamic` | `boolean` |
|
||||
|
||||
For `hires.upscaler`, use `Lanczos`, `Nearest`, `Latent`, `Latent (nearest)`, `Latent (nearest-exact)`, `Latent (antialiased)`, `Latent (bicubic)`, `Latent (bicubic antialiased)`, or an `upscalers[].name` value from `GET /sdcpp/v1/capabilities`. Model-backed upscalers are resolved as `--hires-upscalers-dir / (name + ext)` and must live directly in that directory.
|
||||
For `hires.upscaler`, use `Lanczos`, `Nearest`, `Latent`, `Latent (nearest)`, `Latent (nearest-exact)`, `Latent (antialiased)`, `Latent (bicubic)`, `Latent (bicubic antialiased)`, or an `upscalers[].name` value from `GET /sdcpp/v1/capabilities`. Model-backed upscalers are resolved as `--hires-upscalers-dir / (name + ext)` and must live directly in that directory. `hires.custom_sigmas`, when present, overrides the generated second-pass hires sigma schedule; otherwise the hires schedule is trimmed by `hires.denoising_strength`.
|
||||
|
||||
HTTP-only output fields:
|
||||
|
||||
@@ -1009,11 +1026,13 @@ Example:
|
||||
|
||||
"vae_tiling_params": {
|
||||
"enabled": false,
|
||||
"temporal_tiling": false,
|
||||
"tile_size_x": 0,
|
||||
"tile_size_y": 0,
|
||||
"target_overlap": 0.5,
|
||||
"rel_size_x": 0.0,
|
||||
"rel_size_y": 0.0
|
||||
"rel_size_y": 0.0,
|
||||
"extra_tiling_args": ""
|
||||
},
|
||||
|
||||
"cache_mode": "disabled",
|
||||
@@ -1131,6 +1150,14 @@ Other native fields:
|
||||
| Field | Type |
|
||||
| --- | --- |
|
||||
| `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.target_overlap` | `number` |
|
||||
| `vae_tiling_params.rel_size_x` | `number` |
|
||||
| `vae_tiling_params.rel_size_y` | `number` |
|
||||
| `vae_tiling_params.extra_tiling_args` | `string` |
|
||||
| `cache_mode` | `string` |
|
||||
| `cache_option` | `string` |
|
||||
| `scm_mask` | `string` |
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "async_jobs.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
@@ -173,8 +174,13 @@ bool execute_img_gen_job(ServerRuntime& runtime,
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
|
||||
sd_image_t* raw_results = generate_image(runtime.sd_ctx, ¶ms);
|
||||
results.adopt(raw_results, params.batch_count);
|
||||
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;
|
||||
}
|
||||
results.adopt(raw_results, num_results);
|
||||
}
|
||||
|
||||
const int num_results = results.count();
|
||||
@@ -190,6 +196,8 @@ 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;
|
||||
@@ -198,7 +206,7 @@ bool execute_img_gen_job(ServerRuntime& runtime,
|
||||
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)
|
||||
job.img_gen.gen_params.seed + i / images_per_batch)
|
||||
: "";
|
||||
auto image_bytes = encode_image_to_vector(encoded_format,
|
||||
results[i].data,
|
||||
@@ -231,16 +239,21 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
sd_vid_gen_params_t params = job.vid_gen.to_sd_vid_gen_params_t();
|
||||
|
||||
SDImageVec results;
|
||||
int num_results = 0;
|
||||
int num_results = 0;
|
||||
sd_audio_t* generated_audio = nullptr;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
|
||||
sd_image_t* raw_results = generate_video(runtime.sd_ctx, ¶ms, &num_results);
|
||||
sd_image_t* raw_results = nullptr;
|
||||
if (!generate_video(runtime.sd_ctx, ¶ms, &raw_results, &num_results, &generated_audio)) {
|
||||
raw_results = nullptr;
|
||||
}
|
||||
results.adopt(raw_results, num_results);
|
||||
}
|
||||
|
||||
num_results = results.count();
|
||||
if (num_results <= 0) {
|
||||
free_sd_audio(generated_audio);
|
||||
error_message = "generate_video returned no results";
|
||||
return false;
|
||||
}
|
||||
@@ -249,7 +262,9 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
results.data(),
|
||||
num_results,
|
||||
job.vid_gen.gen_params.fps,
|
||||
job.vid_gen.output_compression);
|
||||
job.vid_gen.output_compression,
|
||||
generated_audio);
|
||||
free_sd_audio(generated_audio);
|
||||
if (video_bytes.empty()) {
|
||||
error_message = "failed to encode generated video container";
|
||||
return false;
|
||||
|
||||
@@ -85,7 +85,7 @@ int main(int argc, const char** argv) {
|
||||
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, false, false);
|
||||
sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(false);
|
||||
SDCtxPtr sd_ctx(new_sd_ctx(&sd_ctx_params));
|
||||
|
||||
if (sd_ctx == nullptr) {
|
||||
|
||||
@@ -229,8 +229,11 @@ static bool execute_sync_img_gen_request(ServerRuntime& runtime,
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
|
||||
sd_image_t* raw_results = generate_image(runtime.sd_ctx, &img_gen_params);
|
||||
num_results = request.gen_params.batch_count;
|
||||
sd_image_t* raw_results = nullptr;
|
||||
if (!generate_image(runtime.sd_ctx, &img_gen_params, &raw_results, &num_results)) {
|
||||
raw_results = nullptr;
|
||||
num_results = 0;
|
||||
}
|
||||
results.adopt(raw_results, num_results);
|
||||
}
|
||||
|
||||
@@ -281,14 +284,16 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
out["data"] = json::array();
|
||||
out["output_format"] = request.output_format;
|
||||
|
||||
for (int i = 0; i < request.gen_params.batch_count; ++i) {
|
||||
int result_count = results.count();
|
||||
int images_per_batch = request.gen_params.batch_count > 0 ? std::max(1, result_count / request.gen_params.batch_count) : 1;
|
||||
for (int i = 0; i < result_count; ++i) {
|
||||
if (results[i].data == nullptr) {
|
||||
continue;
|
||||
}
|
||||
std::string params = request.gen_params.embed_image_metadata
|
||||
? get_image_params(*runtime->ctx_params,
|
||||
request.gen_params,
|
||||
request.gen_params.seed + i)
|
||||
request.gen_params.seed + i / images_per_batch)
|
||||
: "";
|
||||
auto image_bytes = encode_image_to_vector(request.output_format == "jpeg"
|
||||
? EncodedImageFormat::JPEG
|
||||
@@ -353,14 +358,16 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
out["data"] = json::array();
|
||||
out["output_format"] = request.output_format;
|
||||
|
||||
for (int i = 0; i < request.gen_params.batch_count; ++i) {
|
||||
int result_count = results.count();
|
||||
int images_per_batch = request.gen_params.batch_count > 0 ? std::max(1, result_count / request.gen_params.batch_count) : 1;
|
||||
for (int i = 0; i < result_count; ++i) {
|
||||
if (results[i].data == nullptr) {
|
||||
continue;
|
||||
}
|
||||
std::string params = request.gen_params.embed_image_metadata
|
||||
? get_image_params(*runtime->ctx_params,
|
||||
request.gen_params,
|
||||
request.gen_params.seed + i)
|
||||
request.gen_params.seed + i / images_per_batch)
|
||||
: "";
|
||||
auto image_bytes = encode_image_to_vector(request.output_format == "jpeg" ? EncodedImageFormat::JPEG : EncodedImageFormat::PNG,
|
||||
results[i].data,
|
||||
|
||||
@@ -63,6 +63,10 @@ static enum sample_method_t get_sdapi_sample_method(std::string name) {
|
||||
{"ddim", DDIM_TRAILING_SAMPLE_METHOD},
|
||||
{"dpm++ 2m", DPMPP2M_SAMPLE_METHOD},
|
||||
{"k_dpmpp_2m", DPMPP2M_SAMPLE_METHOD},
|
||||
{"dpm++ 2m sde", DPMPP2M_SDE_SAMPLE_METHOD},
|
||||
{"k_dpmpp_2m_sde", DPMPP2M_SDE_SAMPLE_METHOD},
|
||||
{"dpm++ 2m sde gpu", DPMPP2M_SDE_BT_SAMPLE_METHOD},
|
||||
{"k_dpmpp_2m_sde_gpu", DPMPP2M_SDE_BT_SAMPLE_METHOD},
|
||||
{"res multistep", RES_MULTISTEP_SAMPLE_METHOD},
|
||||
{"k_res_multistep", RES_MULTISTEP_SAMPLE_METHOD},
|
||||
{"res 2s", RES_2S_SAMPLE_METHOD},
|
||||
@@ -259,6 +263,48 @@ static bool build_sdapi_img_gen_request(const json& j,
|
||||
return true;
|
||||
}
|
||||
|
||||
static nlohmann::json prepare_info_field(const SDContextParams& ctx_params,
|
||||
const SDGenerationParams& gen_params,
|
||||
bool img2img) {
|
||||
nlohmann::json jsoninfo = nlohmann::json::object();
|
||||
jsoninfo["prompt"] = gen_params.prompt;
|
||||
if (!gen_params.negative_prompt.empty()) {
|
||||
jsoninfo["negative_prompt"] = gen_params.negative_prompt;
|
||||
}
|
||||
jsoninfo["seed"] = gen_params.seed;
|
||||
jsoninfo["cfg_scale"] = gen_params.sample_params.guidance.txt_cfg;
|
||||
jsoninfo["width"] = gen_params.get_resolved_width();
|
||||
jsoninfo["height"] = gen_params.get_resolved_height();
|
||||
jsoninfo["steps"] = gen_params.sample_params.sample_steps;
|
||||
jsoninfo["sampler_name"] = sd_sample_method_name(gen_params.sample_params.sample_method);
|
||||
if (gen_params.clip_skip != -1) {
|
||||
jsoninfo["clip_skip"] = gen_params.clip_skip;
|
||||
}
|
||||
if (gen_params.sample_params.scheduler != scheduler_t::SCHEDULER_COUNT) {
|
||||
jsoninfo["extra_generation_params"] = nlohmann::json::object();
|
||||
jsoninfo["extra_generation_params"]["Schedule type"] = sd_scheduler_name(gen_params.sample_params.scheduler);
|
||||
}
|
||||
if (img2img) {
|
||||
jsoninfo["denoising_strength"] = gen_params.strength;
|
||||
}
|
||||
// not clear what should happen if we have both model and diffusion_model
|
||||
if (!ctx_params.diffusion_model_path.empty()) {
|
||||
jsoninfo["sd_model_name"] = sd_basename(ctx_params.diffusion_model_path);
|
||||
} else if (!ctx_params.model_path.empty()) {
|
||||
jsoninfo["sd_model_name"] = sd_basename(ctx_params.model_path);
|
||||
}
|
||||
if (!ctx_params.vae_path.empty()) {
|
||||
jsoninfo["sd_vae_name"] = sd_basename(ctx_params.vae_path);
|
||||
}
|
||||
jsoninfo["version"] = "stable-diffusion.cpp";
|
||||
|
||||
jsoninfo["infotexts"] = nlohmann::json::array();
|
||||
jsoninfo["all_prompts"] = nlohmann::json::array();
|
||||
jsoninfo["all_negative_prompts"] = nlohmann::json::array();
|
||||
jsoninfo["all_seeds"] = nlohmann::json::array();
|
||||
return jsoninfo;
|
||||
}
|
||||
|
||||
void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
ServerRuntime* runtime = &rt;
|
||||
|
||||
@@ -292,8 +338,11 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime->sd_ctx_mutex);
|
||||
sd_image_t* raw_results = generate_image(runtime->sd_ctx, &img_gen_params);
|
||||
num_results = request.gen_params.batch_count;
|
||||
sd_image_t* raw_results = nullptr;
|
||||
if (!generate_image(runtime->sd_ctx, &img_gen_params, &raw_results, &num_results)) {
|
||||
raw_results = nullptr;
|
||||
num_results = 0;
|
||||
}
|
||||
results.adopt(raw_results, num_results);
|
||||
}
|
||||
|
||||
@@ -306,24 +355,26 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
json out;
|
||||
out["images"] = json::array();
|
||||
out["parameters"] = j;
|
||||
out["info"] = "";
|
||||
json jsoninfo = prepare_info_field(*runtime->ctx_params, request.gen_params, img2img);
|
||||
|
||||
int images_per_batch = request.gen_params.batch_count > 0 ? std::max(1, num_results / request.gen_params.batch_count) : 1;
|
||||
for (int i = 0; i < num_results; ++i) {
|
||||
if (results[i].data == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string params = request.gen_params.embed_image_metadata
|
||||
? get_image_params(*runtime->ctx_params,
|
||||
request.gen_params,
|
||||
request.gen_params.seed + i)
|
||||
: "";
|
||||
auto image_bytes = encode_image_to_vector(EncodedImageFormat::PNG,
|
||||
results[i].data,
|
||||
results[i].width,
|
||||
results[i].height,
|
||||
results[i].channel,
|
||||
params);
|
||||
bool embed_meta = request.gen_params.embed_image_metadata;
|
||||
|
||||
std::string params = get_image_params(*runtime->ctx_params,
|
||||
request.gen_params,
|
||||
request.gen_params.seed + i / images_per_batch);
|
||||
|
||||
auto image_bytes = encode_image_to_vector(EncodedImageFormat::PNG,
|
||||
results[i].data,
|
||||
results[i].width,
|
||||
results[i].height,
|
||||
results[i].channel,
|
||||
embed_meta ? params : "");
|
||||
|
||||
if (image_bytes.empty()) {
|
||||
LOG_ERROR("write image to mem failed");
|
||||
@@ -331,8 +382,16 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
}
|
||||
|
||||
out["images"].push_back(base64_encode(image_bytes));
|
||||
|
||||
jsoninfo["infotexts"][i] = params;
|
||||
jsoninfo["all_seeds"][i] = request.gen_params.seed + i;
|
||||
jsoninfo["all_prompts"][i] = request.gen_params.prompt;
|
||||
jsoninfo["all_negative_prompts"][i] = request.gen_params.negative_prompt;
|
||||
}
|
||||
|
||||
// not a mistake: it is supposed to be a string in json format
|
||||
out["info"] = jsoninfo.dump();
|
||||
|
||||
res.set_content(out.dump(), "application/json");
|
||||
res.status = 200;
|
||||
|
||||
@@ -438,6 +497,9 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
scheduler_names.push_back("default");
|
||||
for (int i = 0; i < SCHEDULER_COUNT; i++) {
|
||||
scheduler_names.push_back(sd_scheduler_name((scheduler_t)i));
|
||||
if (i == DISCRETE_SCHEDULER) {
|
||||
scheduler_names.push_back("normal");
|
||||
}
|
||||
}
|
||||
json r = json::array();
|
||||
for (auto name : scheduler_names) {
|
||||
|
||||
@@ -56,11 +56,13 @@ static const char* capability_sample_method_name(enum sample_method_t sample_met
|
||||
static json make_vae_tiling_json(const sd_tiling_params_t& params) {
|
||||
return {
|
||||
{"enabled", params.enabled},
|
||||
{"temporal_tiling", params.temporal_tiling},
|
||||
{"tile_size_x", params.tile_size_x},
|
||||
{"tile_size_y", params.tile_size_y},
|
||||
{"target_overlap", params.target_overlap},
|
||||
{"rel_size_x", params.rel_size_x},
|
||||
{"rel_size_y", params.rel_size_y},
|
||||
{"extra_tiling_args", params.extra_tiling_args ? params.extra_tiling_args : ""},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -100,6 +102,20 @@ static json make_sample_params_json(const sd_sample_params_t& sample_params, con
|
||||
};
|
||||
}
|
||||
|
||||
static json make_hires_json(const SDGenerationParams& defaults) {
|
||||
return {
|
||||
{"enabled", defaults.hires_enabled},
|
||||
{"upscaler", defaults.hires_upscaler},
|
||||
{"scale", defaults.hires_scale},
|
||||
{"target_width", defaults.hires_width},
|
||||
{"target_height", defaults.hires_height},
|
||||
{"steps", defaults.hires_steps},
|
||||
{"denoising_strength", defaults.hires_denoising_strength},
|
||||
{"custom_sigmas", defaults.hires_custom_sigmas},
|
||||
{"upscale_tile_size", defaults.hires_upscale_tile_size},
|
||||
};
|
||||
}
|
||||
|
||||
static json make_img_gen_defaults_json(const SDGenerationParams& defaults, const std::string& output_format) {
|
||||
return {
|
||||
{"prompt", defaults.prompt},
|
||||
@@ -110,21 +126,12 @@ static json make_img_gen_defaults_json(const SDGenerationParams& defaults, const
|
||||
{"strength", defaults.strength},
|
||||
{"seed", defaults.seed},
|
||||
{"batch_count", defaults.batch_count},
|
||||
{"qwen_image_layers", defaults.qwen_image_layers},
|
||||
{"auto_resize_ref_image", defaults.auto_resize_ref_image},
|
||||
{"increase_ref_index", defaults.increase_ref_index},
|
||||
{"control_strength", defaults.control_strength},
|
||||
{"sample_params", make_sample_params_json(defaults.sample_params, defaults.skip_layers)},
|
||||
{"hires",
|
||||
{
|
||||
{"enabled", defaults.hires_enabled},
|
||||
{"upscaler", defaults.hires_upscaler},
|
||||
{"scale", defaults.hires_scale},
|
||||
{"target_width", defaults.hires_width},
|
||||
{"target_height", defaults.hires_height},
|
||||
{"steps", defaults.hires_steps},
|
||||
{"denoising_strength", defaults.hires_denoising_strength},
|
||||
{"upscale_tile_size", defaults.hires_upscale_tile_size},
|
||||
}},
|
||||
{"hires", make_hires_json(defaults)},
|
||||
{"vae_tiling_params", make_vae_tiling_json(defaults.vae_tiling_params)},
|
||||
{"cache_mode", defaults.cache_mode},
|
||||
{"cache_option", defaults.cache_option},
|
||||
@@ -150,6 +157,7 @@ static json make_vid_gen_defaults_json(const SDGenerationParams& defaults, const
|
||||
{"vace_strength", defaults.vace_strength},
|
||||
{"sample_params", make_sample_params_json(defaults.sample_params, defaults.skip_layers)},
|
||||
{"high_noise_sample_params", make_sample_params_json(defaults.high_noise_sample_params, defaults.high_noise_skip_layers)},
|
||||
{"hires", make_hires_json(defaults)},
|
||||
{"vae_tiling_params", make_vae_tiling_json(defaults.vae_tiling_params)},
|
||||
{"cache_mode", defaults.cache_mode},
|
||||
{"cache_option", defaults.cache_option},
|
||||
@@ -212,6 +220,9 @@ static json make_capabilities_json(ServerRuntime& runtime) {
|
||||
|
||||
for (int i = 0; i < SCHEDULER_COUNT; ++i) {
|
||||
schedulers.push_back(sd_scheduler_name((scheduler_t)i));
|
||||
if (i == DISCRETE_SCHEDULER) {
|
||||
schedulers.push_back("normal");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -190,8 +190,8 @@ ArgOptions SDSvrParams::get_options() {
|
||||
ArgOptions options;
|
||||
|
||||
options.string_options = {
|
||||
{"-l", "--listen-ip", "server listen ip (default: 127.0.0.1)", &listen_ip},
|
||||
{"", "--serve-html-path", "path to HTML file to serve at root (optional)", &serve_html_path},
|
||||
{"-l", "--listen-ip", "server listen ip (default: 127.0.0.1)", 0, &listen_ip},
|
||||
{"", "--serve-html-path", "path to HTML file to serve at root (optional)", 0, &serve_html_path},
|
||||
};
|
||||
|
||||
options.int_options = {
|
||||
@@ -203,8 +203,9 @@ ArgOptions SDSvrParams::get_options() {
|
||||
{"", "--color", "colors the logging tags according to level", true, &color},
|
||||
};
|
||||
|
||||
auto on_help_arg = [&](int, const char**, int) {
|
||||
auto on_help_arg = [&](int, const char**, int, bool& valid) {
|
||||
normal_exit = true;
|
||||
valid = true;
|
||||
return -1;
|
||||
};
|
||||
|
||||
@@ -254,7 +255,7 @@ void refresh_lora_cache(ServerRuntime& rt) {
|
||||
|
||||
fs::path lora_dir = rt.ctx_params->lora_model_dir;
|
||||
if (fs::exists(lora_dir) && fs::is_directory(lora_dir)) {
|
||||
for (auto& entry : fs::recursive_directory_iterator(lora_dir)) {
|
||||
for (auto& entry : fs::recursive_directory_iterator(lora_dir, fs::directory_options::skip_permission_denied)) {
|
||||
if (!entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
for f in src/*.cpp src/*.h src/*.hpp src/tokenizers/*.h src/tokenizers/*.cpp src/tokenizers/vocab/*.h src/tokenizers/vocab/*.cpp \
|
||||
src/model_io/*.h src/model_io/*.cpp examples/cli/*.cpp examples/cli/*.h examples/server/*.cpp \
|
||||
examples/common/*.hpp examples/common/*.h examples/common/*.cpp; do
|
||||
[[ "$f" == vocab* ]] && continue
|
||||
echo "formatting '$f'"
|
||||
# if [ "$f" != "stable-diffusion.h" ]; then
|
||||
# clang-tidy -fix -p build_linux/ "$f"
|
||||
# fi
|
||||
clang-format -style=file -i "$f"
|
||||
done
|
||||
@@ -53,6 +53,9 @@ enum sample_method_t {
|
||||
ER_SDE_SAMPLE_METHOD,
|
||||
EULER_CFG_PP_SAMPLE_METHOD,
|
||||
EULER_A_CFG_PP_SAMPLE_METHOD,
|
||||
EULER_GE_SAMPLE_METHOD,
|
||||
DPMPP2M_SDE_SAMPLE_METHOD,
|
||||
DPMPP2M_SDE_BT_SAMPLE_METHOD,
|
||||
SAMPLE_METHOD_COUNT
|
||||
};
|
||||
|
||||
@@ -68,6 +71,11 @@ enum scheduler_t {
|
||||
KL_OPTIMAL_SCHEDULER,
|
||||
LCM_SCHEDULER,
|
||||
BONG_TANGENT_SCHEDULER,
|
||||
LTX2_SCHEDULER,
|
||||
LOGIT_NORMAL_SCHEDULER,
|
||||
FLUX2_SCHEDULER,
|
||||
FLUX_SCHEDULER,
|
||||
BETA_SCHEDULER,
|
||||
SCHEDULER_COUNT
|
||||
};
|
||||
|
||||
@@ -77,7 +85,8 @@ enum prediction_t {
|
||||
EDM_V_PRED,
|
||||
FLOW_PRED,
|
||||
FLUX_FLOW_PRED,
|
||||
FLUX2_FLOW_PRED,
|
||||
SEFI_FLOW_PRED,
|
||||
MINIT2I_FLOW_PRED,
|
||||
PREDICTION_COUNT
|
||||
};
|
||||
|
||||
@@ -124,7 +133,8 @@ enum sd_type_t {
|
||||
// SD_TYPE_IQ4_NL_8_8 = 38,
|
||||
SD_TYPE_MXFP4 = 39, // MXFP4 (1 block)
|
||||
SD_TYPE_NVFP4 = 40, // NVFP4 (4 blocks, E4M3 scale)
|
||||
SD_TYPE_COUNT = 41,
|
||||
SD_TYPE_Q1_0 = 41,
|
||||
SD_TYPE_COUNT = 42,
|
||||
};
|
||||
|
||||
enum sd_log_level_t {
|
||||
@@ -151,11 +161,13 @@ enum lora_apply_mode_t {
|
||||
|
||||
typedef struct {
|
||||
bool enabled;
|
||||
bool temporal_tiling;
|
||||
int tile_size_x;
|
||||
int tile_size_y;
|
||||
float target_overlap;
|
||||
float rel_size_x;
|
||||
float rel_size_y;
|
||||
const char* extra_tiling_args;
|
||||
} sd_tiling_params_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -163,6 +175,14 @@ typedef struct {
|
||||
const char* path;
|
||||
} sd_embedding_t;
|
||||
|
||||
enum sd_vae_format_t {
|
||||
SD_VAE_FORMAT_AUTO = -1,
|
||||
SD_VAE_FORMAT_FLUX,
|
||||
SD_VAE_FORMAT_SD3,
|
||||
SD_VAE_FORMAT_FLUX2,
|
||||
SD_VAE_FORMAT_COUNT,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char* model_path;
|
||||
const char* clip_l_path;
|
||||
@@ -173,41 +193,52 @@ typedef struct {
|
||||
const char* llm_vision_path;
|
||||
const char* diffusion_model_path;
|
||||
const char* high_noise_diffusion_model_path;
|
||||
const char* uncond_diffusion_model_path;
|
||||
const char* embeddings_connectors_path;
|
||||
const char* vae_path;
|
||||
const char* audio_vae_path;
|
||||
const char* taesd_path;
|
||||
const char* control_net_path;
|
||||
const sd_embedding_t* embeddings;
|
||||
uint32_t embedding_count;
|
||||
const char* photo_maker_path;
|
||||
const char* pulid_weights_path;
|
||||
const char* tensor_type_rules;
|
||||
bool vae_decode_only;
|
||||
bool free_params_immediately;
|
||||
int n_threads;
|
||||
enum sd_type_t wtype;
|
||||
enum rng_type_t rng_type;
|
||||
enum rng_type_t sampler_rng_type;
|
||||
enum prediction_t prediction;
|
||||
enum lora_apply_mode_t lora_apply_mode;
|
||||
bool offload_params_to_cpu;
|
||||
bool enable_mmap;
|
||||
bool keep_clip_on_cpu;
|
||||
bool keep_control_net_on_cpu;
|
||||
bool keep_vae_on_cpu;
|
||||
bool flash_attn;
|
||||
bool diffusion_flash_attn;
|
||||
bool tae_preview_only;
|
||||
bool diffusion_conv_direct;
|
||||
bool vae_conv_direct;
|
||||
bool circular_x;
|
||||
bool circular_y;
|
||||
bool force_sdxl_vae_conv_scale;
|
||||
bool chroma_use_dit_mask;
|
||||
bool chroma_use_t5_mask;
|
||||
int chroma_t5_mask_pad;
|
||||
bool qwen_image_zero_cond_t;
|
||||
float max_vram;
|
||||
enum sd_vae_format_t vae_format;
|
||||
const char* max_vram; // GiB budget or backend assignment spec for graph-cut segmented param offload (0 = disabled, -1 = auto)
|
||||
bool stream_layers; // Enable residency+prefetch streaming on top of --max-vram (no effect without --max-vram)
|
||||
bool eager_load; // Load all params into the params backend at model-load time instead of lazily on first use
|
||||
const char* backend;
|
||||
const char* params_backend;
|
||||
const char* split_mode; // weight distribution for multi-device modules: layer (default) or row, or per-module assignments e.g. "diffusion=row"
|
||||
bool auto_fit;
|
||||
const char* rpc_servers;
|
||||
} sd_ctx_params_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t sample_rate;
|
||||
uint32_t channels;
|
||||
uint64_t sample_count;
|
||||
float* data;
|
||||
} sd_audio_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
@@ -240,6 +271,7 @@ typedef struct {
|
||||
float* custom_sigmas;
|
||||
int custom_sigmas_count;
|
||||
float flow_shift;
|
||||
const char* extra_sample_args;
|
||||
} sd_sample_params_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -249,6 +281,11 @@ typedef struct {
|
||||
float style_strength;
|
||||
} sd_pm_params_t; // photo maker
|
||||
|
||||
typedef struct {
|
||||
const char* id_embedding_path;
|
||||
float id_weight;
|
||||
} sd_pulid_params_t;
|
||||
|
||||
enum sd_cache_mode_t {
|
||||
SD_CACHE_DISABLED = 0,
|
||||
SD_CACHE_EASYCACHE,
|
||||
@@ -316,6 +353,8 @@ typedef struct {
|
||||
int steps;
|
||||
float denoising_strength;
|
||||
int upscale_tile_size;
|
||||
float* custom_sigmas;
|
||||
int custom_sigmas_count;
|
||||
} sd_hires_params_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -339,9 +378,13 @@ typedef struct {
|
||||
sd_image_t control_image;
|
||||
float control_strength;
|
||||
sd_pm_params_t pm_params;
|
||||
sd_pulid_params_t pulid_params;
|
||||
sd_tiling_params_t vae_tiling_params;
|
||||
sd_cache_params_t cache;
|
||||
sd_hires_params_t hires;
|
||||
int qwen_image_layers;
|
||||
bool circular_x;
|
||||
bool circular_y;
|
||||
} sd_img_gen_params_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -362,20 +405,27 @@ typedef struct {
|
||||
float strength;
|
||||
int64_t seed;
|
||||
int video_frames;
|
||||
int fps;
|
||||
float vace_strength;
|
||||
sd_tiling_params_t vae_tiling_params;
|
||||
sd_cache_params_t cache;
|
||||
sd_hires_params_t hires;
|
||||
bool circular_x;
|
||||
bool circular_y;
|
||||
} sd_vid_gen_params_t;
|
||||
|
||||
typedef struct sd_ctx_t sd_ctx_t;
|
||||
struct ggml_tensor;
|
||||
|
||||
typedef void (*sd_log_cb_t)(enum sd_log_level_t level, const char* text, void* data);
|
||||
typedef void (*sd_progress_cb_t)(int step, int steps, float time, void* data);
|
||||
typedef void (*sd_preview_cb_t)(int step, int frame_count, sd_image_t* frames, bool is_noisy, void* data);
|
||||
typedef bool (*sd_graph_eval_callback_t)(struct ggml_tensor* t, bool ask, void* user_data);
|
||||
|
||||
SD_API void sd_set_log_callback(sd_log_cb_t sd_log_cb, void* data);
|
||||
SD_API void sd_set_progress_callback(sd_progress_cb_t cb, void* data);
|
||||
SD_API void sd_set_preview_callback(sd_preview_cb_t cb, enum preview_t mode, int interval, bool denoised, bool noisy, void* data);
|
||||
SD_API void sd_set_backend_eval_callback(sd_graph_eval_callback_t cb, void* data);
|
||||
SD_API int32_t sd_get_num_physical_cores();
|
||||
SD_API const char* sd_get_system_info();
|
||||
SD_API bool sd_ctx_supports_image_generation(const sd_ctx_t* sd_ctx);
|
||||
@@ -406,6 +456,7 @@ SD_API char* sd_ctx_params_to_str(const sd_ctx_params_t* sd_ctx_params);
|
||||
|
||||
SD_API sd_ctx_t* new_sd_ctx(const sd_ctx_params_t* sd_ctx_params);
|
||||
SD_API void free_sd_ctx(sd_ctx_t* sd_ctx);
|
||||
SD_API void free_sd_audio(sd_audio_t* audio);
|
||||
|
||||
SD_API void sd_sample_params_init(sd_sample_params_t* sample_params);
|
||||
SD_API char* sd_sample_params_to_str(const sd_sample_params_t* sample_params);
|
||||
@@ -415,23 +466,44 @@ SD_API enum scheduler_t sd_get_default_scheduler(const sd_ctx_t* sd_ctx, enum sa
|
||||
|
||||
SD_API void sd_img_gen_params_init(sd_img_gen_params_t* sd_img_gen_params);
|
||||
SD_API char* sd_img_gen_params_to_str(const sd_img_gen_params_t* sd_img_gen_params);
|
||||
SD_API sd_image_t* generate_image(sd_ctx_t* sd_ctx, const sd_img_gen_params_t* sd_img_gen_params);
|
||||
SD_API bool generate_image(sd_ctx_t* sd_ctx,
|
||||
const sd_img_gen_params_t* sd_img_gen_params,
|
||||
sd_image_t** images_out,
|
||||
int* num_images_out);
|
||||
|
||||
enum sd_cancel_mode_t {
|
||||
// Stop the current generation as soon as possible.
|
||||
SD_CANCEL_ALL,
|
||||
// Finish the current image sample, then skip additional batch latents and return completed images.
|
||||
SD_CANCEL_NEW_LATENTS,
|
||||
// Clear a pending cancellation request.
|
||||
SD_CANCEL_RESET
|
||||
};
|
||||
|
||||
SD_API void sd_cancel_generation(sd_ctx_t* sd_ctx, enum sd_cancel_mode_t mode);
|
||||
|
||||
SD_API void sd_vid_gen_params_init(sd_vid_gen_params_t* sd_vid_gen_params);
|
||||
SD_API sd_image_t* generate_video(sd_ctx_t* sd_ctx, const sd_vid_gen_params_t* sd_vid_gen_params, int* num_frames_out);
|
||||
SD_API bool generate_video(sd_ctx_t* sd_ctx,
|
||||
const sd_vid_gen_params_t* sd_vid_gen_params,
|
||||
sd_image_t** frames_out,
|
||||
int* num_frames_out,
|
||||
sd_audio_t** audio_out);
|
||||
|
||||
typedef struct upscaler_ctx_t upscaler_ctx_t;
|
||||
|
||||
SD_API upscaler_ctx_t* new_upscaler_ctx(const char* esrgan_path,
|
||||
bool offload_params_to_cpu,
|
||||
bool direct,
|
||||
int n_threads,
|
||||
int tile_size);
|
||||
int tile_size,
|
||||
const char* backend,
|
||||
const char* params_backend);
|
||||
SD_API void free_upscaler_ctx(upscaler_ctx_t* upscaler_ctx);
|
||||
|
||||
SD_API sd_image_t upscale(upscaler_ctx_t* upscaler_ctx,
|
||||
sd_image_t input_image,
|
||||
uint32_t upscale_factor);
|
||||
SD_API bool upscale(upscaler_ctx_t* upscaler_ctx,
|
||||
sd_image_t input_image,
|
||||
uint32_t upscale_factor,
|
||||
sd_image_t** images_out,
|
||||
int* num_images_out);
|
||||
|
||||
SD_API int get_upscale_factor(upscaler_ctx_t* upscaler_ctx);
|
||||
|
||||
@@ -442,6 +514,18 @@ SD_API bool convert(const char* input_path,
|
||||
const char* tensor_type_rules,
|
||||
bool convert_name);
|
||||
|
||||
SD_API bool convert_with_components(const char* model_path,
|
||||
const char* clip_l_path,
|
||||
const char* clip_g_path,
|
||||
const char* t5xxl_path,
|
||||
const char* diffusion_model_path,
|
||||
const char* vae_path,
|
||||
const char* output_path,
|
||||
enum sd_type_t output_type,
|
||||
const char* tensor_type_rules,
|
||||
bool convert_name,
|
||||
int n_threads);
|
||||
|
||||
SD_API bool preprocess_canny(sd_image_t image,
|
||||
float high_threshold,
|
||||
float low_threshold,
|
||||
@@ -449,9 +533,24 @@ SD_API bool preprocess_canny(sd_image_t image,
|
||||
float strong,
|
||||
bool inverse);
|
||||
|
||||
SD_API bool load_imatrix(const char* imatrix_path);
|
||||
SD_API void save_imatrix(const char* imatrix_path);
|
||||
SD_API void enable_imatrix_collection(void);
|
||||
SD_API void disable_imatrix_collection(void);
|
||||
|
||||
SD_API const char* sd_commit(void);
|
||||
SD_API const char* sd_version(void);
|
||||
|
||||
// List available ggml backend devices, one `name<TAB>description` per line.
|
||||
// The names are the device names accepted by the --backend / --params-backend
|
||||
// assignment specs. Returns the number of bytes required, excluding the null
|
||||
// terminator. Passing nullptr or buffer_size 0 only queries the required size.
|
||||
SD_API size_t sd_list_devices(char* buffer, size_t buffer_size);
|
||||
|
||||
// for C API, caller needs to call free_sd_images to free the memory after use
|
||||
// This helps avoid CRT problems on Windows when memory is allocated in the library but freed in the caller, which may use a different CRT.
|
||||
SD_API void free_sd_images(sd_image_t* result_images, int num_images);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
#!/usr/bin/env python
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import struct
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
import torch
|
||||
from safetensors import safe_open
|
||||
|
||||
|
||||
FLOAT_DTYPES = {
|
||||
"BF16",
|
||||
"F16",
|
||||
"F32",
|
||||
"F64",
|
||||
"F8_E4M3",
|
||||
"F8_E4M3FN",
|
||||
"F8_E5M2",
|
||||
}
|
||||
|
||||
FP8_DTYPES = {
|
||||
"F8_E4M3",
|
||||
"F8_E4M3FN",
|
||||
"F8_E5M2",
|
||||
}
|
||||
|
||||
DTYPE_SIZES = {
|
||||
"BOOL": 1,
|
||||
"U8": 1,
|
||||
"I8": 1,
|
||||
"F8_E4M3": 1,
|
||||
"F8_E4M3FN": 1,
|
||||
"F8_E5M2": 1,
|
||||
"U16": 2,
|
||||
"I16": 2,
|
||||
"F16": 2,
|
||||
"BF16": 2,
|
||||
"U32": 4,
|
||||
"I32": 4,
|
||||
"F32": 4,
|
||||
"U64": 8,
|
||||
"I64": 8,
|
||||
"F64": 8,
|
||||
}
|
||||
|
||||
|
||||
def read_safetensors_header(path: Path):
|
||||
with path.open("rb") as f:
|
||||
header_len = struct.unpack("<Q", f.read(8))[0]
|
||||
header = f.read(header_len).decode("utf-8").rstrip()
|
||||
return json.loads(header)
|
||||
|
||||
|
||||
def numel(shape):
|
||||
return math.prod(shape) if shape else 1
|
||||
|
||||
|
||||
def scale_key_for_weight(name: str):
|
||||
if name.endswith(".weight"):
|
||||
return name[:-len(".weight")] + ".weight_scale"
|
||||
if name.endswith("weight"):
|
||||
return name + "_scale"
|
||||
return None
|
||||
|
||||
|
||||
def tensor_nbytes(dtype: str, shape):
|
||||
return numel(shape) * DTYPE_SIZES[dtype]
|
||||
|
||||
|
||||
def build_output_plan(header):
|
||||
entries = {k: v for k, v in header.items() if k != "__metadata__"}
|
||||
paired_scale_keys = set()
|
||||
plan = []
|
||||
|
||||
for name, info in entries.items():
|
||||
scale_key = scale_key_for_weight(name)
|
||||
if info["dtype"] in FP8_DTYPES and scale_key in entries:
|
||||
paired_scale_keys.add(scale_key)
|
||||
|
||||
for name, info in entries.items():
|
||||
if name in paired_scale_keys:
|
||||
continue
|
||||
|
||||
dtype = info["dtype"]
|
||||
shape = info["shape"]
|
||||
scale_key = scale_key_for_weight(name)
|
||||
|
||||
if dtype in FP8_DTYPES and scale_key in entries:
|
||||
scale_info = entries[scale_key]
|
||||
plan.append(
|
||||
{
|
||||
"name": name,
|
||||
"source_dtype": dtype,
|
||||
"output_dtype": "BF16",
|
||||
"shape": shape,
|
||||
"mode": "fp8_scaled_weight",
|
||||
"scale_key": scale_key,
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
if dtype in FLOAT_DTYPES:
|
||||
plan.append(
|
||||
{
|
||||
"name": name,
|
||||
"source_dtype": dtype,
|
||||
"output_dtype": "BF16",
|
||||
"shape": shape,
|
||||
"mode": "float_to_bf16",
|
||||
}
|
||||
)
|
||||
else:
|
||||
plan.append(
|
||||
{
|
||||
"name": name,
|
||||
"source_dtype": dtype,
|
||||
"output_dtype": dtype,
|
||||
"shape": shape,
|
||||
"mode": "copy",
|
||||
}
|
||||
)
|
||||
|
||||
metadata = dict(header.get("__metadata__", {}) or {})
|
||||
metadata["format"] = "pt"
|
||||
metadata["conversion"] = "fp8_weight_scale_to_bf16"
|
||||
|
||||
output_header = {"__metadata__": metadata}
|
||||
offset = 0
|
||||
for item in plan:
|
||||
size = tensor_nbytes(item["output_dtype"], item["shape"])
|
||||
output_header[item["name"]] = {
|
||||
"dtype": item["output_dtype"],
|
||||
"shape": item["shape"],
|
||||
"data_offsets": [offset, offset + size],
|
||||
}
|
||||
offset += size
|
||||
|
||||
return plan, output_header, offset
|
||||
|
||||
|
||||
def write_tensor_bytes(out, tensor):
|
||||
tensor = tensor.detach().cpu().contiguous()
|
||||
if tensor.numel() == 0:
|
||||
return
|
||||
if tensor.dtype == torch.bfloat16:
|
||||
tensor.view(torch.uint16).numpy().tofile(out)
|
||||
elif tensor.dtype in (getattr(torch, "float8_e4m3fn", None), getattr(torch, "float8_e5m2", None)):
|
||||
tensor.view(torch.uint8).numpy().tofile(out)
|
||||
else:
|
||||
tensor.numpy().tofile(out)
|
||||
|
||||
|
||||
def scale_view_for_chunk(scale, chunk, first_dim_start=0, first_dim_end=None):
|
||||
scale = scale.to(torch.float32)
|
||||
|
||||
if scale.numel() == 1:
|
||||
return scale.reshape((1,) * chunk.ndim)
|
||||
|
||||
if chunk.ndim > 0 and scale.ndim == 1:
|
||||
if first_dim_end is not None and scale.shape[0] >= first_dim_end:
|
||||
scale = scale[first_dim_start:first_dim_end]
|
||||
if scale.shape[0] == chunk.shape[0]:
|
||||
return scale.reshape((scale.shape[0],) + (1,) * (chunk.ndim - 1))
|
||||
|
||||
return scale
|
||||
|
||||
|
||||
def write_scaled_fp8_weight(out, weight, scale, chunk_rows):
|
||||
if weight.ndim == 0:
|
||||
result = weight.to(torch.float32) * scale_view_for_chunk(scale, weight)
|
||||
write_tensor_bytes(out, result.to(torch.bfloat16))
|
||||
return
|
||||
|
||||
rows = weight.shape[0]
|
||||
for start in range(0, rows, chunk_rows):
|
||||
end = min(start + chunk_rows, rows)
|
||||
chunk = weight[start:end].to(torch.float32)
|
||||
scale_view = scale_view_for_chunk(scale, chunk, start, end)
|
||||
result = chunk * scale_view
|
||||
write_tensor_bytes(out, result.to(torch.bfloat16))
|
||||
|
||||
|
||||
def write_float_as_bf16(out, tensor, chunk_rows):
|
||||
if tensor.dtype == torch.bfloat16:
|
||||
write_tensor_bytes(out, tensor)
|
||||
return
|
||||
|
||||
if tensor.ndim == 0:
|
||||
write_tensor_bytes(out, tensor.to(torch.bfloat16))
|
||||
return
|
||||
|
||||
rows = tensor.shape[0]
|
||||
for start in range(0, rows, chunk_rows):
|
||||
end = min(start + chunk_rows, rows)
|
||||
write_tensor_bytes(out, tensor[start:end].to(torch.bfloat16))
|
||||
|
||||
|
||||
def convert(input_path: Path, output_path: Path, chunk_rows: int, dry_run: bool):
|
||||
header = read_safetensors_header(input_path)
|
||||
plan, output_header, data_size = build_output_plan(header)
|
||||
|
||||
source_counts = Counter(item["source_dtype"] for item in plan)
|
||||
output_counts = Counter(item["output_dtype"] for item in plan)
|
||||
scaled_count = sum(item["mode"] == "fp8_scaled_weight" for item in plan)
|
||||
dropped_scales = sum(item["mode"] == "fp8_scaled_weight" for item in plan)
|
||||
header_bytes = json.dumps(output_header, separators=(",", ":")).encode("utf-8")
|
||||
expected_size = 8 + len(header_bytes) + data_size
|
||||
|
||||
print(f"input: {input_path}")
|
||||
print(f"output: {output_path}")
|
||||
print(f"tensors written: {len(plan)}")
|
||||
print(f"scaled fp8 weights dequantized: {scaled_count}")
|
||||
print(f"weight_scale tensors dropped: {dropped_scales}")
|
||||
print(f"source dtypes: {dict(sorted(source_counts.items()))}")
|
||||
print(f"output dtypes: {dict(sorted(output_counts.items()))}")
|
||||
print(f"expected output size: {expected_size / (1024 ** 3):.2f} GiB")
|
||||
|
||||
if dry_run:
|
||||
return
|
||||
|
||||
if output_path.exists():
|
||||
raise FileExistsError(f"{output_path} already exists; pass --overwrite to replace it")
|
||||
|
||||
tmp_path = output_path.with_suffix(output_path.suffix + ".tmp")
|
||||
if tmp_path.exists():
|
||||
raise FileExistsError(f"{tmp_path} already exists; remove it or choose another output")
|
||||
|
||||
with safe_open(str(input_path), framework="pt", device="cpu") as sf, tmp_path.open("wb") as out:
|
||||
out.write(struct.pack("<Q", len(header_bytes)))
|
||||
out.write(header_bytes)
|
||||
|
||||
for index, item in enumerate(plan, 1):
|
||||
name = item["name"]
|
||||
print(f"[{index:04d}/{len(plan):04d}] {name} -> {item['output_dtype']}")
|
||||
|
||||
tensor = sf.get_tensor(name)
|
||||
if item["mode"] == "fp8_scaled_weight":
|
||||
scale = sf.get_tensor(item["scale_key"])
|
||||
write_scaled_fp8_weight(out, tensor, scale, chunk_rows)
|
||||
elif item["mode"] == "float_to_bf16":
|
||||
write_float_as_bf16(out, tensor, chunk_rows)
|
||||
else:
|
||||
write_tensor_bytes(out, tensor)
|
||||
|
||||
actual_size = out.tell()
|
||||
|
||||
if actual_size != expected_size:
|
||||
tmp_path.unlink(missing_ok=True)
|
||||
raise RuntimeError(f"wrote {actual_size} bytes, expected {expected_size} bytes")
|
||||
|
||||
tmp_path.replace(output_path)
|
||||
print("done")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Convert an fp8 safetensors checkpoint with weight_scale tensors to bf16."
|
||||
)
|
||||
parser.add_argument("--input", default="ideogram4_fp8.safetensors", type=Path)
|
||||
parser.add_argument("--output", default="ideogram4_bf16.safetensors", type=Path)
|
||||
parser.add_argument("--chunk-rows", default=1024, type=int)
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
parser.add_argument("--overwrite", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
input_path = args.input.resolve()
|
||||
output_path = args.output.resolve()
|
||||
|
||||
if args.chunk_rows < 1:
|
||||
raise ValueError("--chunk-rows must be >= 1")
|
||||
if not input_path.exists():
|
||||
raise FileNotFoundError(input_path)
|
||||
if args.overwrite and output_path.exists():
|
||||
output_path.unlink()
|
||||
|
||||
convert(input_path, output_path, args.chunk_rows, args.dry_run)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert a Qwen3-VL HF safetensors checkpoint into a sd.cpp-loadable form.
|
||||
|
||||
The HF dump prefixes text-tower keys with ``model.language_model.`` and
|
||||
vision-tower keys with ``model.visual.``. sd.cpp expects ``model.<rest>`` for
|
||||
the text side; the vision side is converted by sd.cpp's own
|
||||
``convert_qwen3_vl_vision_name`` and is left as-is here.
|
||||
|
||||
Operates on raw safetensors bytes so any dtype (BF16/F16/F32) is preserved.
|
||||
|
||||
Usage:
|
||||
python3 scripts/convert_qwen3_vl.py <hf_qwen3_vl_dir_or_safetensors> <output.safetensors>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def rewrite_key(key: str) -> str:
|
||||
if key.startswith("model.language_model."):
|
||||
return "model." + key[len("model.language_model."):]
|
||||
return key
|
||||
|
||||
|
||||
def read_safetensors_header(path: str):
|
||||
with open(path, "rb") as f:
|
||||
hdr_len = struct.unpack("<Q", f.read(8))[0]
|
||||
hdr_bytes = f.read(hdr_len)
|
||||
return json.loads(hdr_bytes), 8 + hdr_len
|
||||
|
||||
|
||||
def collect_shard_paths(path: str):
|
||||
if os.path.isdir(path):
|
||||
index_path = os.path.join(path, "model.safetensors.index.json")
|
||||
if os.path.isfile(index_path):
|
||||
with open(index_path) as f:
|
||||
idx = json.load(f)
|
||||
return sorted({os.path.join(path, n) for n in idx["weight_map"].values()})
|
||||
single = os.path.join(path, "model.safetensors")
|
||||
if os.path.isfile(single):
|
||||
return [single]
|
||||
raise FileNotFoundError(f"No Qwen3-VL safetensors in {path}")
|
||||
if os.path.isfile(path):
|
||||
return [path]
|
||||
raise FileNotFoundError(path)
|
||||
|
||||
|
||||
def stage_tensors(input_path: str):
|
||||
entries = []
|
||||
for shard_path in collect_shard_paths(input_path):
|
||||
hdr, data_off = read_safetensors_header(shard_path)
|
||||
for key, info in hdr.items():
|
||||
if key == "__metadata__":
|
||||
continue
|
||||
entries.append((rewrite_key(key), shard_path, data_off, info))
|
||||
return entries
|
||||
|
||||
|
||||
def write_consolidated(out_path: str, entries):
|
||||
entries = sorted(entries, key=lambda e: e[0])
|
||||
|
||||
new_header = {}
|
||||
cur_offset = 0
|
||||
for new_key, shard_path, data_off, info in entries:
|
||||
start, end = info["data_offsets"]
|
||||
size = end - start
|
||||
new_header[new_key] = {
|
||||
"dtype": info["dtype"],
|
||||
"shape": info["shape"],
|
||||
"data_offsets": [cur_offset, cur_offset + size],
|
||||
}
|
||||
cur_offset += size
|
||||
|
||||
header_json = json.dumps(new_header, separators=(",", ":")).encode("utf-8")
|
||||
pad = (-len(header_json)) % 8
|
||||
header_json = header_json + (b" " * pad)
|
||||
|
||||
with open(out_path, "wb") as out:
|
||||
out.write(struct.pack("<Q", len(header_json)))
|
||||
out.write(header_json)
|
||||
for new_key, shard_path, data_off, info in entries:
|
||||
start, end = info["data_offsets"]
|
||||
with open(shard_path, "rb") as src:
|
||||
src.seek(data_off + start)
|
||||
remaining = end - start
|
||||
while remaining > 0:
|
||||
chunk = src.read(min(8 * 1024 * 1024, remaining))
|
||||
if not chunk:
|
||||
raise IOError(f"Truncated tensor in {shard_path}")
|
||||
out.write(chunk)
|
||||
remaining -= len(chunk)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("input", help="HF Qwen3-VL directory or single safetensors file")
|
||||
parser.add_argument("output", help="Output single safetensors path")
|
||||
args = parser.parse_args()
|
||||
|
||||
entries = stage_tensors(args.input)
|
||||
print(f"Tensors: {len(entries)}")
|
||||
print(f"Writing -> {args.output}")
|
||||
os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True)
|
||||
write_consolidated(args.output, entries)
|
||||
print(f"Done. Output size: {os.path.getsize(args.output) / 1e9:.2f} GB")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,279 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert a SeFi-Image diffusers checkpoint into a single sd.cpp-compatible safetensors.
|
||||
|
||||
Operates on raw safetensors bytes so any dtype (BF16, F32, ...) is preserved exactly.
|
||||
No numpy or torch dependency required.
|
||||
|
||||
Usage:
|
||||
python3 scripts/convert_sefi.py <sefi_diffusers_dir> <output.safetensors>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
_LINEAR_TO_LIN = re.compile(r"\.linear\.")
|
||||
_SHARED_MOD_PREFIXES = (
|
||||
"double_stream_modulation_img",
|
||||
"double_stream_modulation_txt",
|
||||
"single_stream_modulation",
|
||||
)
|
||||
|
||||
|
||||
def rewrite_transformer_key(key: str) -> str:
|
||||
if key.startswith("backbone."):
|
||||
key = key[len("backbone."):]
|
||||
elif key.startswith("dual_time_embed."):
|
||||
return key
|
||||
|
||||
if any(key.startswith(prefix + ".") for prefix in _SHARED_MOD_PREFIXES):
|
||||
key = _LINEAR_TO_LIN.sub(".lin.", key, count=1)
|
||||
|
||||
if key == "context_embedder.weight":
|
||||
return "txt_in.weight"
|
||||
if key == "context_embedder.bias":
|
||||
return "txt_in.bias"
|
||||
if key == "x_embedder.weight":
|
||||
return "img_in.weight"
|
||||
if key == "x_embedder.bias":
|
||||
return "img_in.bias"
|
||||
|
||||
if key == "proj_out.weight":
|
||||
return "final_layer.linear.weight"
|
||||
if key == "proj_out.bias":
|
||||
return "final_layer.linear.bias"
|
||||
if key == "norm_out.linear.weight":
|
||||
return "final_layer.adaLN_modulation.1.weight"
|
||||
if key == "norm_out.linear.bias":
|
||||
return "final_layer.adaLN_modulation.1.bias"
|
||||
|
||||
m = re.match(r"transformer_blocks\.(\d+)\.(.*)$", key)
|
||||
if m:
|
||||
return _rewrite_double_stream(m.group(1), m.group(2))
|
||||
m = re.match(r"single_transformer_blocks\.(\d+)\.(.*)$", key)
|
||||
if m:
|
||||
return _rewrite_single_stream(m.group(1), m.group(2))
|
||||
|
||||
return key
|
||||
|
||||
|
||||
def _rewrite_double_stream(idx: str, tail: str) -> str:
|
||||
dst = f"double_blocks.{idx}."
|
||||
mapping = {
|
||||
"norm1.linear.weight": "img_mod.lin.weight",
|
||||
"norm1_context.linear.weight": "txt_mod.lin.weight",
|
||||
"attn.norm_q.weight": "img_attn.norm.query_norm.scale",
|
||||
"attn.norm_k.weight": "img_attn.norm.key_norm.scale",
|
||||
"attn.norm_added_q.weight": "txt_attn.norm.query_norm.scale",
|
||||
"attn.norm_added_k.weight": "txt_attn.norm.key_norm.scale",
|
||||
"attn.to_out.0.weight": "img_attn.proj.weight",
|
||||
"attn.to_add_out.weight": "txt_attn.proj.weight",
|
||||
"ff.net.0.proj.weight": "img_mlp.0.weight",
|
||||
"ff.net.2.weight": "img_mlp.2.weight",
|
||||
"ff_context.net.0.proj.weight": "txt_mlp.0.weight",
|
||||
"ff_context.net.2.weight": "txt_mlp.2.weight",
|
||||
"ff.linear_in.weight": "img_mlp.0.weight",
|
||||
"ff.linear_out.weight": "img_mlp.2.weight",
|
||||
"ff_context.linear_in.weight": "txt_mlp.0.weight",
|
||||
"ff_context.linear_out.weight": "txt_mlp.2.weight",
|
||||
}
|
||||
return dst + mapping.get(tail, tail)
|
||||
|
||||
|
||||
# QKV triplets to fuse on output: source tails -> target fused tail.
|
||||
# Each tuple is (q_tail, k_tail, v_tail, fused_target_tail).
|
||||
QKV_DOUBLE_TRIPLETS = [
|
||||
("attn.to_q.weight", "attn.to_k.weight", "attn.to_v.weight", "img_attn.qkv.weight"),
|
||||
("attn.add_q_proj.weight", "attn.add_k_proj.weight", "attn.add_v_proj.weight", "txt_attn.qkv.weight"),
|
||||
]
|
||||
|
||||
|
||||
def _rewrite_single_stream(idx: str, tail: str) -> str:
|
||||
dst = f"single_blocks.{idx}."
|
||||
mapping = {
|
||||
"norm.linear.weight": "modulation.lin.weight",
|
||||
"attn.norm_q.weight": "norm.query_norm.scale",
|
||||
"attn.norm_k.weight": "norm.key_norm.scale",
|
||||
"attn.to_qkv_mlp_proj.weight": "linear1.weight",
|
||||
"attn.to_out.weight": "linear2.weight",
|
||||
}
|
||||
return dst + mapping.get(tail, tail)
|
||||
|
||||
|
||||
|
||||
|
||||
def read_safetensors_header(path: str):
|
||||
"""Return (header dict, data start byte offset)."""
|
||||
with open(path, "rb") as f:
|
||||
hdr_len = struct.unpack("<Q", f.read(8))[0]
|
||||
hdr_bytes = f.read(hdr_len)
|
||||
return json.loads(hdr_bytes), 8 + hdr_len
|
||||
|
||||
|
||||
def collect_shard_paths(directory: str, weight_pattern: str):
|
||||
index_path = os.path.join(directory, f"{weight_pattern}.safetensors.index.json")
|
||||
if os.path.isfile(index_path):
|
||||
with open(index_path) as f:
|
||||
idx = json.load(f)
|
||||
return sorted({os.path.join(directory, n) for n in idx["weight_map"].values()})
|
||||
single = os.path.join(directory, f"{weight_pattern}.safetensors")
|
||||
if not os.path.isfile(single):
|
||||
raise FileNotFoundError(f"No checkpoint at {directory}: missing {weight_pattern}")
|
||||
return [single]
|
||||
|
||||
|
||||
def stage_tensors_for_section(section_dir: str, rewrite_fn):
|
||||
"""Return a list of (new_key, shard_path, data_start_offset, info_dict) entries.
|
||||
|
||||
A "qkv_fuse" pseudo-entry with three source descriptors is emitted when a
|
||||
transformer_blocks.* split q/k/v triplet is found, so the writer can fuse
|
||||
them into a single output tensor.
|
||||
"""
|
||||
entries = []
|
||||
# First, index all raw keys per shard so we can detect qkv triplets.
|
||||
raw_by_block = {} # block_idx -> {tail: (key, shard_path, data_off, info)}
|
||||
raw_others = []
|
||||
for shard_path in collect_shard_paths(section_dir, "diffusion_pytorch_model"):
|
||||
hdr, data_off = read_safetensors_header(shard_path)
|
||||
for key, info in hdr.items():
|
||||
if key == "__metadata__":
|
||||
continue
|
||||
m = re.match(r"backbone\.transformer_blocks\.(\d+)\.(.*)$", key)
|
||||
if m and any(m.group(2) in trip[:3] for trip in QKV_DOUBLE_TRIPLETS):
|
||||
idx = m.group(1)
|
||||
raw_by_block.setdefault(idx, {})[m.group(2)] = (key, shard_path, data_off, info)
|
||||
else:
|
||||
raw_others.append((key, shard_path, data_off, info))
|
||||
|
||||
for key, shard_path, data_off, info in raw_others:
|
||||
new_key = rewrite_fn(key)
|
||||
# Swap the (scale, shift) halves to (shift, scale) at conversion time so
|
||||
# the on-disk weight matches BFL flux ordering and the runtime stays
|
||||
# version-agnostic. norm_out.linear weight shape is [2*dim, dim] and bias
|
||||
# is [2*dim]; both split along axis 0 (outermost == row-major outer).
|
||||
if new_key in ("final_layer.adaLN_modulation.1.weight",
|
||||
"final_layer.adaLN_modulation.1.bias"):
|
||||
info = dict(info)
|
||||
info["_chunk_swap_halves"] = True
|
||||
entries.append((new_key, shard_path, data_off, info))
|
||||
|
||||
for block_idx, tails in raw_by_block.items():
|
||||
for q_tail, k_tail, v_tail, fused_tail in QKV_DOUBLE_TRIPLETS:
|
||||
if q_tail in tails and k_tail in tails and v_tail in tails:
|
||||
q = tails[q_tail]; k = tails[k_tail]; v = tails[v_tail]
|
||||
# Validate shapes match.
|
||||
q_shape = q[3]["shape"]; k_shape = k[3]["shape"]; v_shape = v[3]["shape"]
|
||||
if q_shape != k_shape or q_shape != v_shape:
|
||||
raise ValueError(f"qkv shape mismatch at block {block_idx} {q_tail}: q={q_shape} k={k_shape} v={v_shape}")
|
||||
fused_shape = [q_shape[0] * 3] + list(q_shape[1:])
|
||||
fused_info = {
|
||||
"dtype": q[3]["dtype"],
|
||||
"shape": fused_shape,
|
||||
"_qkv_sources": [q, k, v], # pseudo field consumed by writer
|
||||
}
|
||||
entries.append((f"double_blocks.{block_idx}.{fused_tail}",
|
||||
None, None, fused_info))
|
||||
del tails[q_tail]; del tails[k_tail]; del tails[v_tail]
|
||||
# Anything left in tails was an unmatched single - pass through.
|
||||
for tail, payload in tails.items():
|
||||
entries.append((rewrite_fn(payload[0]),) + payload[1:])
|
||||
return entries
|
||||
|
||||
|
||||
_DTYPE_BYTES = {
|
||||
"BF16": 2, "F16": 2, "F32": 4, "F64": 8,
|
||||
"U8": 1, "I8": 1, "I16": 2, "I32": 4, "I64": 8,
|
||||
"BOOL": 1,
|
||||
}
|
||||
|
||||
|
||||
def _total_bytes(info: dict) -> int:
|
||||
if "_qkv_sources" in info:
|
||||
elems = 1
|
||||
for d in info["shape"]:
|
||||
elems *= d
|
||||
return elems * _DTYPE_BYTES[info["dtype"]]
|
||||
start, end = info["data_offsets"]
|
||||
return end - start
|
||||
|
||||
|
||||
def write_consolidated(out_path: str, entries):
|
||||
"""Write a single safetensors file by streaming raw bytes from each shard.
|
||||
|
||||
For qkv-fused entries, q/k/v are concatenated along axis 0 (row-major), so a
|
||||
simple byte-level concatenation produces the correct fused layout for any
|
||||
standard dtype.
|
||||
"""
|
||||
entries = sorted(entries, key=lambda e: e[0])
|
||||
|
||||
new_header = {}
|
||||
cur_offset = 0
|
||||
for new_key, shard_path, data_off, info in entries:
|
||||
size = _total_bytes(info)
|
||||
new_header[new_key] = {
|
||||
"dtype": info["dtype"],
|
||||
"shape": info["shape"],
|
||||
"data_offsets": [cur_offset, cur_offset + size],
|
||||
}
|
||||
cur_offset += size
|
||||
|
||||
header_json = json.dumps(new_header, separators=(",", ":")).encode("utf-8")
|
||||
pad = (-len(header_json)) % 8
|
||||
header_json = header_json + (b" " * pad)
|
||||
|
||||
def copy_range(src_path, src_data_off, src_info, out, byte_range=None):
|
||||
start, end = src_info["data_offsets"]
|
||||
if byte_range is not None:
|
||||
sub_start, sub_end = byte_range
|
||||
start, end = start + sub_start, start + sub_end
|
||||
with open(src_path, "rb") as src:
|
||||
src.seek(src_data_off + start)
|
||||
remaining = end - start
|
||||
while remaining > 0:
|
||||
chunk = src.read(min(8 * 1024 * 1024, remaining))
|
||||
if not chunk:
|
||||
raise IOError(f"Truncated tensor in {src_path}")
|
||||
out.write(chunk)
|
||||
remaining -= len(chunk)
|
||||
|
||||
with open(out_path, "wb") as out:
|
||||
out.write(struct.pack("<Q", len(header_json)))
|
||||
out.write(header_json)
|
||||
for new_key, shard_path, data_off, info in entries:
|
||||
if "_qkv_sources" in info:
|
||||
for q_entry in info["_qkv_sources"]:
|
||||
_, src_path, src_data_off, src_info = q_entry
|
||||
copy_range(src_path, src_data_off, src_info, out)
|
||||
elif info.get("_chunk_swap_halves"):
|
||||
size = _total_bytes(info)
|
||||
half = size // 2
|
||||
if size != half * 2:
|
||||
raise ValueError(f"{new_key}: odd byte size {size} cannot be split into halves")
|
||||
copy_range(shard_path, data_off, info, out, byte_range=(half, size))
|
||||
copy_range(shard_path, data_off, info, out, byte_range=(0, half))
|
||||
else:
|
||||
copy_range(shard_path, data_off, info, out)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("input_dir", help="SeFi diffusers checkpoint directory")
|
||||
parser.add_argument("output", help="Output transformer safetensors path (load via --diffusion-model)")
|
||||
args = parser.parse_args()
|
||||
|
||||
transformer_entries = stage_tensors_for_section(
|
||||
os.path.join(args.input_dir, "transformer"), rewrite_transformer_key)
|
||||
|
||||
print(f"Transformer tensors: {len(transformer_entries)}")
|
||||
print(f"Writing {len(transformer_entries)} tensors -> {args.output}")
|
||||
os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True)
|
||||
write_consolidated(args.output, transformer_entries)
|
||||
print(f"Done. Output size: {os.path.getsize(args.output) / 1e9:.2f} GB")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,60 @@
|
||||
$patterns = @(
|
||||
"src/*.cpp"
|
||||
"src/*.h"
|
||||
"src/*.hpp"
|
||||
"src/conditioning/*.cpp"
|
||||
"src/conditioning/*.h"
|
||||
"src/conditioning/*.hpp"
|
||||
"src/core/*.cpp"
|
||||
"src/core/*.h"
|
||||
"src/core/*.hpp"
|
||||
"src/extensions/*.cpp"
|
||||
"src/extensions/*.h"
|
||||
"src/extensions/*.hpp"
|
||||
"src/runtime/*.cpp"
|
||||
"src/runtime/*.h"
|
||||
"src/runtime/*.hpp"
|
||||
"src/model/*/*.cpp"
|
||||
"src/model/*/*.h"
|
||||
"src/model/*/*.hpp"
|
||||
"src/tokenizers/*.h"
|
||||
"src/tokenizers/*.cpp"
|
||||
"src/tokenizers/vocab/*.h"
|
||||
"src/tokenizers/vocab/*.cpp"
|
||||
"src/model_io/*.h"
|
||||
"src/model_io/*.cpp"
|
||||
"examples/cli/*.cpp"
|
||||
"examples/cli/*.h"
|
||||
"examples/server/*.cpp"
|
||||
"examples/common/*.hpp"
|
||||
"examples/common/*.h"
|
||||
"examples/common/*.cpp"
|
||||
)
|
||||
|
||||
Push-Location (Join-Path $PSScriptRoot "..")
|
||||
|
||||
try {
|
||||
$root = (Get-Location).Path
|
||||
|
||||
foreach ($pattern in $patterns) {
|
||||
$files = Get-ChildItem -Path $pattern -File -ErrorAction SilentlyContinue | Sort-Object FullName
|
||||
|
||||
foreach ($file in $files) {
|
||||
$relativePath = $file.FullName.Substring($root.Length).TrimStart('\', '/') -replace '\\', '/'
|
||||
|
||||
if ($relativePath -like "vocab*") {
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Host "formatting '$relativePath'"
|
||||
|
||||
# if ($relativePath -ne "stable-diffusion.h") {
|
||||
# clang-tidy -fix -p build_linux/ "$relativePath"
|
||||
# }
|
||||
|
||||
& clang-format -style=file -i $relativePath
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
repo_root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$repo_root"
|
||||
|
||||
for f in src/*.cpp src/*.h src/*.hpp \
|
||||
src/conditioning/*.cpp src/conditioning/*.h src/conditioning/*.hpp \
|
||||
src/core/*.cpp src/core/*.h src/core/*.hpp \
|
||||
src/extensions/*.cpp src/extensions/*.h src/extensions/*.hpp \
|
||||
src/runtime/*.cpp src/runtime/*.h src/runtime/*.hpp \
|
||||
src/model/*/*.cpp src/model/*/*.h src/model/*/*.hpp \
|
||||
src/tokenizers/*.h src/tokenizers/*.cpp src/tokenizers/vocab/*.h src/tokenizers/vocab/*.cpp \
|
||||
src/model_io/*.h src/model_io/*.cpp examples/cli/*.cpp examples/cli/*.h examples/server/*.cpp \
|
||||
examples/common/*.hpp examples/common/*.h examples/common/*.cpp; do
|
||||
[[ -e "$f" ]] || continue
|
||||
[[ "$f" == vocab* ]] && continue
|
||||
echo "formatting '$f'"
|
||||
# if [ "$f" != "stable-diffusion.h" ]; then
|
||||
# clang-tidy -fix -p build_linux/ "$f"
|
||||
# fi
|
||||
clang-format -style=file -i "$f"
|
||||
done
|
||||
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
Precompute a PuLID-Flux identity embedding from a single source portrait.
|
||||
|
||||
Writes a gguf file (a single tensor `pulid_id`) that stable-diffusion.cpp's
|
||||
`--pulid-id-embedding` flag consumes.
|
||||
|
||||
Dependencies (recommended: vendor rather than pip-install due to upstream
|
||||
packaging quirks):
|
||||
- torch + safetensors
|
||||
- The ToTheBeginning/PuLID repository's `pulid/` package and `eva_clip/`.
|
||||
Put them on PYTHONPATH or sys.path before running this script.
|
||||
- insightface, facexlib, torchvision, opencv-python, huggingface_hub, gguf
|
||||
- numpy, Pillow
|
||||
|
||||
Usage:
|
||||
python scripts/pulid_extract_id.py \\
|
||||
--portrait /path/to/source-photo.jpg \\
|
||||
--pulid-weights /path/to/pulid_flux_v0.9.1.safetensors \\
|
||||
--out /path/to/source.pulidembd
|
||||
|
||||
The portrait must contain a clearly visible face. insightface's antelopev2
|
||||
detector will be auto-downloaded on first run.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from types import SimpleNamespace
|
||||
|
||||
|
||||
def extract(portrait_path: str, pulid_weights: str) -> "torch.Tensor":
|
||||
import numpy as np
|
||||
import torch
|
||||
from PIL import Image
|
||||
from pulid.pipeline_flux import PuLIDPipeline
|
||||
|
||||
if torch.cuda.is_available():
|
||||
device, onnx_provider = "cuda", "gpu"
|
||||
else:
|
||||
device, onnx_provider = "cpu", "cpu"
|
||||
|
||||
print(f"device={device}", flush=True)
|
||||
|
||||
# PuLIDPipeline only attaches pulid_ca attributes to `dit` during
|
||||
# construction; get_id_embedding() never runs Flux, so a dummy object is
|
||||
# enough and avoids importing/building a Flux skeleton.
|
||||
print("instantiating PuLIDPipeline with a dummy Flux object", flush=True)
|
||||
dit = SimpleNamespace()
|
||||
pulid = PuLIDPipeline(dit=dit,
|
||||
device=device,
|
||||
weight_dtype=torch.bfloat16,
|
||||
onnx_provider=onnx_provider)
|
||||
|
||||
print(f"loading PuLID weights from {pulid_weights}", flush=True)
|
||||
pulid.load_pretrain(pretrain_path=pulid_weights, version="v0.9.1")
|
||||
|
||||
print(f"extracting ID embedding from {portrait_path}", flush=True)
|
||||
face_img = np.array(Image.open(portrait_path).convert("RGB"))
|
||||
id_embedding, _ = pulid.get_id_embedding(face_img)
|
||||
print(f"id embedding shape={tuple(id_embedding.shape)} dtype={id_embedding.dtype}",
|
||||
flush=True)
|
||||
|
||||
if id_embedding.ndim == 3 and id_embedding.shape[0] == 1:
|
||||
id_embedding = id_embedding[0]
|
||||
return id_embedding
|
||||
|
||||
|
||||
def write_embd(tensor, out_path: str, dtype_choice: str) -> None:
|
||||
import gguf
|
||||
import torch
|
||||
|
||||
if tensor.ndim != 2:
|
||||
raise ValueError(f"expected (num_tokens, token_dim); got {tuple(tensor.shape)}")
|
||||
num_tokens, token_dim = tensor.shape
|
||||
|
||||
os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
|
||||
|
||||
writer = gguf.GGUFWriter(out_path, arch="pulid")
|
||||
writer.add_uint32("pulid.version", 1)
|
||||
|
||||
if dtype_choice == "fp16":
|
||||
arr = tensor.to(torch.float16).contiguous().cpu().numpy()
|
||||
writer.add_tensor("pulid_id", arr)
|
||||
elif dtype_choice == "fp32":
|
||||
arr = tensor.to(torch.float32).contiguous().cpu().numpy()
|
||||
writer.add_tensor("pulid_id", arr)
|
||||
elif dtype_choice == "bf16":
|
||||
raw = tensor.to(torch.bfloat16).contiguous().view(torch.uint16).cpu().numpy()
|
||||
writer.add_tensor("pulid_id", raw,
|
||||
raw_shape=(int(num_tokens), int(token_dim)),
|
||||
raw_dtype=gguf.GGMLQuantizationType.BF16)
|
||||
else:
|
||||
raise ValueError(f"unknown --dtype {dtype_choice}")
|
||||
|
||||
writer.write_header_to_file()
|
||||
writer.write_kv_data_to_file()
|
||||
writer.write_tensors_to_file()
|
||||
writer.close()
|
||||
|
||||
print(f"wrote {out_path}: gguf, tensor pulid_id [{token_dim}, {num_tokens}] {dtype_choice}",
|
||||
flush=True)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(
|
||||
description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument("--portrait", required=True,
|
||||
help="Path to the source portrait image (JPG/PNG).")
|
||||
ap.add_argument("--pulid-weights", required=True,
|
||||
help="Path to pulid_flux_v0.9.x.safetensors.")
|
||||
ap.add_argument("--out", required=True,
|
||||
help="Output path for the .pulidembd binary.")
|
||||
ap.add_argument("--dtype", default="fp16",
|
||||
choices=["fp16", "bf16", "fp32"],
|
||||
help="Storage dtype (default fp16; produces ~131 KB).")
|
||||
args = ap.parse_args()
|
||||
|
||||
if not os.path.exists(args.portrait):
|
||||
print(f"ERROR: portrait not found at {args.portrait}", file=sys.stderr)
|
||||
return 2
|
||||
if not os.path.exists(args.pulid_weights):
|
||||
print(f"ERROR: PuLID weights not found at {args.pulid_weights}", file=sys.stderr)
|
||||
return 3
|
||||
|
||||
embedding = extract(args.portrait, args.pulid_weights)
|
||||
write_embd(embedding, args.out, args.dtype)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Remove UTF-8 BOMs from files under a directory.
|
||||
|
||||
By default this scans the current working directory recursively and skips
|
||||
repository areas that should not be touched by ordinary maintenance scripts.
|
||||
Only files whose first three bytes are the UTF-8 BOM are rewritten.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
UTF8_BOM = b"\xef\xbb\xbf"
|
||||
|
||||
DEFAULT_EXCLUDED_DIR_NAMES = {
|
||||
".git",
|
||||
".hg",
|
||||
".svn",
|
||||
".mypy_cache",
|
||||
".pytest_cache",
|
||||
"__pycache__",
|
||||
"test",
|
||||
}
|
||||
|
||||
DEFAULT_EXCLUDED_DIR_PREFIXES = {
|
||||
"build",
|
||||
}
|
||||
|
||||
DEFAULT_EXCLUDED_REL_DIRS = {
|
||||
"examples/server/frontend",
|
||||
"ggml",
|
||||
"models",
|
||||
"src/vocab",
|
||||
"thirdparty",
|
||||
}
|
||||
|
||||
|
||||
def rel_posix(path: Path, root: Path) -> str:
|
||||
try:
|
||||
return path.relative_to(root).as_posix()
|
||||
except ValueError:
|
||||
return path.as_posix()
|
||||
|
||||
|
||||
def should_skip_dir(
|
||||
path: Path,
|
||||
root: Path,
|
||||
excluded_rel_dirs: set[str],
|
||||
excluded_names: set[str],
|
||||
excluded_prefixes: set[str],
|
||||
) -> bool:
|
||||
rel = rel_posix(path, root)
|
||||
return (
|
||||
path.name in excluded_names
|
||||
or rel in excluded_rel_dirs
|
||||
or any(path.name.startswith(prefix) for prefix in excluded_prefixes)
|
||||
)
|
||||
|
||||
|
||||
def iter_files(
|
||||
root: Path,
|
||||
recursive: bool,
|
||||
excluded_rel_dirs: set[str],
|
||||
excluded_names: set[str],
|
||||
excluded_prefixes: set[str],
|
||||
follow_symlinks: bool,
|
||||
):
|
||||
if recursive:
|
||||
for dirpath, dirnames, filenames in os.walk(root, followlinks=follow_symlinks):
|
||||
current_dir = Path(dirpath)
|
||||
dirnames[:] = [
|
||||
name
|
||||
for name in dirnames
|
||||
if not should_skip_dir(
|
||||
current_dir / name,
|
||||
root,
|
||||
excluded_rel_dirs,
|
||||
excluded_names,
|
||||
excluded_prefixes,
|
||||
)
|
||||
]
|
||||
for filename in filenames:
|
||||
path = current_dir / filename
|
||||
if path.is_symlink() and not follow_symlinks:
|
||||
continue
|
||||
yield path
|
||||
else:
|
||||
for path in root.iterdir():
|
||||
if path.is_file() and (follow_symlinks or not path.is_symlink()):
|
||||
yield path
|
||||
|
||||
|
||||
def has_utf8_bom(path: Path) -> bool:
|
||||
with path.open("rb") as f:
|
||||
return f.read(len(UTF8_BOM)) == UTF8_BOM
|
||||
|
||||
|
||||
def strip_utf8_bom(path: Path) -> None:
|
||||
tmp_path = None
|
||||
try:
|
||||
with path.open("rb") as src:
|
||||
if src.read(len(UTF8_BOM)) != UTF8_BOM:
|
||||
return
|
||||
|
||||
fd, tmp_name = tempfile.mkstemp(
|
||||
prefix=f".{path.name}.",
|
||||
suffix=".tmp",
|
||||
dir=str(path.parent),
|
||||
)
|
||||
tmp_path = Path(tmp_name)
|
||||
with os.fdopen(fd, "wb") as dst:
|
||||
shutil.copyfileobj(src, dst, length=1024 * 1024)
|
||||
|
||||
shutil.copystat(path, tmp_path, follow_symlinks=False)
|
||||
os.replace(tmp_path, path)
|
||||
tmp_path = None
|
||||
finally:
|
||||
if tmp_path is not None:
|
||||
try:
|
||||
tmp_path.unlink()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Scan files and convert UTF-8 BOM files to UTF-8 without BOM.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"root",
|
||||
nargs="?",
|
||||
default=".",
|
||||
help="Directory to scan. Defaults to the current directory.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Only list files that would be converted.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-recursive",
|
||||
action="store_true",
|
||||
help="Only scan files directly under root.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-repo-excluded",
|
||||
action="store_true",
|
||||
help="Do not skip default repository excluded directories.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--exclude-dir",
|
||||
action="append",
|
||||
default=[],
|
||||
metavar="DIR",
|
||||
help="Additional directory name or root-relative path to skip. Can be used multiple times.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--follow-symlinks",
|
||||
action="store_true",
|
||||
help="Follow symlinked directories and files.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q",
|
||||
"--quiet",
|
||||
action="store_true",
|
||||
help="Only print the final summary.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
root = Path(args.root).resolve()
|
||||
|
||||
if not root.is_dir():
|
||||
print(f"error: not a directory: {root}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
excluded_names = set()
|
||||
excluded_rel_dirs = set()
|
||||
excluded_prefixes = set()
|
||||
if not args.include_repo_excluded:
|
||||
excluded_names.update(DEFAULT_EXCLUDED_DIR_NAMES)
|
||||
excluded_rel_dirs.update(DEFAULT_EXCLUDED_REL_DIRS)
|
||||
excluded_prefixes.update(DEFAULT_EXCLUDED_DIR_PREFIXES)
|
||||
|
||||
for item in args.exclude_dir:
|
||||
normalized = Path(item).as_posix().strip("/")
|
||||
if "/" in normalized:
|
||||
excluded_rel_dirs.add(normalized)
|
||||
else:
|
||||
excluded_names.add(normalized)
|
||||
|
||||
scanned = 0
|
||||
converted = 0
|
||||
errors = 0
|
||||
|
||||
for path in iter_files(
|
||||
root=root,
|
||||
recursive=not args.no_recursive,
|
||||
excluded_rel_dirs=excluded_rel_dirs,
|
||||
excluded_names=excluded_names,
|
||||
excluded_prefixes=excluded_prefixes,
|
||||
follow_symlinks=args.follow_symlinks,
|
||||
):
|
||||
scanned += 1
|
||||
try:
|
||||
if not has_utf8_bom(path):
|
||||
continue
|
||||
converted += 1
|
||||
rel = rel_posix(path, root)
|
||||
if args.dry_run:
|
||||
if not args.quiet:
|
||||
print(f"would convert: {rel}")
|
||||
else:
|
||||
strip_utf8_bom(path)
|
||||
if not args.quiet:
|
||||
print(f"converted: {rel}")
|
||||
except OSError as exc:
|
||||
errors += 1
|
||||
print(f"error: {rel_posix(path, root)}: {exc}", file=sys.stderr)
|
||||
|
||||
action = "would convert" if args.dry_run else "converted"
|
||||
print(f"scanned {scanned} file(s), {action} {converted}, errors {errors}")
|
||||
return 1 if errors else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,14 +1,33 @@
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "model.h"
|
||||
#include "core/util.h"
|
||||
#include "model_io/gguf_io.h"
|
||||
#include "model_io/safetensors_io.h"
|
||||
#include "util.h"
|
||||
#include "model_io/streaming_writer.h"
|
||||
#include "model_loader.h"
|
||||
|
||||
#include "ggml-cpu.h"
|
||||
struct TensorExportInfo {
|
||||
TensorStorage storage;
|
||||
ggml_type type;
|
||||
};
|
||||
|
||||
struct TensorExportJob {
|
||||
TensorExportInfo info;
|
||||
std::vector<uint8_t> data;
|
||||
std::string error;
|
||||
bool success = false;
|
||||
};
|
||||
|
||||
static ggml_type get_export_tensor_type(ModelLoader& model_loader,
|
||||
const TensorStorage& tensor_storage,
|
||||
@@ -33,106 +52,355 @@ static ggml_type get_export_tensor_type(ModelLoader& model_loader,
|
||||
return tensor_type;
|
||||
}
|
||||
|
||||
static bool load_tensors_for_export(ModelLoader& model_loader,
|
||||
ggml_context* ggml_ctx,
|
||||
ggml_type type,
|
||||
const TensorTypeRules& tensor_type_rules,
|
||||
std::vector<TensorWriteInfo>& tensors) {
|
||||
std::mutex tensor_mutex;
|
||||
auto on_new_tensor_cb = [&](const TensorStorage& tensor_storage, ggml_tensor** dst_tensor) -> bool {
|
||||
const std::string& name = tensor_storage.name;
|
||||
ggml_type tensor_type = get_export_tensor_type(model_loader, tensor_storage, type, tensor_type_rules);
|
||||
static bool collect_tensors_for_export(ModelLoader& model_loader,
|
||||
ggml_type type,
|
||||
const TensorTypeRules& tensor_type_rules,
|
||||
std::vector<TensorExportInfo>& tensors) {
|
||||
tensors.clear();
|
||||
tensors.reserve(model_loader.get_tensor_storage_map().size());
|
||||
for (const auto& kv : model_loader.get_tensor_storage_map()) {
|
||||
const TensorStorage& tensor_storage = kv.second;
|
||||
TensorExportInfo info;
|
||||
info.storage = tensor_storage;
|
||||
info.type = get_export_tensor_type(model_loader, tensor_storage, type, tensor_type_rules);
|
||||
tensors.push_back(std::move(info));
|
||||
}
|
||||
LOG_INFO("collected %zu tensors for export", tensors.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(tensor_mutex);
|
||||
ggml_tensor* tensor = ggml_new_tensor(ggml_ctx, tensor_type, tensor_storage.n_dims, tensor_storage.ne);
|
||||
if (tensor == nullptr) {
|
||||
LOG_ERROR("ggml_new_tensor failed");
|
||||
static size_t export_tensor_nbytes(const TensorExportInfo& info) {
|
||||
TensorStorage output_storage = info.storage;
|
||||
output_storage.type = info.type;
|
||||
return static_cast<size_t>(output_storage.nbytes());
|
||||
}
|
||||
|
||||
static TensorWritePlan tensor_write_plan_from_export_info(const TensorExportInfo& info) {
|
||||
TensorWritePlan plan;
|
||||
plan.name = info.storage.name;
|
||||
plan.type = info.type;
|
||||
plan.n_dims = info.storage.n_dims;
|
||||
for (int i = 0; i < SD_MAX_DIMS; i++) {
|
||||
plan.ne[i] = info.storage.ne[i];
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
|
||||
static std::vector<TensorWritePlan> tensor_write_plans_from_export_infos(const std::vector<TensorExportInfo>& tensors) {
|
||||
std::vector<TensorWritePlan> plans;
|
||||
plans.reserve(tensors.size());
|
||||
for (const TensorExportInfo& info : tensors) {
|
||||
plans.push_back(tensor_write_plan_from_export_info(info));
|
||||
}
|
||||
return plans;
|
||||
}
|
||||
|
||||
static bool preallocate_output_file(const std::string& output_path, uint64_t file_size, std::string* error) {
|
||||
if (file_size == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::fstream file(output_path, std::ios::binary | std::ios::in | std::ios::out);
|
||||
if (!file.is_open()) {
|
||||
if (error != nullptr) {
|
||||
*error = "failed to open output file '" + output_path + "' for preallocation";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// This portable fallback sets the final file size. A platform-specific
|
||||
// posix_fallocate/ftruncate path can replace it later.
|
||||
file.seekp(static_cast<std::streamoff>(file_size - 1), std::ios::beg);
|
||||
file.put('\0');
|
||||
file.flush();
|
||||
if (!file) {
|
||||
if (error != nullptr) {
|
||||
*error = "failed to preallocate output file '" + output_path + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool load_tensor_for_export(ModelLoader& model_loader, TensorExportJob& job) {
|
||||
size_t mem_size = 1 * 1024 * 1024;
|
||||
mem_size += ggml_tensor_overhead();
|
||||
TensorStorage output_storage = job.info.storage;
|
||||
output_storage.type = job.info.type;
|
||||
mem_size += static_cast<size_t>(output_storage.nbytes());
|
||||
|
||||
ggml_context* ggml_ctx = ggml_init({mem_size, nullptr, false});
|
||||
if (ggml_ctx == nullptr) {
|
||||
job.error = "ggml_init failed for tensor '" + job.info.storage.name + "'";
|
||||
return false;
|
||||
}
|
||||
|
||||
ggml_tensor* tensor = ggml_new_tensor(ggml_ctx, job.info.type, job.info.storage.n_dims, job.info.storage.ne);
|
||||
if (tensor == nullptr) {
|
||||
ggml_free(ggml_ctx);
|
||||
job.error = "ggml_new_tensor failed for tensor '" + job.info.storage.name + "'";
|
||||
return false;
|
||||
}
|
||||
ggml_set_name(tensor, job.info.storage.name.c_str());
|
||||
|
||||
const size_t tensor_nbytes = ggml_nbytes(tensor);
|
||||
if (tensor_nbytes > 0 && !model_loader.load_tensor(job.info.storage, tensor)) {
|
||||
ggml_free(ggml_ctx);
|
||||
job.error = "failed to load tensor '" + job.info.storage.name + "'";
|
||||
return false;
|
||||
}
|
||||
|
||||
job.data.resize(tensor_nbytes);
|
||||
if (tensor_nbytes > 0) {
|
||||
memcpy(job.data.data(), tensor->data, tensor_nbytes);
|
||||
}
|
||||
ggml_free(ggml_ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool stream_tensor_data(ModelLoader& model_loader,
|
||||
const std::string& output_path,
|
||||
const std::vector<TensorExportInfo>& tensors,
|
||||
const StreamingModelWriter& writer,
|
||||
int n_threads,
|
||||
std::string* error) {
|
||||
n_threads = n_threads > 0 ? n_threads : sd_get_num_physical_cores();
|
||||
n_threads = std::max(1, n_threads);
|
||||
LOG_INFO("streaming convert with %d threads", n_threads);
|
||||
|
||||
int64_t start_time = ggml_time_ms();
|
||||
uint64_t bytes_written = 0;
|
||||
size_t tensors_written = 0;
|
||||
size_t next_tensor_index = 0;
|
||||
bool failed = false;
|
||||
std::string failure;
|
||||
|
||||
const size_t memory_budget = 1024ull * 1024ull * 1024ull;
|
||||
size_t reserved_bytes = 0;
|
||||
|
||||
std::mutex work_mutex;
|
||||
std::mutex progress_mutex;
|
||||
std::condition_variable memory_cv;
|
||||
std::vector<std::thread> workers;
|
||||
workers.reserve(n_threads);
|
||||
|
||||
auto reserve_memory = [&](size_t bytes) -> bool {
|
||||
std::unique_lock<std::mutex> lock(work_mutex);
|
||||
memory_cv.wait(lock, [&]() {
|
||||
return failed || reserved_bytes == 0 || reserved_bytes + bytes <= memory_budget;
|
||||
});
|
||||
if (failed) {
|
||||
return false;
|
||||
}
|
||||
ggml_set_name(tensor, name.c_str());
|
||||
|
||||
if (!tensor->data) {
|
||||
GGML_ASSERT(ggml_nelements(tensor) == 0);
|
||||
// Avoid crashing writers by setting a dummy pointer for zero-sized tensors.
|
||||
LOG_DEBUG("setting dummy pointer for zero-sized tensor %s", name.c_str());
|
||||
tensor->data = ggml_get_mem_buffer(ggml_ctx);
|
||||
}
|
||||
|
||||
TensorWriteInfo write_info;
|
||||
write_info.tensor = tensor;
|
||||
write_info.n_dims = tensor_storage.n_dims;
|
||||
for (int i = 0; i < tensor_storage.n_dims; ++i) {
|
||||
write_info.ne[i] = tensor_storage.ne[i];
|
||||
}
|
||||
|
||||
*dst_tensor = tensor;
|
||||
tensors.push_back(std::move(write_info));
|
||||
|
||||
reserved_bytes += bytes;
|
||||
return true;
|
||||
};
|
||||
|
||||
bool success = model_loader.load_tensors(on_new_tensor_cb);
|
||||
LOG_INFO("load tensors done");
|
||||
auto release_memory = [&](size_t bytes) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(work_mutex);
|
||||
reserved_bytes -= std::min(reserved_bytes, bytes);
|
||||
}
|
||||
memory_cv.notify_all();
|
||||
};
|
||||
|
||||
auto fail = [&](const std::string& message) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(work_mutex);
|
||||
if (!failed) {
|
||||
failed = true;
|
||||
failure = message;
|
||||
}
|
||||
}
|
||||
memory_cv.notify_all();
|
||||
};
|
||||
|
||||
for (int worker = 0; worker < n_threads; worker++) {
|
||||
workers.emplace_back([&]() {
|
||||
std::fstream output_file(output_path, std::ios::binary | std::ios::in | std::ios::out);
|
||||
if (!output_file.is_open()) {
|
||||
fail("failed to open output file '" + output_path + "' for tensor writing");
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
size_t tensor_index = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(work_mutex);
|
||||
if (failed || next_tensor_index >= tensors.size()) {
|
||||
return;
|
||||
}
|
||||
tensor_index = next_tensor_index++;
|
||||
}
|
||||
|
||||
const size_t tensor_bytes = export_tensor_nbytes(tensors[tensor_index]);
|
||||
if (!reserve_memory(tensor_bytes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TensorExportJob job;
|
||||
job.info = tensors[tensor_index];
|
||||
try {
|
||||
job.success = load_tensor_for_export(model_loader, job);
|
||||
} catch (const std::exception& e) {
|
||||
job.error = e.what();
|
||||
job.success = false;
|
||||
}
|
||||
|
||||
if (!job.success) {
|
||||
release_memory(tensor_bytes);
|
||||
fail(job.error.empty() ? "streaming conversion failed" : job.error);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string write_error;
|
||||
if (!writer.write_tensor(output_file,
|
||||
tensor_index,
|
||||
job.data.empty() ? nullptr : job.data.data(),
|
||||
job.data.size(),
|
||||
&write_error)) {
|
||||
release_memory(tensor_bytes);
|
||||
fail(write_error.empty() ? "streaming conversion write failed" : write_error);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(progress_mutex);
|
||||
bytes_written += job.data.size();
|
||||
tensors_written++;
|
||||
float elapsed_seconds = (ggml_time_ms() - start_time) / 1000.0f;
|
||||
pretty_bytes_progress(static_cast<int>(tensors_written),
|
||||
static_cast<int>(tensors.size()),
|
||||
bytes_written,
|
||||
elapsed_seconds);
|
||||
}
|
||||
release_memory(tensor_bytes);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& worker : workers) {
|
||||
worker.join();
|
||||
}
|
||||
printf("\n");
|
||||
if (failed) {
|
||||
if (error != nullptr) {
|
||||
*error = failure;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("streaming conversion completed, taking %.2fs", (ggml_time_ms() - start_time) / 1000.f);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool write_model_file_streaming(ModelLoader& model_loader,
|
||||
const std::string& output_path,
|
||||
const std::vector<TensorExportInfo>& tensors,
|
||||
StreamingModelWriter& writer,
|
||||
int n_threads,
|
||||
std::string* error) {
|
||||
std::vector<TensorWritePlan> plans = tensor_write_plans_from_export_infos(tensors);
|
||||
if (!writer.write_metadata(output_path, plans, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!preallocate_output_file(output_path, writer.file_size(), error)) {
|
||||
return false;
|
||||
}
|
||||
model_loader.process_model_files(false, false);
|
||||
return stream_tensor_data(model_loader, output_path, tensors, writer, n_threads, error);
|
||||
}
|
||||
|
||||
static bool init_convert_path(ModelLoader& model_loader, const char* path, const char* prefix, bool& loaded_any) {
|
||||
if (path == nullptr || strlen(path) == 0) {
|
||||
return true;
|
||||
}
|
||||
if (!model_loader.init_from_file(path, prefix)) {
|
||||
LOG_ERROR("init model loader from file failed: '%s'", path);
|
||||
return false;
|
||||
}
|
||||
loaded_any = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool export_loaded_model(ModelLoader& model_loader,
|
||||
const char* output_path,
|
||||
sd_type_t output_type,
|
||||
const char* tensor_type_rules,
|
||||
int n_threads) {
|
||||
ggml_type type = sd_type_to_ggml_type(output_type);
|
||||
bool output_is_safetensors = ends_with(output_path, ".safetensors");
|
||||
TensorTypeRules type_rules = parse_tensor_type_rules(tensor_type_rules);
|
||||
|
||||
std::vector<TensorExportInfo> tensors;
|
||||
bool success = collect_tensors_for_export(model_loader, type, type_rules, tensors);
|
||||
std::string error;
|
||||
if (success) {
|
||||
std::unique_ptr<StreamingModelWriter> writer;
|
||||
if (output_is_safetensors) {
|
||||
writer = std::make_unique<SafetensorsStreamingWriter>();
|
||||
} else {
|
||||
writer = std::make_unique<GGUFStreamingWriter>();
|
||||
}
|
||||
success = write_model_file_streaming(model_loader, output_path, tensors, *writer, n_threads, &error);
|
||||
}
|
||||
|
||||
if (!success && !error.empty()) {
|
||||
LOG_ERROR("%s", error.c_str());
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool convert_with_components(const char* model_path,
|
||||
const char* clip_l_path,
|
||||
const char* clip_g_path,
|
||||
const char* t5xxl_path,
|
||||
const char* diffusion_model_path,
|
||||
const char* vae_path,
|
||||
const char* output_path,
|
||||
sd_type_t output_type,
|
||||
const char* tensor_type_rules,
|
||||
bool convert_name,
|
||||
int n_threads) {
|
||||
ModelLoader model_loader;
|
||||
bool loaded_any = false;
|
||||
|
||||
if (!init_convert_path(model_loader, model_path, "", loaded_any) ||
|
||||
!init_convert_path(model_loader, clip_l_path, "text_encoders.clip_l.transformer.", loaded_any) ||
|
||||
!init_convert_path(model_loader, clip_g_path, "text_encoders.clip_g.transformer.", loaded_any) ||
|
||||
!init_convert_path(model_loader, t5xxl_path, "text_encoders.t5xxl.transformer.", loaded_any) ||
|
||||
!init_convert_path(model_loader, diffusion_model_path, "model.diffusion_model.", loaded_any) ||
|
||||
!init_convert_path(model_loader, vae_path, "vae.", loaded_any)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loaded_any) {
|
||||
LOG_ERROR("no input model path provided for convert");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (convert_name) {
|
||||
model_loader.convert_tensors_name();
|
||||
}
|
||||
|
||||
return export_loaded_model(model_loader, output_path, output_type, tensor_type_rules, n_threads);
|
||||
}
|
||||
|
||||
bool convert(const char* input_path,
|
||||
const char* vae_path,
|
||||
const char* output_path,
|
||||
sd_type_t output_type,
|
||||
const char* tensor_type_rules,
|
||||
bool convert_name) {
|
||||
ModelLoader model_loader;
|
||||
|
||||
if (!model_loader.init_from_file(input_path)) {
|
||||
LOG_ERROR("init model loader from file failed: '%s'", input_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vae_path != nullptr && strlen(vae_path) > 0) {
|
||||
if (!model_loader.init_from_file(vae_path, "vae.")) {
|
||||
LOG_ERROR("init model loader from file failed: '%s'", vae_path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (convert_name) {
|
||||
model_loader.convert_tensors_name();
|
||||
}
|
||||
|
||||
ggml_type type = (ggml_type)output_type;
|
||||
bool output_is_safetensors = ends_with(output_path, ".safetensors");
|
||||
TensorTypeRules type_rules = parse_tensor_type_rules(tensor_type_rules);
|
||||
|
||||
auto backend = ggml_backend_cpu_init();
|
||||
size_t mem_size = 1 * 1024 * 1024; // for padding
|
||||
mem_size += model_loader.get_tensor_storage_map().size() * ggml_tensor_overhead();
|
||||
mem_size += model_loader.get_params_mem_size(backend, type);
|
||||
LOG_INFO("model tensors mem size: %.2fMB", mem_size / 1024.f / 1024.f);
|
||||
ggml_context* ggml_ctx = ggml_init({mem_size, nullptr, false});
|
||||
|
||||
if (ggml_ctx == nullptr) {
|
||||
LOG_ERROR("ggml_init failed for converter");
|
||||
ggml_backend_free(backend);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<TensorWriteInfo> tensors;
|
||||
bool success = load_tensors_for_export(model_loader, ggml_ctx, type, type_rules, tensors);
|
||||
ggml_backend_free(backend);
|
||||
|
||||
std::string error;
|
||||
if (success) {
|
||||
if (output_is_safetensors) {
|
||||
success = write_safetensors_file(output_path, tensors, &error);
|
||||
} else {
|
||||
success = write_gguf_file(output_path, tensors, &error);
|
||||
}
|
||||
}
|
||||
|
||||
if (!success && !error.empty()) {
|
||||
LOG_ERROR("%s", error.c_str());
|
||||
}
|
||||
|
||||
ggml_free(ggml_ctx);
|
||||
return success;
|
||||
return convert_with_components(input_path,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
vae_path,
|
||||
output_path,
|
||||
output_type,
|
||||
tensor_type_rules,
|
||||
convert_name,
|
||||
0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
#include "backend_fit.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "core/ggml_extend_backend.h"
|
||||
#include "core/util.h"
|
||||
#include "ggml-backend.h"
|
||||
|
||||
namespace sd::backend_fit {
|
||||
namespace {
|
||||
|
||||
constexpr int64_t MiB = 1024ll * 1024;
|
||||
|
||||
enum class ComponentKind {
|
||||
DIT = 0,
|
||||
VAE = 1,
|
||||
CONDITIONER = 2,
|
||||
};
|
||||
|
||||
struct Component {
|
||||
ComponentKind kind;
|
||||
const char* name;
|
||||
int64_t params_bytes = 0;
|
||||
int64_t reserve_bytes = 0;
|
||||
bool splittable = false;
|
||||
};
|
||||
|
||||
struct Device {
|
||||
ggml_backend_dev_t dev = nullptr;
|
||||
std::string name;
|
||||
std::string description;
|
||||
int64_t free_bytes = 0;
|
||||
int64_t total_bytes = 0;
|
||||
int64_t budget_bytes = 0;
|
||||
};
|
||||
|
||||
struct Decision {
|
||||
ComponentKind kind;
|
||||
bool on_cpu = false;
|
||||
std::vector<size_t> device_idxs;
|
||||
};
|
||||
|
||||
struct Plan {
|
||||
bool valid = false;
|
||||
bool time_share = false;
|
||||
std::vector<Decision> decisions;
|
||||
};
|
||||
|
||||
bool classify_tensor(const std::string& name, ComponentKind& out) {
|
||||
auto contains = [&](const char* s) { return name.find(s) != std::string::npos; };
|
||||
|
||||
if (contains("model.diffusion_model.") || contains("unet.")) {
|
||||
out = ComponentKind::DIT;
|
||||
return true;
|
||||
}
|
||||
if (contains("first_stage_model.") ||
|
||||
name.rfind("vae.", 0) == 0 ||
|
||||
name.rfind("tae.", 0) == 0) {
|
||||
out = ComponentKind::VAE;
|
||||
return true;
|
||||
}
|
||||
if (contains("text_encoders") ||
|
||||
contains("cond_stage_model") ||
|
||||
contains("te.text_model.") ||
|
||||
contains("conditioner") ||
|
||||
name.rfind("text_encoder.", 0) == 0 ||
|
||||
name.rfind("text_embedding_projection.", 0) == 0 ||
|
||||
contains(".aggregate_embed.")) {
|
||||
out = ComponentKind::CONDITIONER;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<Component> estimate_components(ModelLoader& loader, ggml_type override_wtype) {
|
||||
const auto& storage = loader.get_tensor_storage_map();
|
||||
|
||||
int64_t bytes[3] = {0, 0, 0};
|
||||
for (const auto& [name, ts_const] : storage) {
|
||||
TensorStorage ts = ts_const;
|
||||
if (is_unused_tensor(ts.name)) {
|
||||
continue;
|
||||
}
|
||||
ComponentKind kind;
|
||||
if (!classify_tensor(ts.name, kind)) {
|
||||
continue;
|
||||
}
|
||||
if (override_wtype != GGML_TYPE_COUNT &&
|
||||
loader.tensor_should_be_converted(ts, override_wtype)) {
|
||||
ts.type = override_wtype;
|
||||
} else if (ts.expected_type != GGML_TYPE_COUNT && ts.expected_type != ts.type) {
|
||||
ts.type = ts.expected_type;
|
||||
}
|
||||
bytes[int(kind)] += (int64_t)ts.nbytes() + 64;
|
||||
}
|
||||
|
||||
std::vector<Component> out;
|
||||
out.push_back({ComponentKind::DIT, "DiT", bytes[int(ComponentKind::DIT)], 2048 * MiB, true});
|
||||
out.push_back({ComponentKind::VAE, "VAE", bytes[int(ComponentKind::VAE)], 1024 * MiB, false});
|
||||
out.push_back({ComponentKind::CONDITIONER, "Conditioner", bytes[int(ComponentKind::CONDITIONER)], 2048 * MiB, true});
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<Device> enumerate_gpu_devices(const sd::ggml_graph_cut::MaxVramAssignment& budgets) {
|
||||
std::vector<Device> out;
|
||||
for (size_t i = 0; i < ggml_backend_dev_count(); i++) {
|
||||
ggml_backend_dev_t dev = ggml_backend_dev_get(i);
|
||||
if (ggml_backend_dev_type(dev) != GGML_BACKEND_DEVICE_TYPE_GPU) {
|
||||
continue;
|
||||
}
|
||||
Device d;
|
||||
d.dev = dev;
|
||||
d.name = ggml_backend_dev_name(dev);
|
||||
d.description = ggml_backend_dev_description(dev);
|
||||
size_t free_bytes = 0, total_bytes = 0;
|
||||
ggml_backend_dev_memory(dev, &free_bytes, &total_bytes);
|
||||
d.free_bytes = (int64_t)free_bytes;
|
||||
d.total_bytes = (int64_t)total_bytes;
|
||||
|
||||
std::string budget_key = d.name;
|
||||
std::transform(budget_key.begin(), budget_key.end(), budget_key.begin(),
|
||||
[](unsigned char c) { return (char)std::tolower(c); });
|
||||
float gib = budgets.default_gib;
|
||||
auto it = budgets.backend_gib.find(budget_key);
|
||||
if (it != budgets.backend_gib.end()) {
|
||||
gib = it->second;
|
||||
}
|
||||
if (gib > 0.f) {
|
||||
d.budget_bytes = std::min<int64_t>((int64_t)(gib * 1024.0 * 1024.0 * 1024.0), d.free_bytes);
|
||||
} else if (gib < 0.f) {
|
||||
d.budget_bytes = d.free_bytes + (int64_t)(gib * 1024.0 * 1024.0 * 1024.0);
|
||||
} else {
|
||||
d.budget_bytes = d.free_bytes - 512 * MiB;
|
||||
}
|
||||
d.budget_bytes = std::max<int64_t>(d.budget_bytes, 0);
|
||||
out.push_back(d);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Plan compute_plan(const std::vector<Component>& components, const std::vector<Device>& devices) {
|
||||
Plan plan;
|
||||
if (devices.empty()) {
|
||||
return plan;
|
||||
}
|
||||
|
||||
std::vector<size_t> order(components.size());
|
||||
for (size_t i = 0; i < order.size(); i++) {
|
||||
order[i] = i;
|
||||
}
|
||||
std::sort(order.begin(), order.end(), [&](size_t a, size_t b) {
|
||||
return components[a].params_bytes > components[b].params_bytes;
|
||||
});
|
||||
|
||||
{
|
||||
std::vector<int64_t> params_sum(devices.size(), 0);
|
||||
std::vector<int64_t> max_reserve(devices.size(), 0);
|
||||
std::vector<Decision> decisions(components.size());
|
||||
bool ok = true;
|
||||
for (size_t ci : order) {
|
||||
const Component& comp = components[ci];
|
||||
decisions[ci].kind = comp.kind;
|
||||
if (comp.params_bytes == 0) {
|
||||
continue;
|
||||
}
|
||||
int best = -1;
|
||||
for (size_t di = 0; di < devices.size(); di++) {
|
||||
int64_t need = params_sum[di] + comp.params_bytes + std::max(max_reserve[di], comp.reserve_bytes);
|
||||
if (need <= devices[di].budget_bytes &&
|
||||
(best < 0 || devices[di].budget_bytes - params_sum[di] > devices[best].budget_bytes - params_sum[best])) {
|
||||
best = (int)di;
|
||||
}
|
||||
}
|
||||
if (best < 0) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
params_sum[best] += comp.params_bytes;
|
||||
max_reserve[best] = std::max(max_reserve[best], comp.reserve_bytes);
|
||||
decisions[ci].device_idxs.push_back((size_t)best);
|
||||
}
|
||||
if (ok) {
|
||||
plan.valid = true;
|
||||
plan.time_share = false;
|
||||
plan.decisions = std::move(decisions);
|
||||
return plan;
|
||||
}
|
||||
}
|
||||
|
||||
plan.decisions.assign(components.size(), {});
|
||||
for (size_t ci : order) {
|
||||
const Component& comp = components[ci];
|
||||
Decision& decision = plan.decisions[ci];
|
||||
decision.kind = comp.kind;
|
||||
if (comp.params_bytes == 0) {
|
||||
continue;
|
||||
}
|
||||
int best = -1;
|
||||
for (size_t di = 0; di < devices.size(); di++) {
|
||||
if (comp.params_bytes + comp.reserve_bytes <= devices[di].budget_bytes &&
|
||||
(best < 0 || devices[di].budget_bytes > devices[best].budget_bytes)) {
|
||||
best = (int)di;
|
||||
}
|
||||
}
|
||||
if (best >= 0) {
|
||||
decision.device_idxs.push_back((size_t)best);
|
||||
continue;
|
||||
}
|
||||
if (comp.splittable && devices.size() > 1) {
|
||||
int64_t capacity = 0;
|
||||
for (const Device& d : devices) {
|
||||
capacity += std::max<int64_t>(d.budget_bytes - comp.reserve_bytes, 0);
|
||||
}
|
||||
if (comp.params_bytes <= capacity) {
|
||||
std::vector<size_t> idxs(devices.size());
|
||||
for (size_t i = 0; i < idxs.size(); i++) {
|
||||
idxs[i] = i;
|
||||
}
|
||||
std::sort(idxs.begin(), idxs.end(), [&](size_t a, size_t b) {
|
||||
return devices[a].budget_bytes > devices[b].budget_bytes;
|
||||
});
|
||||
decision.device_idxs = std::move(idxs);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
decision.on_cpu = true;
|
||||
}
|
||||
plan.valid = true;
|
||||
plan.time_share = true;
|
||||
return plan;
|
||||
}
|
||||
|
||||
void print_plan(const Plan& plan,
|
||||
const std::vector<Component>& components,
|
||||
const std::vector<Device>& devices) {
|
||||
LOG_INFO("auto-fit plan%s:", plan.time_share ? " (time-share: params load per phase and free after)" : "");
|
||||
LOG_INFO(" devices:");
|
||||
for (const Device& d : devices) {
|
||||
LOG_INFO(" %-12s %-32s free %6lld MiB, budget %6lld MiB",
|
||||
d.name.c_str(), d.description.c_str(),
|
||||
(long long)(d.free_bytes / MiB), (long long)(d.budget_bytes / MiB));
|
||||
}
|
||||
LOG_INFO(" components:");
|
||||
for (size_t ci = 0; ci < components.size(); ci++) {
|
||||
const Component& comp = components[ci];
|
||||
const Decision& decision = plan.decisions[ci];
|
||||
std::string target;
|
||||
if (comp.params_bytes == 0) {
|
||||
target = "(not present)";
|
||||
} else if (decision.on_cpu) {
|
||||
target = "CPU";
|
||||
} else {
|
||||
for (size_t k = 0; k < decision.device_idxs.size(); k++) {
|
||||
if (k > 0) {
|
||||
target += " & ";
|
||||
}
|
||||
target += devices[decision.device_idxs[k]].name;
|
||||
}
|
||||
if (decision.device_idxs.size() > 1) {
|
||||
target += " (split)";
|
||||
}
|
||||
}
|
||||
LOG_INFO(" %-12s params %6lld MiB, compute reserve %5lld MiB -> %s",
|
||||
comp.name,
|
||||
(long long)(comp.params_bytes / MiB),
|
||||
(long long)(comp.reserve_bytes / MiB),
|
||||
target.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void append_assignment(std::string& spec, const char* key, const std::string& value) {
|
||||
if (!spec.empty()) {
|
||||
spec += ",";
|
||||
}
|
||||
spec += key;
|
||||
spec += "=";
|
||||
spec += value;
|
||||
}
|
||||
|
||||
void append_component_decision(const std::vector<Component>& components,
|
||||
const std::vector<Device>& devices,
|
||||
const Plan& plan,
|
||||
ComponentKind kind,
|
||||
const char* module_key,
|
||||
std::string& runtime_spec,
|
||||
std::string& params_spec) {
|
||||
for (size_t ci = 0; ci < components.size(); ci++) {
|
||||
if (components[ci].kind != kind || components[ci].params_bytes == 0) {
|
||||
continue;
|
||||
}
|
||||
const Decision& decision = plan.decisions[ci];
|
||||
if (decision.on_cpu) {
|
||||
append_assignment(runtime_spec, module_key, "cpu");
|
||||
return;
|
||||
}
|
||||
if (decision.device_idxs.empty()) {
|
||||
return;
|
||||
}
|
||||
std::string device_list;
|
||||
for (size_t k = 0; k < decision.device_idxs.size(); k++) {
|
||||
if (k > 0) {
|
||||
device_list += "&";
|
||||
}
|
||||
device_list += devices[decision.device_idxs[k]].name;
|
||||
}
|
||||
append_assignment(runtime_spec, module_key, device_list);
|
||||
if (plan.time_share) {
|
||||
append_assignment(params_spec, module_key, "disk");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool derive_backend_specs(ModelLoader& loader,
|
||||
ggml_type override_wtype,
|
||||
sd::ggml_graph_cut::MaxVramAssignment& budgets,
|
||||
std::string& runtime_spec,
|
||||
std::string& params_spec) {
|
||||
if (!runtime_spec.empty() || !params_spec.empty()) {
|
||||
LOG_WARN("--auto-fit is enabled; ignoring --backend / --params-backend");
|
||||
}
|
||||
|
||||
{
|
||||
std::string error;
|
||||
if (!budgets.canonicalize_backend_keys(&error)) {
|
||||
LOG_ERROR("%s", error.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto components = estimate_components(loader, override_wtype);
|
||||
auto devices = enumerate_gpu_devices(budgets);
|
||||
auto plan = compute_plan(components, devices);
|
||||
if (!plan.valid) {
|
||||
LOG_WARN("auto-fit: no usable GPU devices; using the default backend");
|
||||
runtime_spec.clear();
|
||||
params_spec.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
print_plan(plan, components, devices);
|
||||
|
||||
std::string derived_runtime_spec;
|
||||
std::string derived_params_spec;
|
||||
append_component_decision(components, devices, plan, ComponentKind::DIT, "diffusion", derived_runtime_spec, derived_params_spec);
|
||||
append_component_decision(components, devices, plan, ComponentKind::CONDITIONER, "te", derived_runtime_spec, derived_params_spec);
|
||||
append_component_decision(components, devices, plan, ComponentKind::VAE, "vae", derived_runtime_spec, derived_params_spec);
|
||||
|
||||
runtime_spec = std::move(derived_runtime_spec);
|
||||
params_spec = std::move(derived_params_spec);
|
||||
|
||||
LOG_INFO("auto-fit: --backend \"%s\"%s%s%s",
|
||||
runtime_spec.empty() ? "(default)" : runtime_spec.c_str(),
|
||||
params_spec.empty() ? "" : " --params-backend \"",
|
||||
params_spec.c_str(),
|
||||
params_spec.empty() ? "" : "\"");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool prepare_vae_decode_retry_tiling(sd_tiling_params_t& tiling_params, bool prefer_temporal_tiling) {
|
||||
if (prefer_temporal_tiling) {
|
||||
if (tiling_params.temporal_tiling) {
|
||||
return false;
|
||||
}
|
||||
tiling_params.temporal_tiling = true;
|
||||
} else {
|
||||
if (tiling_params.enabled) {
|
||||
return false;
|
||||
}
|
||||
tiling_params.enabled = true;
|
||||
if (tiling_params.tile_size_x <= 0) {
|
||||
tiling_params.tile_size_x = 256;
|
||||
}
|
||||
if (tiling_params.tile_size_y <= 0) {
|
||||
tiling_params.tile_size_y = 256;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_WARN("auto-fit: VAE decode failed (likely out of memory); retrying with %s tiling",
|
||||
tiling_params.temporal_tiling ? "temporal" : "spatial");
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace sd::backend_fit
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef __SD_BACKEND_FIT_H__
|
||||
#define __SD_BACKEND_FIT_H__
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core/ggml_graph_cut.h"
|
||||
#include "model_loader.h"
|
||||
#include "stable-diffusion.h"
|
||||
|
||||
namespace sd::backend_fit {
|
||||
|
||||
bool derive_backend_specs(ModelLoader& loader,
|
||||
ggml_type override_wtype,
|
||||
sd::ggml_graph_cut::MaxVramAssignment& budgets,
|
||||
std::string& runtime_spec,
|
||||
std::string& params_spec);
|
||||
|
||||
bool prepare_vae_decode_retry_tiling(sd_tiling_params_t& tiling_params,
|
||||
bool prefer_temporal_tiling);
|
||||
|
||||
} // namespace sd::backend_fit
|
||||
|
||||
#endif // __SD_BACKEND_FIT_H__
|
||||
@@ -0,0 +1,961 @@
|
||||
#include "core/ggml_extend_backend.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "core/util.h"
|
||||
#include "ggml/src/ggml-impl.h"
|
||||
#include "stable-diffusion.h"
|
||||
|
||||
static std::string trim_copy(const std::string& value) {
|
||||
size_t begin = 0;
|
||||
while (begin < value.size() && std::isspace(static_cast<unsigned char>(value[begin]))) {
|
||||
++begin;
|
||||
}
|
||||
size_t end = value.size();
|
||||
while (end > begin && std::isspace(static_cast<unsigned char>(value[end - 1]))) {
|
||||
--end;
|
||||
}
|
||||
return value.substr(begin, end - begin);
|
||||
}
|
||||
|
||||
static std::string lower_copy(std::string value) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) {
|
||||
return static_cast<char>(std::tolower(c));
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
static std::vector<std::string> split_copy(const std::string& value, char delimiter) {
|
||||
std::vector<std::string> parts;
|
||||
std::string part;
|
||||
std::istringstream stream(value);
|
||||
while (std::getline(stream, part, delimiter)) {
|
||||
parts.push_back(part);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
static bool is_default_backend_token(const std::string& name) {
|
||||
const std::string lower = lower_copy(trim_copy(name));
|
||||
return lower.empty() || lower == "default" || lower == "auto";
|
||||
}
|
||||
|
||||
static bool is_disk_backend_token(const std::string& name) {
|
||||
return lower_copy(trim_copy(name)) == "disk";
|
||||
}
|
||||
|
||||
static bool parse_backend_module(const std::string& raw_name, SDBackendModule* module) {
|
||||
std::string name = lower_copy(trim_copy(raw_name));
|
||||
name.erase(std::remove(name.begin(), name.end(), '-'), name.end());
|
||||
name.erase(std::remove(name.begin(), name.end(), '_'), name.end());
|
||||
|
||||
if (name == "diffusion" || name == "model" || name == "unet" || name == "dit") {
|
||||
*module = SDBackendModule::DIFFUSION;
|
||||
return true;
|
||||
}
|
||||
if (name == "te" || name == "clip" || name == "text" || name == "textencoder" || name == "textencoders" || name == "conditioner" || name == "cond" || name == "llm" || name == "t5" || name == "t5xxl") {
|
||||
*module = SDBackendModule::TE;
|
||||
return true;
|
||||
}
|
||||
if (name == "clipvision" || name == "vision") {
|
||||
*module = SDBackendModule::CLIP_VISION;
|
||||
return true;
|
||||
}
|
||||
if (name == "vae" || name == "firststage" || name == "autoencoder" || name == "tae") {
|
||||
*module = SDBackendModule::VAE;
|
||||
return true;
|
||||
}
|
||||
if (name == "controlnet" || name == "control") {
|
||||
*module = SDBackendModule::CONTROL_NET;
|
||||
return true;
|
||||
}
|
||||
if (name == "photomaker" || name == "photomakerid" || name == "pmid" || name == "photo") {
|
||||
*module = SDBackendModule::PHOTOMAKER;
|
||||
return true;
|
||||
}
|
||||
if (name == "upscaler" || name == "esrgan" || name == "hires") {
|
||||
*module = SDBackendModule::UPSCALER;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::string module_assignment_name(const SDBackendAssignment& assignment, SDBackendModule module) {
|
||||
auto it = assignment.module_names.find(module);
|
||||
if (it != assignment.module_names.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return assignment.default_name;
|
||||
}
|
||||
|
||||
static std::string backend_cache_key(ggml_backend_t backend) {
|
||||
if (backend == nullptr) {
|
||||
return "";
|
||||
}
|
||||
ggml_backend_dev_t dev = ggml_backend_get_device(backend);
|
||||
if (dev != nullptr) {
|
||||
return lower_copy(ggml_backend_dev_name(dev));
|
||||
}
|
||||
const char* backend_name = ggml_backend_name(backend);
|
||||
return backend_name != nullptr ? lower_copy(backend_name) : "";
|
||||
}
|
||||
|
||||
static std::string resolve_first_device_by_type(enum ggml_backend_dev_type type) {
|
||||
ggml_backend_dev_t dev = ggml_backend_dev_by_type(type);
|
||||
if (dev == nullptr) {
|
||||
return "";
|
||||
}
|
||||
const char* dev_name = ggml_backend_dev_name(dev);
|
||||
if (dev_name != nullptr && dev_name[0] != '\0') {
|
||||
return dev_name;
|
||||
}
|
||||
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(dev);
|
||||
const char* reg_name = reg != nullptr ? ggml_backend_reg_name(reg) : nullptr;
|
||||
return reg_name != nullptr ? reg_name : "";
|
||||
}
|
||||
|
||||
static ggml_backend_dev_t resolve_first_device_by_registry_name(const std::string& name) {
|
||||
std::string lower = lower_copy(trim_copy(name));
|
||||
if (lower == "metal") {
|
||||
lower = "mtl";
|
||||
}
|
||||
if (lower.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const size_t device_count = ggml_backend_dev_count();
|
||||
for (size_t i = 0; i < device_count; ++i) {
|
||||
ggml_backend_dev_t dev = ggml_backend_dev_get(i);
|
||||
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(dev);
|
||||
if (reg == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const char* reg_name = ggml_backend_reg_name(reg);
|
||||
if (reg_name != nullptr && lower_copy(reg_name) == lower) {
|
||||
return dev;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static ggml_backend_dev_t resolve_device_by_name(const std::string& name) {
|
||||
const std::string lower = lower_copy(trim_copy(name));
|
||||
if (lower.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const size_t device_count = ggml_backend_dev_count();
|
||||
for (size_t i = 0; i < device_count; ++i) {
|
||||
ggml_backend_dev_t dev = ggml_backend_dev_get(i);
|
||||
const char* dev_name = ggml_backend_dev_name(dev);
|
||||
if (dev_name != nullptr && lower_copy(dev_name) == lower) {
|
||||
return dev;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::string backend_device_name(ggml_backend_dev_t dev) {
|
||||
if (dev == nullptr) {
|
||||
return "";
|
||||
}
|
||||
const char* name = ggml_backend_dev_name(dev);
|
||||
if (name != nullptr && name[0] != '\0') {
|
||||
return name;
|
||||
}
|
||||
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(dev);
|
||||
const char* reg_name = reg != nullptr ? ggml_backend_reg_name(reg) : nullptr;
|
||||
return reg_name != nullptr ? reg_name : "";
|
||||
}
|
||||
|
||||
static ggml_backend_buffer_t ggml_backend_tensor_buffer(const struct ggml_tensor* tensor) {
|
||||
if (tensor == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return tensor->view_src ? tensor->view_src->buffer : tensor->buffer;
|
||||
}
|
||||
|
||||
static bool ggml_backend_tensor_is_host_accessible(const struct ggml_tensor* tensor) {
|
||||
if (tensor == nullptr || tensor->data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ggml_backend_buffer_t buffer = ggml_backend_tensor_buffer(tensor);
|
||||
return buffer == nullptr || ggml_backend_buffer_is_host(buffer);
|
||||
}
|
||||
|
||||
static size_t ggml_backend_tensor_offset(const struct ggml_tensor* tensor, int64_t i0, int64_t i1, int64_t i2, int64_t i3) {
|
||||
return static_cast<size_t>(i0 * tensor->nb[0] + i1 * tensor->nb[1] + i2 * tensor->nb[2] + i3 * tensor->nb[3]);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void ggml_backend_tensor_write_scalar(const struct ggml_tensor* tensor, int64_t i0, int64_t i1, int64_t i2, int64_t i3, T value) {
|
||||
const size_t offset = ggml_backend_tensor_offset(tensor, i0, i1, i2, i3);
|
||||
|
||||
if (ggml_backend_tensor_is_host_accessible(tensor)) {
|
||||
auto* dst = reinterpret_cast<T*>(reinterpret_cast<char*>(tensor->data) + offset);
|
||||
*dst = value;
|
||||
return;
|
||||
}
|
||||
|
||||
ggml_backend_tensor_set(const_cast<struct ggml_tensor*>(tensor), &value, offset, sizeof(T));
|
||||
}
|
||||
|
||||
static void ggml_set_f32_nd(const struct ggml_tensor* tensor, int64_t i0, int64_t i1, int64_t i2, int64_t i3, float value) {
|
||||
switch (tensor->type) {
|
||||
case GGML_TYPE_I8:
|
||||
ggml_backend_tensor_write_scalar(tensor, i0, i1, i2, i3, static_cast<int8_t>(value));
|
||||
break;
|
||||
case GGML_TYPE_I16:
|
||||
ggml_backend_tensor_write_scalar(tensor, i0, i1, i2, i3, static_cast<int16_t>(value));
|
||||
break;
|
||||
case GGML_TYPE_I32:
|
||||
ggml_backend_tensor_write_scalar(tensor, i0, i1, i2, i3, static_cast<int32_t>(value));
|
||||
break;
|
||||
case GGML_TYPE_F16:
|
||||
ggml_backend_tensor_write_scalar(tensor, i0, i1, i2, i3, ggml_fp32_to_fp16(value));
|
||||
break;
|
||||
case GGML_TYPE_BF16:
|
||||
ggml_backend_tensor_write_scalar(tensor, i0, i1, i2, i3, ggml_fp32_to_bf16(value));
|
||||
break;
|
||||
case GGML_TYPE_F32:
|
||||
ggml_backend_tensor_write_scalar(tensor, i0, i1, i2, i3, value);
|
||||
break;
|
||||
default:
|
||||
GGML_ABORT("fatal error");
|
||||
}
|
||||
}
|
||||
|
||||
void ggml_ext_im_set_f32_1d(const struct ggml_tensor* tensor, int i, float value) {
|
||||
if (!ggml_is_contiguous(tensor)) {
|
||||
int64_t id[4] = {0, 0, 0, 0};
|
||||
ggml_unravel_index(tensor, i, &id[0], &id[1], &id[2], &id[3]);
|
||||
ggml_set_f32_nd(tensor, id[0], id[1], id[2], id[3], value);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (tensor->type) {
|
||||
case GGML_TYPE_I8:
|
||||
ggml_backend_tensor_write_scalar(tensor, i, 0, 0, 0, static_cast<int8_t>(value));
|
||||
break;
|
||||
case GGML_TYPE_I16:
|
||||
ggml_backend_tensor_write_scalar(tensor, i, 0, 0, 0, static_cast<int16_t>(value));
|
||||
break;
|
||||
case GGML_TYPE_I32:
|
||||
ggml_backend_tensor_write_scalar(tensor, i, 0, 0, 0, static_cast<int32_t>(value));
|
||||
break;
|
||||
case GGML_TYPE_F16:
|
||||
ggml_backend_tensor_write_scalar(tensor, i, 0, 0, 0, ggml_fp32_to_fp16(value));
|
||||
break;
|
||||
case GGML_TYPE_BF16:
|
||||
ggml_backend_tensor_write_scalar(tensor, i, 0, 0, 0, ggml_fp32_to_bf16(value));
|
||||
break;
|
||||
case GGML_TYPE_F32:
|
||||
ggml_backend_tensor_write_scalar(tensor, i, 0, 0, 0, value);
|
||||
break;
|
||||
default:
|
||||
GGML_ABORT("fatal error");
|
||||
}
|
||||
}
|
||||
|
||||
bool add_rpc_devices(const std::string& servers) {
|
||||
const std::string in = trim_copy(servers);
|
||||
if (in.empty()) {
|
||||
return true;
|
||||
}
|
||||
auto rpc_servers = split_copy(in, ',');
|
||||
if (rpc_servers.empty()) {
|
||||
LOG_ERROR("invalid RPC servers specification: '%s'", servers.c_str());
|
||||
return false;
|
||||
}
|
||||
ggml_backend_reg_t rpc_reg = ggml_backend_reg_by_name("RPC");
|
||||
if (!rpc_reg) {
|
||||
LOG_ERROR("RPC backend not found, cannot add RPC servers");
|
||||
return false;
|
||||
}
|
||||
typedef ggml_backend_reg_t (*ggml_backend_rpc_add_server_t)(const char* endpoint);
|
||||
ggml_backend_rpc_add_server_t ggml_backend_rpc_add_server_fn = (ggml_backend_rpc_add_server_t)ggml_backend_reg_get_proc_address(rpc_reg, "ggml_backend_rpc_add_server");
|
||||
if (!ggml_backend_rpc_add_server_fn) {
|
||||
LOG_ERROR("RPC backend does not have ggml_backend_rpc_add_server function, cannot add RPC servers");
|
||||
return false;
|
||||
}
|
||||
for (const auto& server : rpc_servers) {
|
||||
LOG_INFO("Adding RPC server: %s", server.c_str());
|
||||
auto reg = ggml_backend_rpc_add_server_fn(server.c_str());
|
||||
// no return value to check for success but should print errors from the RPC backend if it fails to add the server
|
||||
ggml_backend_register(reg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ggml_backend_load_all_once() {
|
||||
// If the registry already has devices and the CPU backend is present,
|
||||
// assume either static registration or explicit host-side preloading has
|
||||
// completed and avoid rescanning the default paths.
|
||||
if (ggml_backend_dev_count() > 0 && ggml_backend_reg_by_name("CPU") != nullptr) {
|
||||
return;
|
||||
}
|
||||
// In dynamic-backend mode the backend modules are discovered at runtime,
|
||||
// so we must load them before asking for the CPU backend or its proc table.
|
||||
// If the host preloaded only a subset of backends, allow one default-path
|
||||
// scan so missing modules can still be discovered.
|
||||
static std::once_flag once;
|
||||
std::call_once(once, []() {
|
||||
if (ggml_backend_dev_count() > 0 && ggml_backend_reg_by_name("CPU") != nullptr) {
|
||||
return;
|
||||
}
|
||||
ggml_backend_load_all();
|
||||
});
|
||||
}
|
||||
|
||||
bool sd_backend_is(ggml_backend_t backend, const std::string& name) {
|
||||
if (!backend) {
|
||||
return false;
|
||||
}
|
||||
ggml_backend_dev_t dev = ggml_backend_get_device(backend);
|
||||
if (!dev) {
|
||||
return false;
|
||||
}
|
||||
std::string dev_name = ggml_backend_dev_name(dev);
|
||||
return lower_copy(dev_name).find(lower_copy(name)) != std::string::npos;
|
||||
}
|
||||
|
||||
static std::string get_default_backend_name() {
|
||||
ggml_backend_load_all_once();
|
||||
// should pick the same backend preference as ggml_backend_init_best
|
||||
std::string name = resolve_first_device_by_type(GGML_BACKEND_DEVICE_TYPE_GPU);
|
||||
if (!name.empty()) {
|
||||
return name;
|
||||
}
|
||||
name = resolve_first_device_by_type(GGML_BACKEND_DEVICE_TYPE_IGPU);
|
||||
if (!name.empty()) {
|
||||
return name;
|
||||
}
|
||||
return resolve_first_device_by_type(GGML_BACKEND_DEVICE_TYPE_CPU);
|
||||
}
|
||||
|
||||
std::string sd_backend_resolve_name(const std::string& name) {
|
||||
ggml_backend_load_all_once();
|
||||
std::string requested = trim_copy(name);
|
||||
std::string lower = lower_copy(requested);
|
||||
|
||||
if (is_default_backend_token(lower)) {
|
||||
return get_default_backend_name();
|
||||
}
|
||||
if (lower == "gpu") {
|
||||
std::string result = resolve_first_device_by_type(GGML_BACKEND_DEVICE_TYPE_GPU);
|
||||
if (!result.empty()) {
|
||||
return result;
|
||||
}
|
||||
return resolve_first_device_by_type(GGML_BACKEND_DEVICE_TYPE_IGPU);
|
||||
}
|
||||
|
||||
if (ggml_backend_dev_t dev = resolve_first_device_by_registry_name(requested)) {
|
||||
return backend_device_name(dev);
|
||||
}
|
||||
|
||||
const size_t device_count = ggml_backend_dev_count();
|
||||
for (size_t i = 0; i < device_count; ++i) {
|
||||
ggml_backend_dev_t dev = ggml_backend_dev_get(i);
|
||||
std::string dev_name = ggml_backend_dev_name(dev);
|
||||
if (lower_copy(dev_name) == lower) {
|
||||
return dev_name;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < device_count; ++i) {
|
||||
ggml_backend_dev_t dev = ggml_backend_dev_get(i);
|
||||
std::string dev_name = ggml_backend_dev_name(dev);
|
||||
std::string dev_lower = lower_copy(dev_name);
|
||||
if (dev_lower.rfind(lower, 0) == 0) {
|
||||
return dev_name;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static bool backend_name_exists(const std::string& name) {
|
||||
return !sd_backend_resolve_name(name).empty();
|
||||
}
|
||||
|
||||
static ggml_backend_t init_named_backend(const std::string& name) {
|
||||
ggml_backend_load_all_once();
|
||||
LOG_DEBUG("Initializing backend: %s", name.c_str());
|
||||
if (trim_copy(name).empty()) {
|
||||
return ggml_backend_init_best();
|
||||
}
|
||||
|
||||
if (ggml_backend_dev_t dev = resolve_device_by_name(name)) {
|
||||
return ggml_backend_dev_init(dev, nullptr);
|
||||
}
|
||||
if (ggml_backend_dev_t dev = resolve_first_device_by_registry_name(name)) {
|
||||
return ggml_backend_dev_init(dev, nullptr);
|
||||
}
|
||||
|
||||
std::string resolved = sd_backend_resolve_name(name);
|
||||
if (ggml_backend_dev_t dev = resolve_device_by_name(resolved)) {
|
||||
return ggml_backend_dev_init(dev, nullptr);
|
||||
}
|
||||
if (ggml_backend_dev_t dev = resolve_first_device_by_registry_name(resolved)) {
|
||||
return ggml_backend_dev_init(dev, nullptr);
|
||||
}
|
||||
if (resolved.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return ggml_backend_init_by_name(resolved.c_str(), nullptr);
|
||||
}
|
||||
|
||||
bool sd_backend_is_cpu(ggml_backend_t backend) {
|
||||
if (backend == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto dev = ggml_backend_get_device(backend);
|
||||
return dev != nullptr && ggml_backend_dev_type(dev) == GGML_BACKEND_DEVICE_TYPE_CPU;
|
||||
}
|
||||
|
||||
ggml_backend_t sd_backend_cpu_init() {
|
||||
ggml_backend_load_all_once();
|
||||
return ggml_backend_init_by_type(GGML_BACKEND_DEVICE_TYPE_CPU, nullptr);
|
||||
}
|
||||
|
||||
bool sd_backend_cpu_set_n_threads(ggml_backend_t backend, int n_threads) {
|
||||
if (backend == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto dev = ggml_backend_get_device(backend);
|
||||
if (dev != nullptr && ggml_backend_dev_type(dev) == GGML_BACKEND_DEVICE_TYPE_CPU) {
|
||||
auto reg = ggml_backend_dev_backend_reg(dev);
|
||||
auto ggml_backend_set_n_threads_fn = (ggml_backend_set_n_threads_t)ggml_backend_reg_get_proc_address(reg, "ggml_backend_set_n_threads");
|
||||
if (ggml_backend_set_n_threads_fn != nullptr) {
|
||||
ggml_backend_set_n_threads_fn(backend, n_threads);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static ggml_cgraph sd_ggml_graph_view(ggml_cgraph* cgraph0, int i0, int i1) {
|
||||
ggml_cgraph cgraph = {
|
||||
/*.size =*/0,
|
||||
/*.n_nodes =*/i1 - i0,
|
||||
/*.n_leafs =*/0,
|
||||
/*.nodes =*/cgraph0->nodes + i0,
|
||||
/*.grads =*/nullptr,
|
||||
/*.grad_accs =*/nullptr,
|
||||
/*.leafs =*/nullptr,
|
||||
/*.use_counts =*/cgraph0->use_counts,
|
||||
/*.visited_hash_set =*/cgraph0->visited_hash_set,
|
||||
/*.order =*/cgraph0->order,
|
||||
/*.uid =*/0,
|
||||
};
|
||||
return cgraph;
|
||||
}
|
||||
|
||||
ggml_status sd_backend_graph_compute_with_eval_callback(ggml_backend_t backend,
|
||||
ggml_cgraph* gf,
|
||||
sd_graph_eval_callback_t callback_eval,
|
||||
void* callback_eval_user_data) {
|
||||
if (callback_eval == nullptr) {
|
||||
return ggml_backend_graph_compute(backend, gf);
|
||||
}
|
||||
|
||||
ggml_status status = GGML_STATUS_SUCCESS;
|
||||
const int n_nodes = ggml_graph_n_nodes(gf);
|
||||
bool stopped = false;
|
||||
|
||||
for (int j0 = 0; j0 < n_nodes; ++j0) {
|
||||
ggml_tensor* t = ggml_graph_node(gf, j0);
|
||||
bool need = callback_eval(t, true, callback_eval_user_data);
|
||||
int j1 = j0;
|
||||
|
||||
while (!need && j1 < n_nodes - 1) {
|
||||
t = ggml_graph_node(gf, ++j1);
|
||||
need = callback_eval(t, true, callback_eval_user_data);
|
||||
}
|
||||
|
||||
ggml_cgraph gv = sd_ggml_graph_view(gf, j0, j1 + 1);
|
||||
status = ggml_backend_graph_compute_async(backend, &gv);
|
||||
if (status != GGML_STATUS_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
ggml_backend_synchronize(backend);
|
||||
|
||||
if (need && !callback_eval(t, false, callback_eval_user_data)) {
|
||||
stopped = true;
|
||||
break;
|
||||
}
|
||||
|
||||
j0 = j1;
|
||||
}
|
||||
|
||||
ggml_backend_synchronize(backend);
|
||||
if (stopped && status == GGML_STATUS_SUCCESS) {
|
||||
status = GGML_STATUS_ABORTED;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
const char* sd_get_system_info() {
|
||||
static std::string cache_info = []() -> std::string {
|
||||
ggml_backend_load_all_once();
|
||||
std::stringstream ss;
|
||||
ss << "System Info: \n";
|
||||
auto dev = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_CPU);
|
||||
if (dev != nullptr) {
|
||||
auto reg = ggml_backend_dev_backend_reg(dev);
|
||||
auto ggml_backend_get_features_fn = (ggml_backend_get_features_t)ggml_backend_reg_get_proc_address(reg, "ggml_backend_get_features");
|
||||
if (ggml_backend_get_features_fn != nullptr) {
|
||||
ggml_backend_feature* feat = ggml_backend_get_features_fn(reg);
|
||||
while (feat->name && feat->value) {
|
||||
ss << " " << feat->name << " = " << feat->value << " | ";
|
||||
feat++;
|
||||
}
|
||||
} else {
|
||||
LOG_WARN("unable to get CPU features");
|
||||
}
|
||||
} else {
|
||||
LOG_WARN("unable to get CPU features");
|
||||
}
|
||||
return ss.str();
|
||||
}();
|
||||
return cache_info.c_str();
|
||||
}
|
||||
|
||||
static ggml_backend_t sd_get_default_backend() {
|
||||
ggml_backend_load_all_once();
|
||||
static std::once_flag once;
|
||||
std::call_once(once, []() {
|
||||
size_t dev_count = ggml_backend_dev_count();
|
||||
if (dev_count == 0) {
|
||||
LOG_ERROR("No devices found!");
|
||||
} else {
|
||||
LOG_DEBUG("Found %zu backend devices:", dev_count);
|
||||
for (size_t i = 0; i < dev_count; ++i) {
|
||||
auto dev = ggml_backend_dev_get(i);
|
||||
LOG_DEBUG("#%zu: %s", i, ggml_backend_dev_name(dev));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ggml_backend_t backend = nullptr;
|
||||
const char* SD_VK_DEVICE = getenv("SD_VK_DEVICE");
|
||||
if (SD_VK_DEVICE != nullptr) {
|
||||
std::string sd_vk_device_str = SD_VK_DEVICE;
|
||||
try {
|
||||
unsigned long long device = std::stoull(sd_vk_device_str);
|
||||
std::string vk_device_name = "Vulkan" + std::to_string(device);
|
||||
if (backend_name_exists(vk_device_name)) {
|
||||
LOG_INFO("Selecting %s as main device by env var SD_VK_DEVICE", vk_device_name.c_str());
|
||||
backend = init_named_backend(vk_device_name);
|
||||
if (!backend) {
|
||||
LOG_WARN("Device %s requested by SD_VK_DEVICE failed to init. Falling back to the default device.", vk_device_name.c_str());
|
||||
}
|
||||
} else {
|
||||
LOG_WARN("Device %s requested by SD_VK_DEVICE was not found. Falling back to the default device.", vk_device_name.c_str());
|
||||
}
|
||||
} catch (const std::invalid_argument&) {
|
||||
LOG_WARN("SD_VK_DEVICE environment variable is not a valid integer (%s). Falling back to the default device.", SD_VK_DEVICE);
|
||||
} catch (const std::out_of_range&) {
|
||||
LOG_WARN("SD_VK_DEVICE environment variable value is out of range for `unsigned long long` type (%s). Falling back to the default device.", SD_VK_DEVICE);
|
||||
}
|
||||
}
|
||||
|
||||
if (!backend) {
|
||||
std::string dev_name = get_default_backend_name();
|
||||
backend = init_named_backend(dev_name);
|
||||
if (!backend && !dev_name.empty()) {
|
||||
LOG_WARN("device %s failed to init", dev_name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (!backend) {
|
||||
LOG_WARN("loading CPU backend");
|
||||
backend = sd_backend_cpu_init();
|
||||
}
|
||||
|
||||
if (sd_backend_is_cpu(backend)) {
|
||||
LOG_DEBUG("Using CPU backend");
|
||||
}
|
||||
|
||||
return backend;
|
||||
}
|
||||
|
||||
static bool sd_parse_backend_assignment(const std::string& spec, SDBackendAssignment* assignment, std::string* error) {
|
||||
if (assignment == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*assignment = {};
|
||||
const std::string in = trim_copy(spec);
|
||||
if (in.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const std::string& raw_part : split_copy(in, ',')) {
|
||||
const std::string part = trim_copy(raw_part);
|
||||
if (part.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t eq = part.find('=');
|
||||
if (eq == std::string::npos) {
|
||||
assignment->set_default(part);
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string key = trim_copy(part.substr(0, eq));
|
||||
const std::string value = trim_copy(part.substr(eq + 1));
|
||||
if (key.empty() || value.empty()) {
|
||||
if (error != nullptr) {
|
||||
*error = "invalid backend assignment '" + part + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string key_lower = lower_copy(key);
|
||||
if (key_lower == "all" || key_lower == "default" || key_lower == "*") {
|
||||
assignment->set_default(value);
|
||||
continue;
|
||||
}
|
||||
|
||||
SDBackendModule module = SDBackendModule::DIFFUSION;
|
||||
if (!parse_backend_module(key, &module)) {
|
||||
if (error != nullptr) {
|
||||
*error = "unknown backend module '" + key + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
assignment->set_module(module, value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SDBackendAssignment::empty() const {
|
||||
return default_name.empty() && module_names.empty();
|
||||
}
|
||||
|
||||
std::string SDBackendAssignment::get(SDBackendModule module) const {
|
||||
return module_assignment_name(*this, module);
|
||||
}
|
||||
|
||||
void SDBackendAssignment::set_default(const std::string& name) {
|
||||
default_name = trim_copy(name);
|
||||
}
|
||||
|
||||
void SDBackendAssignment::set_module(SDBackendModule module, const std::string& name) {
|
||||
module_names[module] = trim_copy(name);
|
||||
}
|
||||
|
||||
void SDBackendHandleDeleter::operator()(ggml_backend_t backend) const {
|
||||
ggml_backend_free(backend);
|
||||
}
|
||||
|
||||
SDBackendManager::~SDBackendManager() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void SDBackendManager::reset() {
|
||||
backends_.clear();
|
||||
runtime_assignment_ = {};
|
||||
params_assignment_ = {};
|
||||
split_mode_assignment_ = {};
|
||||
}
|
||||
|
||||
static std::vector<std::string> split_device_list(const std::string& value) {
|
||||
std::vector<std::string> names;
|
||||
for (const std::string& raw : split_copy(value, '&')) {
|
||||
const std::string name = trim_copy(raw);
|
||||
if (!name.empty()) {
|
||||
names.push_back(name);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
static std::string primary_device_name(const std::string& value) {
|
||||
std::vector<std::string> names = split_device_list(value);
|
||||
return names.empty() ? std::string() : names.front();
|
||||
}
|
||||
|
||||
ggml_backend_t SDBackendManager::runtime_backend(SDBackendModule module) {
|
||||
return init_cached_backend(primary_device_name(runtime_assignment_.get(module)));
|
||||
}
|
||||
|
||||
std::vector<ggml_backend_t> SDBackendManager::runtime_backends(SDBackendModule module) {
|
||||
std::vector<ggml_backend_t> backends;
|
||||
for (const std::string& name : split_device_list(runtime_assignment_.get(module))) {
|
||||
ggml_backend_t backend = init_cached_backend(name);
|
||||
if (backend == nullptr) {
|
||||
LOG_ERROR("failed to initialize backend '%s' for module %s",
|
||||
name.c_str(),
|
||||
sd_backend_module_name(module));
|
||||
continue;
|
||||
}
|
||||
if (std::find(backends.begin(), backends.end(), backend) == backends.end()) {
|
||||
backends.push_back(backend);
|
||||
}
|
||||
}
|
||||
if (backends.empty()) {
|
||||
ggml_backend_t backend = runtime_backend(module);
|
||||
if (backend != nullptr) {
|
||||
backends.push_back(backend);
|
||||
}
|
||||
}
|
||||
return backends;
|
||||
}
|
||||
|
||||
ggml_backend_t SDBackendManager::params_backend(SDBackendModule module) {
|
||||
std::string name = params_assignment_.get(module);
|
||||
if (name.empty()) {
|
||||
return runtime_backend(module);
|
||||
}
|
||||
if (is_disk_backend_token(name)) {
|
||||
return runtime_backend(module);
|
||||
}
|
||||
return init_cached_backend(name);
|
||||
}
|
||||
|
||||
bool SDBackendManager::runtime_backend_is_cpu(SDBackendModule module) {
|
||||
return sd_backend_is_cpu(runtime_backend(module));
|
||||
}
|
||||
|
||||
bool SDBackendManager::params_backend_is_cpu(SDBackendModule module) {
|
||||
return sd_backend_is_cpu(params_backend(module));
|
||||
}
|
||||
|
||||
bool SDBackendManager::params_backend_is_disk(SDBackendModule module) const {
|
||||
return is_disk_backend_token(params_assignment_.get(module));
|
||||
}
|
||||
|
||||
bool SDBackendManager::params_backend_follows_runtime(SDBackendModule module) const {
|
||||
return params_assignment_.get(module).empty();
|
||||
}
|
||||
|
||||
bool SDBackendManager::runtime_backend_supports_host_buffer(SDBackendModule module) {
|
||||
ggml_backend_t backend = runtime_backend(module);
|
||||
if (backend == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (sd_backend_is_cpu(backend)) {
|
||||
return true;
|
||||
}
|
||||
ggml_backend_dev_t dev = ggml_backend_get_device(backend);
|
||||
if (dev == nullptr) {
|
||||
return false;
|
||||
}
|
||||
ggml_backend_dev_props props;
|
||||
ggml_backend_dev_get_props(dev, &props);
|
||||
return props.caps.buffer_from_host_ptr;
|
||||
}
|
||||
|
||||
bool SDBackendManager::init(const char* backend_spec,
|
||||
const char* params_backend_spec,
|
||||
const char* split_mode_spec,
|
||||
std::string* error) {
|
||||
reset();
|
||||
|
||||
if (!sd_parse_backend_assignment(SAFE_STR(backend_spec), &runtime_assignment_, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!sd_parse_backend_assignment(SAFE_STR(params_backend_spec), ¶ms_assignment_, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!sd_parse_backend_assignment(SAFE_STR(split_mode_spec), &split_mode_assignment_, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return validate(error);
|
||||
}
|
||||
|
||||
SDSplitMode SDBackendManager::split_mode(SDBackendModule module) const {
|
||||
return lower_copy(trim_copy(split_mode_assignment_.get(module))) == "row" ? SDSplitMode::ROW
|
||||
: SDSplitMode::LAYER;
|
||||
}
|
||||
|
||||
ggml_backend_buffer_type_t SDBackendManager::split_buffer_type(ggml_backend_t backend,
|
||||
const std::vector<float>& tensor_split) {
|
||||
if (backend == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
ggml_backend_dev_t dev = ggml_backend_get_device(backend);
|
||||
if (dev == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(dev);
|
||||
if (reg == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
auto fn = (ggml_backend_split_buffer_type_t)ggml_backend_reg_get_proc_address(reg, "ggml_backend_split_buffer_type");
|
||||
if (fn == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
int main_device = -1;
|
||||
const size_t dev_count = ggml_backend_reg_dev_count(reg);
|
||||
for (size_t i = 0; i < dev_count; ++i) {
|
||||
if (ggml_backend_reg_dev_get(reg, i) == dev) {
|
||||
main_device = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (main_device < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<float> padded_split(std::max<size_t>(tensor_split.size(), 64), 0.0f);
|
||||
std::copy(tensor_split.begin(), tensor_split.end(), padded_split.begin());
|
||||
return fn(main_device, padded_split.data());
|
||||
}
|
||||
|
||||
bool SDBackendManager::validate(std::string* error) const {
|
||||
auto validate_single_runtime_name = [&](const std::string& name) -> bool {
|
||||
if (is_default_backend_token(name)) {
|
||||
return true;
|
||||
}
|
||||
if (is_disk_backend_token(name)) {
|
||||
if (error != nullptr) {
|
||||
*error = "backend 'disk' is only supported by params_backend";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!sd_backend_resolve_name(name).empty() || resolve_first_device_by_registry_name(name) != nullptr) {
|
||||
return true;
|
||||
}
|
||||
if (error != nullptr) {
|
||||
*error = "backend '" + name + "' was not found";
|
||||
}
|
||||
return false;
|
||||
};
|
||||
auto validate_runtime_name = [&](const std::string& name) -> bool {
|
||||
if (name.find('&') == std::string::npos) {
|
||||
return validate_single_runtime_name(name);
|
||||
}
|
||||
std::vector<std::string> names = split_device_list(name);
|
||||
if (names.empty()) {
|
||||
if (error != nullptr) {
|
||||
*error = "invalid backend device list '" + name + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (const std::string& entry : names) {
|
||||
if (is_default_backend_token(entry)) {
|
||||
if (error != nullptr) {
|
||||
*error = "default backend token is not allowed in a device list '" + name + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!validate_single_runtime_name(entry)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
auto validate_params_name = [&](const std::string& name) -> bool {
|
||||
if (is_disk_backend_token(name)) {
|
||||
return true;
|
||||
}
|
||||
if (name.find('&') != std::string::npos) {
|
||||
if (error != nullptr) {
|
||||
*error = "params_backend does not accept device lists ('" + name + "')";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return validate_single_runtime_name(name);
|
||||
};
|
||||
auto validate_split_mode_name = [&](const std::string& name) -> bool {
|
||||
const std::string lower = lower_copy(trim_copy(name));
|
||||
if (lower.empty() || lower == "layer" || lower == "row") {
|
||||
return true;
|
||||
}
|
||||
if (error != nullptr) {
|
||||
*error = "invalid split mode '" + name + "' (expected layer or row)";
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!validate_runtime_name(runtime_assignment_.default_name) ||
|
||||
!validate_params_name(params_assignment_.default_name) ||
|
||||
!validate_split_mode_name(split_mode_assignment_.default_name)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& kv : runtime_assignment_.module_names) {
|
||||
if (!validate_runtime_name(kv.second)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& kv : params_assignment_.module_names) {
|
||||
if (!validate_params_name(kv.second)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& kv : split_mode_assignment_.module_names) {
|
||||
if (!validate_split_mode_name(kv.second)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ggml_backend_t SDBackendManager::init_cached_backend(const std::string& name) {
|
||||
std::string resolved = sd_backend_resolve_name(name);
|
||||
std::string key = lower_copy(resolved);
|
||||
ggml_backend_t backend = nullptr;
|
||||
|
||||
if (!key.empty()) {
|
||||
auto it = backends_.find(key);
|
||||
if (it != backends_.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
} else if (!is_default_backend_token(name)) {
|
||||
LOG_ERROR("backend '%s' was not found", name.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
backend = is_default_backend_token(name) ? sd_get_default_backend() : init_named_backend(resolved);
|
||||
if (backend == nullptr) {
|
||||
LOG_ERROR("failed to initialize backend '%s'", name.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string actual_key = backend_cache_key(backend);
|
||||
if (actual_key.empty()) {
|
||||
actual_key = !key.empty() ? key : lower_copy(trim_copy(name));
|
||||
}
|
||||
|
||||
auto it = backends_.find(actual_key);
|
||||
if (it != backends_.end()) {
|
||||
ggml_backend_free(backend);
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
SDBackendHandle handle(backend);
|
||||
backends_.emplace(actual_key, std::move(handle));
|
||||
return backend;
|
||||
}
|
||||
|
||||
const char* sd_backend_module_name(SDBackendModule module) {
|
||||
switch (module) {
|
||||
case SDBackendModule::DIFFUSION:
|
||||
return "diffusion";
|
||||
case SDBackendModule::TE:
|
||||
return "te";
|
||||
case SDBackendModule::CLIP_VISION:
|
||||
return "clip_vision";
|
||||
case SDBackendModule::VAE:
|
||||
return "vae";
|
||||
case SDBackendModule::CONTROL_NET:
|
||||
return "controlnet";
|
||||
case SDBackendModule::PHOTOMAKER:
|
||||
return "photomaker";
|
||||
case SDBackendModule::UPSCALER:
|
||||
return "upscaler";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
#ifndef __SD_CORE_GGML_EXTEND_BACKEND_H__
|
||||
#define __SD_CORE_GGML_EXTEND_BACKEND_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "ggml-backend.h"
|
||||
#include "ggml.h"
|
||||
#include "stable-diffusion.h"
|
||||
|
||||
enum class SDBackendModule {
|
||||
DIFFUSION,
|
||||
TE,
|
||||
CLIP_VISION,
|
||||
VAE,
|
||||
CONTROL_NET,
|
||||
PHOTOMAKER,
|
||||
UPSCALER,
|
||||
};
|
||||
|
||||
struct SDBackendAssignment {
|
||||
std::string default_name;
|
||||
std::unordered_map<SDBackendModule, std::string> module_names;
|
||||
|
||||
bool empty() const;
|
||||
std::string get(SDBackendModule module) const;
|
||||
void set_default(const std::string& name);
|
||||
void set_module(SDBackendModule module, const std::string& name);
|
||||
};
|
||||
|
||||
struct SDBackendHandleDeleter {
|
||||
void operator()(ggml_backend_t backend) const;
|
||||
};
|
||||
|
||||
using SDBackendHandle = std::unique_ptr<struct ggml_backend, SDBackendHandleDeleter>;
|
||||
|
||||
enum class SDSplitMode {
|
||||
LAYER,
|
||||
ROW,
|
||||
};
|
||||
|
||||
class SDBackendManager {
|
||||
private:
|
||||
SDBackendAssignment runtime_assignment_;
|
||||
SDBackendAssignment params_assignment_;
|
||||
SDBackendAssignment split_mode_assignment_;
|
||||
std::unordered_map<std::string, SDBackendHandle> backends_;
|
||||
|
||||
public:
|
||||
SDBackendManager() = default;
|
||||
~SDBackendManager();
|
||||
|
||||
SDBackendManager(const SDBackendManager&) = delete;
|
||||
SDBackendManager& operator=(const SDBackendManager&) = delete;
|
||||
|
||||
bool init(const char* backend_spec,
|
||||
const char* params_backend_spec,
|
||||
const char* split_mode_spec,
|
||||
std::string* error);
|
||||
void reset();
|
||||
|
||||
ggml_backend_t runtime_backend(SDBackendModule module);
|
||||
ggml_backend_t params_backend(SDBackendModule module);
|
||||
|
||||
std::vector<ggml_backend_t> runtime_backends(SDBackendModule module);
|
||||
|
||||
SDSplitMode split_mode(SDBackendModule module) const;
|
||||
ggml_backend_buffer_type_t split_buffer_type(ggml_backend_t backend,
|
||||
const std::vector<float>& tensor_split);
|
||||
|
||||
bool runtime_backend_is_cpu(SDBackendModule module);
|
||||
bool params_backend_is_cpu(SDBackendModule module);
|
||||
bool params_backend_is_disk(SDBackendModule module) const;
|
||||
bool params_backend_follows_runtime(SDBackendModule module) const;
|
||||
bool runtime_backend_supports_host_buffer(SDBackendModule module);
|
||||
|
||||
private:
|
||||
bool validate(std::string* error) const;
|
||||
ggml_backend_t init_cached_backend(const std::string& name);
|
||||
};
|
||||
|
||||
bool sd_backend_is(ggml_backend_t backend, const std::string& name);
|
||||
bool sd_backend_is_cpu(ggml_backend_t backend);
|
||||
ggml_backend_t sd_backend_cpu_init();
|
||||
bool sd_backend_cpu_set_n_threads(ggml_backend_t backend_cpu, int n_threads);
|
||||
ggml_status sd_backend_graph_compute_with_eval_callback(ggml_backend_t backend,
|
||||
ggml_cgraph* gf,
|
||||
sd_graph_eval_callback_t callback_eval,
|
||||
void* callback_eval_user_data);
|
||||
std::string sd_backend_resolve_name(const std::string& name);
|
||||
const char* sd_backend_module_name(SDBackendModule module);
|
||||
void ggml_ext_im_set_f32_1d(const struct ggml_tensor* tensor, int i, float value);
|
||||
bool add_rpc_devices(const std::string& servers);
|
||||
#endif // __SD_CORE_GGML_EXTEND_BACKEND_H__
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "ggml_graph_cut.h"
|
||||
#include "core/ggml_graph_cut.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <set>
|
||||
@@ -8,14 +10,17 @@
|
||||
#include <stack>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "core/ggml_extend_backend.h"
|
||||
#include "core/util.h"
|
||||
#include "ggml-alloc.h"
|
||||
#include "ggml-backend.h"
|
||||
#include "util.h"
|
||||
|
||||
#include "../ggml/src/ggml-impl.h"
|
||||
#include "ggml/src/ggml-impl.h"
|
||||
|
||||
namespace sd::ggml_graph_cut {
|
||||
|
||||
static constexpr double MAX_VRAM_BYTES_PER_GIB = 1024.0 * 1024.0 * 1024.0;
|
||||
|
||||
static std::string graph_cut_tensor_display_name(const ggml_tensor* tensor) {
|
||||
if (tensor == nullptr) {
|
||||
return "<null>";
|
||||
@@ -42,7 +47,24 @@ namespace sd::ggml_graph_cut {
|
||||
if (tensor == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return params_tensor_set.find(tensor) != params_tensor_set.end();
|
||||
return params_tensor_set.find(tensor) != params_tensor_set.end() ||
|
||||
(tensor->view_src != nullptr &&
|
||||
params_tensor_set.find(tensor->view_src) != params_tensor_set.end());
|
||||
}
|
||||
|
||||
static int graph_node_index_by_name(ggml_cgraph* gf, const char* name) {
|
||||
GGML_ASSERT(gf != nullptr);
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
return -1;
|
||||
}
|
||||
const int n_nodes = ggml_graph_n_nodes(gf);
|
||||
for (int i = 0; i < n_nodes; ++i) {
|
||||
ggml_tensor* node = ggml_graph_node(gf, i);
|
||||
if (node != nullptr && std::strcmp(node->name, name) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static Plan::InputShape input_shape(const ggml_tensor* tensor) {
|
||||
@@ -64,6 +86,229 @@ namespace sd::ggml_graph_cut {
|
||||
segment.output_bytes;
|
||||
}
|
||||
|
||||
static std::string lower_ascii_copy(std::string value) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) {
|
||||
return static_cast<char>(std::tolower(c));
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
static std::string normalize_backend_budget_key(const std::string& value) {
|
||||
return lower_ascii_copy(trim(value));
|
||||
}
|
||||
|
||||
static bool is_default_max_vram_key(const std::string& key) {
|
||||
std::string normalized = normalize_backend_budget_key(key);
|
||||
return normalized == "all" || normalized == "default" || normalized == "*";
|
||||
}
|
||||
|
||||
static bool parse_max_vram_budget_value(const std::string& text, float* value, std::string* error) {
|
||||
float parsed = 0.f;
|
||||
if (!parse_strict_float(text, parsed) || !std::isfinite(parsed)) {
|
||||
if (error != nullptr) {
|
||||
*error = "invalid --max-vram value '" + text + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
*value = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<std::string> backend_budget_keys(ggml_backend_t backend) {
|
||||
std::vector<std::string> keys;
|
||||
if (backend == nullptr) {
|
||||
return keys;
|
||||
}
|
||||
|
||||
ggml_backend_dev_t dev = ggml_backend_get_device(backend);
|
||||
if (dev != nullptr) {
|
||||
keys.push_back(normalize_backend_budget_key(ggml_backend_dev_name(dev)));
|
||||
}
|
||||
const char* backend_name = ggml_backend_name(backend);
|
||||
if (backend_name != nullptr) {
|
||||
keys.push_back(normalize_backend_budget_key(backend_name));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
void MaxVramAssignment::reset(float fallback_gib) {
|
||||
default_gib = fallback_gib;
|
||||
backend_gib.clear();
|
||||
resolved_backend_bytes.clear();
|
||||
}
|
||||
|
||||
bool MaxVramAssignment::parse(const std::string& raw_spec, std::string* error) {
|
||||
const std::string in = trim(raw_spec);
|
||||
if (in.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const std::string& raw_part : split_string(in, ',')) {
|
||||
const std::string part = trim(raw_part);
|
||||
if (part.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t eq = part.find('=');
|
||||
if (eq == std::string::npos) {
|
||||
float value = 0.f;
|
||||
if (!parse_max_vram_budget_value(part, &value, error)) {
|
||||
return false;
|
||||
}
|
||||
default_gib = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string key = trim(part.substr(0, eq));
|
||||
const std::string value_text = trim(part.substr(eq + 1));
|
||||
if (key.empty() || value_text.empty()) {
|
||||
if (error != nullptr) {
|
||||
*error = "invalid --max-vram assignment '" + part + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float value = 0.f;
|
||||
if (!parse_max_vram_budget_value(value_text, &value, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_default_max_vram_key(key)) {
|
||||
default_gib = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string backend_key = trim(key);
|
||||
if (backend_key.empty()) {
|
||||
if (error != nullptr) {
|
||||
*error = "invalid --max-vram backend key in '" + part + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
backend_gib[backend_key] = value;
|
||||
}
|
||||
resolved_backend_bytes.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MaxVramAssignment::canonicalize_backend_keys(std::string* error) {
|
||||
if (backend_gib.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, float> normalized;
|
||||
for (const auto& kv : backend_gib) {
|
||||
std::string resolved = sd_backend_resolve_name(kv.first);
|
||||
if (resolved.empty()) {
|
||||
if (error != nullptr) {
|
||||
*error = "unknown --max-vram backend '" + kv.first + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
normalized[normalize_backend_budget_key(resolved)] = kv.second;
|
||||
}
|
||||
backend_gib = std::move(normalized);
|
||||
resolved_backend_bytes.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t MaxVramAssignment::bytes_for_backend(ggml_backend_t backend) {
|
||||
std::vector<std::string> keys = backend_budget_keys(backend);
|
||||
const std::string cache_key = keys.empty() ? std::string("<none>") : keys.front();
|
||||
auto cached = resolved_backend_bytes.find(cache_key);
|
||||
if (cached != resolved_backend_bytes.end()) {
|
||||
return cached->second;
|
||||
}
|
||||
|
||||
float budget_gib = default_gib;
|
||||
if (!backend_gib.empty()) {
|
||||
for (const std::string& key : keys) {
|
||||
auto backend_it = backend_gib.find(key);
|
||||
if (backend_it != backend_gib.end()) {
|
||||
budget_gib = backend_it->second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const float resolved_gib = resolve_max_vram_gib(budget_gib, backend);
|
||||
const size_t bytes = max_vram_gib_to_bytes(resolved_gib);
|
||||
resolved_backend_bytes[cache_key] = bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
size_t max_vram_gib_to_bytes(float max_vram) {
|
||||
if (max_vram <= 0.f) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<size_t>(static_cast<double>(max_vram) * MAX_VRAM_BYTES_PER_GIB);
|
||||
}
|
||||
|
||||
static float max_vram_bytes_to_gib(size_t max_vram_bytes) {
|
||||
return static_cast<float>(static_cast<double>(max_vram_bytes) / MAX_VRAM_BYTES_PER_GIB);
|
||||
}
|
||||
|
||||
static size_t resolve_auto_max_vram_bytes(float spare_vram, ggml_backend_t backend) {
|
||||
if (backend == nullptr) {
|
||||
LOG_WARN("--max-vram < 0 requested, but no backend is available; disabling graph splitting");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ggml_backend_dev_t dev = ggml_backend_get_device(backend);
|
||||
if (dev == nullptr) {
|
||||
LOG_WARN("--max-vram < 0 requested, but no backend device is available; disabling graph splitting");
|
||||
return 0;
|
||||
}
|
||||
if (ggml_backend_dev_type(dev) == GGML_BACKEND_DEVICE_TYPE_CPU) {
|
||||
LOG_WARN("--max-vram < 0 requested, but the main backend is CPU; disabling graph splitting");
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t free_vram = 0;
|
||||
size_t total_vram = 0;
|
||||
ggml_backend_dev_memory(dev, &free_vram, &total_vram);
|
||||
size_t spare_bytes = static_cast<size_t>(MAX_VRAM_BYTES_PER_GIB * spare_vram);
|
||||
|
||||
if (free_vram <= spare_bytes) {
|
||||
LOG_WARN("--max-vram < 0 requested, but free VRAM is %.2f GiB; reserving %.2f GiB leaves no graph budget",
|
||||
free_vram / MAX_VRAM_BYTES_PER_GIB, spare_vram);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const size_t max_vram_bytes = free_vram - spare_bytes;
|
||||
LOG_INFO("--max-vram < 0 auto-detected %.2f GiB free VRAM (%.2f GiB total), reserving %.2f GiB; using %.2f GiB",
|
||||
free_vram / MAX_VRAM_BYTES_PER_GIB,
|
||||
total_vram / MAX_VRAM_BYTES_PER_GIB,
|
||||
spare_vram,
|
||||
max_vram_bytes / MAX_VRAM_BYTES_PER_GIB);
|
||||
return max_vram_bytes;
|
||||
}
|
||||
|
||||
float resolve_max_vram_gib(float max_vram, ggml_backend_t backend) {
|
||||
if (max_vram >= 0.f) {
|
||||
return max_vram;
|
||||
}
|
||||
return max_vram_bytes_to_gib(resolve_auto_max_vram_bytes(-max_vram, backend));
|
||||
}
|
||||
|
||||
static bool is_segment_output_needed_after(const Plan& plan,
|
||||
size_t end_segment_index,
|
||||
int output_node_index) {
|
||||
if (end_segment_index + 1 >= plan.segments.size()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t seg_idx = end_segment_index + 1; seg_idx < plan.segments.size(); ++seg_idx) {
|
||||
const auto& segment = plan.segments[seg_idx];
|
||||
for (const auto& input_ref : segment.input_refs) {
|
||||
if (input_ref.type == Segment::INPUT_PREVIOUS_CUT &&
|
||||
input_ref.node_index == output_node_index) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Segment make_segment_seed(const Plan& plan,
|
||||
size_t start_segment_index,
|
||||
size_t end_segment_index) {
|
||||
@@ -76,8 +321,11 @@ namespace sd::ggml_graph_cut {
|
||||
const auto& target_segment = plan.segments[end_segment_index];
|
||||
std::unordered_set<int> seen_output_node_indices;
|
||||
for (size_t seg_idx = start_segment_index; seg_idx <= end_segment_index; ++seg_idx) {
|
||||
const bool is_boundary_segment = seg_idx == end_segment_index;
|
||||
for (int output_node_index : plan.segments[seg_idx].output_node_indices) {
|
||||
if (seen_output_node_indices.insert(output_node_index).second) {
|
||||
if ((is_boundary_segment ||
|
||||
is_segment_output_needed_after(plan, end_segment_index, output_node_index)) &&
|
||||
seen_output_node_indices.insert(output_node_index).second) {
|
||||
seed.output_node_indices.push_back(output_node_index);
|
||||
}
|
||||
}
|
||||
@@ -244,6 +492,11 @@ namespace sd::ggml_graph_cut {
|
||||
if (tensor == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (tensor_buffer(tensor) == nullptr && tensor->src[0] != nullptr &&
|
||||
ggml_nelements(tensor->src[0]) == ggml_nelements(tensor) &&
|
||||
ggml_nbytes(tensor->src[0]) == ggml_nbytes(tensor)) {
|
||||
return cache_source_tensor(tensor->src[0]);
|
||||
}
|
||||
return tensor->view_src ? tensor->view_src : tensor;
|
||||
}
|
||||
|
||||
@@ -324,23 +577,6 @@ namespace sd::ggml_graph_cut {
|
||||
return tensors;
|
||||
}
|
||||
|
||||
std::vector<ggml_tensor*> runtime_param_tensors(ggml_cgraph* gf, const Segment& segment, const char* log_desc) {
|
||||
std::vector<ggml_tensor*> tensors = param_tensors(gf, segment);
|
||||
std::vector<ggml_tensor*> filtered_tensors;
|
||||
filtered_tensors.reserve(tensors.size());
|
||||
for (ggml_tensor* tensor : tensors) {
|
||||
if (tensor_buffer(tensor) == nullptr) {
|
||||
LOG_WARN("%s graph cut skipping param input without buffer: segment=%s tensor=%s",
|
||||
log_desc == nullptr ? "unknown" : log_desc,
|
||||
segment.group_name.c_str(),
|
||||
tensor->name);
|
||||
continue;
|
||||
}
|
||||
filtered_tensors.push_back(tensor);
|
||||
}
|
||||
return filtered_tensors;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> collect_future_input_names(ggml_cgraph* gf,
|
||||
const Plan& plan,
|
||||
size_t current_segment_index) {
|
||||
@@ -411,6 +647,44 @@ namespace sd::ggml_graph_cut {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct TensorRuntimeBinding {
|
||||
ggml_backend_buffer_t buffer = nullptr;
|
||||
void* data = nullptr;
|
||||
void* extra = nullptr;
|
||||
};
|
||||
std::unordered_map<ggml_tensor*, TensorRuntimeBinding> saved_bindings;
|
||||
auto mark_measurement_external = [&](ggml_tensor* tensor) {
|
||||
if (tensor == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto save_tensor = [&](ggml_tensor* t) {
|
||||
if (t == nullptr || saved_bindings.find(t) != saved_bindings.end()) {
|
||||
return;
|
||||
}
|
||||
saved_bindings[t] = {t->buffer, t->data, t->extra};
|
||||
// During real execution params and previous-cut inputs already
|
||||
// have backend/cache buffers, so gallocr must not reserve them.
|
||||
t->data = reinterpret_cast<void*>(static_cast<uintptr_t>(1));
|
||||
};
|
||||
save_tensor(tensor);
|
||||
save_tensor(tensor->view_src);
|
||||
};
|
||||
for (const auto& input : segment.input_refs) {
|
||||
if (input.type != Segment::INPUT_PARAM &&
|
||||
input.type != Segment::INPUT_PREVIOUS_CUT) {
|
||||
continue;
|
||||
}
|
||||
mark_measurement_external(input_tensor(gf, input));
|
||||
}
|
||||
|
||||
std::unordered_map<ggml_tensor*, int32_t> saved_output_flags;
|
||||
for (int output_node_index : segment.output_node_indices) {
|
||||
ggml_tensor* output = ggml_graph_node(gf, output_node_index);
|
||||
if (output != nullptr && saved_output_flags.find(output) == saved_output_flags.end()) {
|
||||
saved_output_flags[output] = output->flags;
|
||||
}
|
||||
}
|
||||
|
||||
ggml_context* graph_ctx = nullptr;
|
||||
ggml_cgraph* segment_graph = build_segment_graph(gf, segment, &graph_ctx);
|
||||
ggml_gallocr_t allocr = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend));
|
||||
@@ -426,6 +700,14 @@ namespace sd::ggml_graph_cut {
|
||||
|
||||
ggml_gallocr_free(allocr);
|
||||
ggml_free(graph_ctx);
|
||||
for (const auto& kv : saved_output_flags) {
|
||||
kv.first->flags = kv.second;
|
||||
}
|
||||
for (const auto& kv : saved_bindings) {
|
||||
kv.first->buffer = kv.second.buffer;
|
||||
kv.first->data = kv.second.data;
|
||||
kv.first->extra = kv.second.extra;
|
||||
}
|
||||
return buffer_size;
|
||||
}
|
||||
|
||||
@@ -503,11 +785,15 @@ namespace sd::ggml_graph_cut {
|
||||
log_desc);
|
||||
}
|
||||
|
||||
ggml_tensor* final_output = ggml_graph_node(gf, -1);
|
||||
if (final_output != nullptr && available_cut_output_node_indices.find(n_nodes - 1) == available_cut_output_node_indices.end()) {
|
||||
int final_output_index = graph_node_index_by_name(gf, "ggml_runner_final_result_tensor");
|
||||
if (final_output_index < 0) {
|
||||
final_output_index = n_nodes - 1;
|
||||
}
|
||||
ggml_tensor* final_output = final_output_index >= 0 ? ggml_graph_node(gf, final_output_index) : nullptr;
|
||||
if (final_output != nullptr && available_cut_output_node_indices.find(final_output_index) == available_cut_output_node_indices.end()) {
|
||||
Segment final_segment;
|
||||
final_segment.group_name = "ggml_runner.final";
|
||||
final_segment.output_node_indices.push_back(n_nodes - 1);
|
||||
final_segment.output_node_indices.push_back(final_output_index);
|
||||
build_segment(gf,
|
||||
plan,
|
||||
final_segment,
|
||||
@@ -589,7 +875,8 @@ namespace sd::ggml_graph_cut {
|
||||
GGML_ASSERT(!candidate_plan.segments.empty());
|
||||
|
||||
const auto& candidate_segment = candidate_plan.segments.back();
|
||||
if (graph_cut_segment_vram_bytes(candidate_segment) > max_graph_vram_bytes) {
|
||||
const size_t candidate_bytes = graph_cut_segment_vram_bytes(candidate_segment);
|
||||
if (candidate_bytes > max_graph_vram_bytes) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -619,9 +906,9 @@ namespace sd::ggml_graph_cut {
|
||||
}
|
||||
|
||||
if (log_desc != nullptr) {
|
||||
LOG_INFO("%s graph cut max_vram budget merge took %lld ms",
|
||||
log_desc,
|
||||
ggml_time_ms() - t_budget_begin);
|
||||
LOG_DEBUG("%s graph cut max_vram budget merge took %lld ms",
|
||||
log_desc,
|
||||
ggml_time_ms() - t_budget_begin);
|
||||
}
|
||||
|
||||
return merged_plan;
|
||||
@@ -673,4 +960,54 @@ namespace sd::ggml_graph_cut {
|
||||
return resolved_plan;
|
||||
}
|
||||
|
||||
void annotate_residency(Plan& plan, size_t max_graph_vram_bytes) {
|
||||
// Cached plans may be reused with a smaller live budget.
|
||||
for (auto& seg : plan.segments) {
|
||||
seg.residency = SegmentResidency::STREAMED;
|
||||
}
|
||||
if (max_graph_vram_bytes == 0 || plan.segments.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool any_param_bearing = false;
|
||||
for (const auto& seg : plan.segments) {
|
||||
if (seg.input_param_bytes > 0) {
|
||||
any_param_bearing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!any_param_bearing) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Leave room for the largest active streamed segment.
|
||||
size_t worst_streamed_footprint = 0;
|
||||
for (const auto& seg : plan.segments) {
|
||||
const size_t seg_footprint = seg.input_param_bytes +
|
||||
seg.compute_buffer_size +
|
||||
seg.output_bytes +
|
||||
seg.input_previous_cut_bytes +
|
||||
seg.input_external_bytes;
|
||||
if (seg_footprint > worst_streamed_footprint) {
|
||||
worst_streamed_footprint = seg_footprint;
|
||||
}
|
||||
}
|
||||
constexpr size_t safety = 512ull * 1024 * 1024;
|
||||
const size_t reserved = safety + worst_streamed_footprint;
|
||||
|
||||
if (max_graph_vram_bytes <= reserved) {
|
||||
return;
|
||||
}
|
||||
const size_t available = max_graph_vram_bytes - reserved;
|
||||
|
||||
size_t cumulative = 0;
|
||||
for (auto& seg : plan.segments) {
|
||||
if (cumulative + seg.input_param_bytes > available) {
|
||||
break;
|
||||
}
|
||||
seg.residency = SegmentResidency::RESIDENT;
|
||||
cumulative += seg.input_param_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sd::ggml_graph_cut
|
||||