mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-11-09 18:36:33 +00:00
Compare commits
65
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3c49b13be | ||
|
|
73fef08224 | ||
|
|
d36f72bbe5 | ||
|
|
46da352af0 | ||
|
|
c44cbec722 | ||
|
|
fbb381da25 | ||
|
|
991f825583 | ||
|
|
008b9a20ca | ||
|
|
cfdf3cf037 | ||
|
|
d22dcda1cf | ||
|
|
f977addf06 | ||
|
|
fc350be2ce | ||
|
|
b43ce4badd | ||
|
|
cd5594e9f9 | ||
|
|
abc6718455 | ||
|
|
80c4707c53 | ||
|
|
3d315c47d4 | ||
|
|
b43af40461 | ||
|
|
f61053c25d | ||
|
|
0b3b98d7b8 | ||
|
|
de00bafc3d | ||
|
|
7bcd4ce8ce | ||
|
|
5f8c19f98d | ||
|
|
9234ad822a | ||
|
|
b1f5a51a1e | ||
|
|
fdfc913c51 | ||
|
|
5a1ba5d895 | ||
|
|
f5df5f8b84 | ||
|
|
7952693004 | ||
|
|
58bb3c1b1c | ||
|
|
d1358945dc | ||
|
|
3e92bf990a | ||
|
|
984eb0686c | ||
|
|
d30854463d | ||
|
|
6225bf8c88 | ||
|
|
f8ea2e162c | ||
|
|
3be23788fc | ||
|
|
b4d6b83af6 | ||
|
|
250ea6e385 | ||
|
|
1350cf5097 | ||
|
|
290148d2e3 | ||
|
|
133087deb8 | ||
|
|
5d33b1bd2b | ||
|
|
62c54439c8 | ||
|
|
fe2682ba16 | ||
|
|
df0fa26a61 | ||
|
|
5356982584 | ||
|
|
45a63ac460 | ||
|
|
c6dfa9def1 | ||
|
|
0a067120b3 | ||
|
|
e239e4b0fd | ||
|
|
37bc88b5e9 | ||
|
|
e7b7ec6d6f | ||
|
|
997478a4cb | ||
|
|
8ee08e3cb9 | ||
|
|
77b72a2efe | ||
|
|
8cbe905fdd | ||
|
|
687915518e | ||
|
|
d09cb4c63a | ||
|
|
209b28c3cd | ||
|
|
9bfc4d1089 | ||
|
|
2032526e8c | ||
|
|
be486498ea | ||
|
|
bb6b128b2d | ||
|
|
e41fbcf455 |
No files matched your search
+2
-1
@@ -1,7 +1,8 @@
|
||||
build*/
|
||||
docs/
|
||||
test/
|
||||
tests/
|
||||
|
||||
.cache/
|
||||
*.swp
|
||||
models/
|
||||
models/
|
||||
+130
-66
@@ -16,6 +16,7 @@ on:
|
||||
".github/workflows/**",
|
||||
".dockerignore",
|
||||
"docker/Dockerfile*",
|
||||
"scripts/ci-cuda-cache.sh",
|
||||
"**/CMakeLists.txt",
|
||||
"**/Makefile",
|
||||
"**/*.h",
|
||||
@@ -23,6 +24,7 @@ on:
|
||||
"**/*.c",
|
||||
"**/*.cpp",
|
||||
"**/*.cu",
|
||||
"**/*.cuh",
|
||||
"ggml",
|
||||
"examples/server/frontend",
|
||||
"examples/server/frontend/**",
|
||||
@@ -37,6 +39,7 @@ on:
|
||||
".github/workflows/**",
|
||||
".dockerignore",
|
||||
"docker/Dockerfile*",
|
||||
"scripts/ci-cuda-cache.sh",
|
||||
"**/CMakeLists.txt",
|
||||
"**/Makefile",
|
||||
"**/*.h",
|
||||
@@ -44,6 +47,7 @@ on:
|
||||
"**/*.c",
|
||||
"**/*.cpp",
|
||||
"**/*.cu",
|
||||
"**/*.cuh",
|
||||
"ggml",
|
||||
"examples/server/frontend",
|
||||
"examples/server/frontend/**",
|
||||
@@ -55,6 +59,9 @@ on:
|
||||
env:
|
||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
@@ -66,17 +73,17 @@ jobs:
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v5
|
||||
with:
|
||||
version: 10.15.1
|
||||
|
||||
@@ -97,7 +104,8 @@ jobs:
|
||||
- name: Get commit hash
|
||||
id: commit
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: prompt/actions-commit-hash@v2
|
||||
shell: bash
|
||||
run: echo "short=${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Fetch system info
|
||||
id: system-info
|
||||
@@ -118,7 +126,7 @@ jobs:
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}.zip
|
||||
path: |
|
||||
@@ -130,17 +138,17 @@ jobs:
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v5
|
||||
with:
|
||||
version: 10.15.1
|
||||
|
||||
@@ -161,7 +169,8 @@ jobs:
|
||||
- name: Get commit hash
|
||||
id: commit
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: prompt/actions-commit-hash@v2
|
||||
shell: bash
|
||||
run: echo "short=${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Fetch system info
|
||||
id: system-info
|
||||
@@ -182,7 +191,7 @@ jobs:
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}-vulkan.zip
|
||||
path: |
|
||||
@@ -230,25 +239,27 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v5
|
||||
with:
|
||||
version: 10.15.1
|
||||
|
||||
- name: Get commit hash
|
||||
id: commit
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: prompt/actions-commit-hash@v2
|
||||
shell: bash
|
||||
run: echo "short=${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Log in to the container registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -256,7 +267,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
@@ -267,9 +278,24 @@ jobs:
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
|
||||
- name: Cache CUDA compilation
|
||||
if: matrix.variant == 'cuda'
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ runner.temp }}/cuda-ccache
|
||||
key: cuda-ccache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('docker/Dockerfile.cuda') }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
restore-keys: |
|
||||
cuda-ccache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('docker/Dockerfile.cuda') }}-
|
||||
|
||||
- name: Restore CUDA compiler cache mount
|
||||
if: matrix.variant == 'cuda'
|
||||
env:
|
||||
CUDA_CACHE_BUILDER: ${{ steps.buildx.outputs.name }}
|
||||
run: bash scripts/ci-cuda-cache.sh restore
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build-push
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.platform }}
|
||||
@@ -280,23 +306,29 @@ jobs:
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
build-args: ${{ matrix.build-args }}
|
||||
|
||||
- name: Export CUDA compiler cache mount
|
||||
if: matrix.variant == 'cuda'
|
||||
env:
|
||||
CUDA_CACHE_BUILDER: ${{ steps.buildx.outputs.name }}
|
||||
run: bash scripts/ci-cuda-cache.sh save
|
||||
|
||||
macOS-latest-cmake:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v5
|
||||
with:
|
||||
version: 10.15.1
|
||||
|
||||
@@ -317,7 +349,8 @@ jobs:
|
||||
- name: Get commit hash
|
||||
id: commit
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: prompt/actions-commit-hash@v2
|
||||
shell: bash
|
||||
run: echo "short=${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Fetch system info
|
||||
id: system-info
|
||||
@@ -338,7 +371,7 @@ jobs:
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}.zip
|
||||
path: |
|
||||
@@ -356,35 +389,44 @@ jobs:
|
||||
- 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\"' -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON"
|
||||
defines: "-DSD_CUDA=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_CUDA_COMPILER_LAUNCHER=ccache -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 -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON"
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v5
|
||||
with:
|
||||
version: 10.15.1
|
||||
|
||||
- name: Install cuda-toolkit
|
||||
id: cuda-toolkit
|
||||
if: ${{ matrix.build == 'cuda12' }}
|
||||
uses: Jimver/cuda-toolkit@v0.2.22
|
||||
uses: Jimver/cuda-toolkit@v0.2.35
|
||||
with:
|
||||
cuda: "12.8.1"
|
||||
method: "network"
|
||||
sub-packages: '["nvcc", "cudart", "cublas", "cublas_dev", "thrust", "visual_studio_integration"]'
|
||||
|
||||
- name: Cache CUDA compilation
|
||||
if: matrix.build == 'cuda12'
|
||||
uses: ggml-org/ccache-action@v1.2.21
|
||||
with:
|
||||
variant: ccache
|
||||
install: binary
|
||||
key: windows-cuda-12.8.1-x64
|
||||
max-size: "2G"
|
||||
|
||||
- name: Install Vulkan SDK
|
||||
id: get_vulkan
|
||||
if: ${{ matrix.build == 'vulkan' }}
|
||||
@@ -394,13 +436,15 @@ jobs:
|
||||
Add-Content $env:GITHUB_ENV "VULKAN_SDK=C:\VulkanSDK\${env:VULKAN_VERSION}"
|
||||
Add-Content $env:GITHUB_PATH "C:\VulkanSDK\${env:VULKAN_VERSION}\bin"
|
||||
|
||||
- name: Activate MSVC environment
|
||||
id: msvc_dev_cmd
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Build
|
||||
id: cmake_build
|
||||
shell: pwsh
|
||||
run: |
|
||||
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||
$vsPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
|
||||
if (-not $vsPath) { throw "Visual Studio C++ tools not found" }
|
||||
& "$vsPath\Common7\Tools\Launch-VsDevShell.ps1" -Arch amd64 -HostArch amd64 -SkipAutomaticLocation
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_CXX_FLAGS='/bigobj' -G Ninja -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe -DCMAKE_BUILD_TYPE=Release ${{ matrix.defines }}
|
||||
@@ -409,7 +453,8 @@ jobs:
|
||||
- name: Get commit hash
|
||||
id: commit
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: prompt/actions-commit-hash@v2
|
||||
shell: bash
|
||||
run: echo "short=${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Pack artifacts
|
||||
id: pack_artifacts
|
||||
@@ -429,6 +474,9 @@ jobs:
|
||||
ls .\build\bin
|
||||
throw "Can't find stable-diffusion.dll"
|
||||
}
|
||||
if ("${{ matrix.build }}" -eq "cuda12") {
|
||||
Copy-Item thirdparty/cutlass/LICENSE.txt (Join-Path (Split-Path -Parent $filePath) 'cutlass.txt')
|
||||
}
|
||||
7z a sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-${{ matrix.build }}-x64.zip $filePath .\start-server.sh .\start-server.bat
|
||||
|
||||
- name: Copy and pack Cuda runtime
|
||||
@@ -442,7 +490,7 @@ jobs:
|
||||
|
||||
- name: Upload Cuda runtime
|
||||
if: ${{ matrix.build == 'cuda12' && (github.event_name == 'push' && github.ref == 'refs/heads/master' || github.event.inputs.create_release == 'true') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: sd-cudart-sd-bin-win-cu12-x64.zip
|
||||
path: |
|
||||
@@ -450,7 +498,7 @@ jobs:
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-${{ matrix.build }}-x64.zip
|
||||
path: |
|
||||
@@ -464,29 +512,29 @@ jobs:
|
||||
gpu_targets: "gfx1010;gfx1011;gfx1012;gfx1030;gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036;gfx1100;gfx1101;gfx1102;gfx1103;gfx1150;gfx1151;gfx1152;gfx1153;gfx1200;gfx1201"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v5
|
||||
with:
|
||||
version: 10.15.1
|
||||
|
||||
- name: Cache ROCm Installation
|
||||
id: cache-rocm
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: C:\TheRock\build
|
||||
key: rocm-wheels-${{ env.ROCM_VERSION }}-${{ runner.os }}
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.16
|
||||
uses: ggml-org/ccache-action@v1.2.21
|
||||
with:
|
||||
key: windows-rocm-${{ env.ROCM_VERSION }}-x64
|
||||
evict-old-files: 1d
|
||||
@@ -567,7 +615,8 @@ jobs:
|
||||
- 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
|
||||
shell: bash
|
||||
run: echo "short=${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Bundle HIP runtime libraries
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
@@ -588,7 +637,7 @@ jobs:
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-win-rocm-${{ env.ROCM_VERSION }}-x64.zip
|
||||
path: |
|
||||
@@ -615,7 +664,7 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.16
|
||||
uses: ggml-org/ccache-action@v1.2.21
|
||||
with:
|
||||
key: ubuntu-rocm-cmake-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
|
||||
evict-old-files: 1d
|
||||
@@ -688,12 +737,12 @@ jobs:
|
||||
# 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
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v5
|
||||
with:
|
||||
version: 10.15.1
|
||||
|
||||
@@ -722,7 +771,8 @@ jobs:
|
||||
- name: Get commit hash
|
||||
id: commit
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: prompt/actions-commit-hash@v2
|
||||
shell: bash
|
||||
run: echo "short=${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Prepare artifacts
|
||||
id: prepare_artifacts
|
||||
@@ -751,7 +801,7 @@ jobs:
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
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-${{ matrix.ROCM_VERSION }}.zip
|
||||
path: |
|
||||
@@ -762,6 +812,9 @@ jobs:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
needs:
|
||||
- ubuntu-latest-cmake
|
||||
- ubuntu-latest-cmake-vulkan
|
||||
@@ -773,13 +826,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download artifacts
|
||||
id: download-artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: ./artifact
|
||||
pattern: sd-*
|
||||
@@ -792,36 +845,47 @@ jobs:
|
||||
|
||||
- name: Get commit hash
|
||||
id: commit
|
||||
uses: prompt/actions-commit-hash@v2
|
||||
shell: bash
|
||||
run: echo "short=${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create release
|
||||
id: create_release
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || github.ref_name == 'master' }}
|
||||
uses: anzz1/action-create-release@v1
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_TAG: ${{ format('{0}-{1}-{2}', env.BRANCH_NAME, steps.commit_count.outputs.count, steps.commit.outputs.short) }}
|
||||
with:
|
||||
tag_name: ${{ format('{0}-{1}-{2}', env.BRANCH_NAME, steps.commit_count.outputs.count, steps.commit.outputs.short) }}
|
||||
script: |
|
||||
const { data: release } = await github.rest.repos.createRelease({
|
||||
...context.repo,
|
||||
tag_name: process.env.RELEASE_TAG,
|
||||
name: process.env.RELEASE_TAG,
|
||||
target_commitish: context.sha,
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
});
|
||||
core.setOutput('id', release.id);
|
||||
|
||||
- name: Upload release
|
||||
id: upload_release
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || github.ref_name == 'master' }}
|
||||
uses: actions/github-script@v3
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
RELEASE_ID: ${{ steps.create_release.outputs.id }}
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const release_id = '${{ steps.create_release.outputs.id }}';
|
||||
for (let file of await fs.readdirSync('./artifact')) {
|
||||
const fs = require('fs').promises;
|
||||
const release_id = Number(process.env.RELEASE_ID);
|
||||
for (const file of await fs.readdir('./artifact')) {
|
||||
if (path.extname(file) === '.zip') {
|
||||
console.log('uploadReleaseAsset', file);
|
||||
await github.repos.uploadReleaseAsset({
|
||||
await github.rest.repos.uploadReleaseAsset({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
release_id: release_id,
|
||||
name: file,
|
||||
data: await fs.readFileSync(`./artifact/${file}`)
|
||||
data: await fs.readFile(`./artifact/${file}`)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ build*/
|
||||
cmake-build-*/
|
||||
test/
|
||||
tests/
|
||||
logs/
|
||||
.vscode/
|
||||
.idea/
|
||||
.cache/
|
||||
|
||||
@@ -10,3 +10,6 @@
|
||||
[submodule "thirdparty/libwebm"]
|
||||
path = thirdparty/libwebm
|
||||
url = https://github.com/webmproject/libwebm.git
|
||||
[submodule "thirdparty/cutlass"]
|
||||
path = thirdparty/cutlass
|
||||
url = https://github.com/NVIDIA/cutlass.git
|
||||
+70
-2
@@ -9,13 +9,28 @@ if (NOT XCODE AND NOT MSVC AND NOT CMAKE_BUILD_TYPE)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
cmake_host_system_information(RESULT SD_LOGICAL_CORES QUERY NUMBER_OF_LOGICAL_CORES)
|
||||
if (SD_LOGICAL_CORES GREATER 8)
|
||||
math(EXPR SD_DEFAULT_MSVC_MP_COUNT "${SD_LOGICAL_CORES} - 2")
|
||||
elseif (SD_LOGICAL_CORES GREATER 2)
|
||||
math(EXPR SD_DEFAULT_MSVC_MP_COUNT "${SD_LOGICAL_CORES} - 1")
|
||||
else()
|
||||
set(SD_DEFAULT_MSVC_MP_COUNT "${SD_LOGICAL_CORES}")
|
||||
endif()
|
||||
if (NOT SD_DEFAULT_MSVC_MP_COUNT GREATER 0)
|
||||
set(SD_DEFAULT_MSVC_MP_COUNT 1)
|
||||
endif()
|
||||
set(SD_MSVC_MP_COUNT "${SD_DEFAULT_MSVC_MP_COUNT}" CACHE STRING "Maximum parallel MSVC compilations per project")
|
||||
if (NOT SD_MSVC_MP_COUNT MATCHES "^[1-9][0-9]*$" OR SD_MSVC_MP_COUNT GREATER 65536)
|
||||
message(FATAL_ERROR "SD_MSVC_MP_COUNT must be an integer between 1 and 65536")
|
||||
endif()
|
||||
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
|
||||
add_compile_definitions(_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
|
||||
# /MP is MSVC-only: icx rejects it outright once offloading is enabled.
|
||||
add_compile_options(
|
||||
$<$<AND:$<COMPILE_LANGUAGE:C>,$<C_COMPILER_ID:MSVC>>:/MP>
|
||||
$<$<AND:$<COMPILE_LANGUAGE:C>,$<C_COMPILER_ID:MSVC>>:/MP${SD_MSVC_MP_COUNT}>
|
||||
$<$<COMPILE_LANGUAGE:C>:/utf-8>
|
||||
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/MP>
|
||||
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/MP${SD_MSVC_MP_COUNT}>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:/utf-8>
|
||||
)
|
||||
endif()
|
||||
@@ -111,6 +126,12 @@ if(SD_CUDA)
|
||||
endif()
|
||||
|
||||
if(SD_METAL)
|
||||
if(NOT SD_USE_UPSTREAM_GGML)
|
||||
if(CMAKE_VERSION VERSION_LESS 3.16)
|
||||
message(FATAL_ERROR "SD_METAL with SD_USE_UPSTREAM_GGML=OFF requires CMake 3.16 or later for OBJC and OBJCXX support")
|
||||
endif()
|
||||
enable_language(OBJC OBJCXX)
|
||||
endif()
|
||||
message("-- Use Metal as backend stable-diffusion")
|
||||
set(GGML_METAL ON)
|
||||
endif()
|
||||
@@ -225,6 +246,8 @@ file(GLOB SD_LIB_SOURCES CONFIGURE_DEPENDS
|
||||
"src/core/*.h"
|
||||
"src/core/*.cpp"
|
||||
"src/core/*.hpp"
|
||||
"src/core/cpu/*.h"
|
||||
"src/core/cpu/*.cpp"
|
||||
"src/core/probe/*.h"
|
||||
"src/core/probe/*.cpp"
|
||||
"src/extensions/*.h"
|
||||
@@ -343,6 +366,51 @@ if (SD_CUDA)
|
||||
# Keep the driver stub on downstream link lines when no driver is installed.
|
||||
target_link_libraries(${SD_LIB} PUBLIC CUDA::cuda_driver)
|
||||
set_property(SOURCE src/core/ggml_extend_backend.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_USE_CUDA)
|
||||
if (NOT SD_USE_UPSTREAM_GGML)
|
||||
include(cmake/cutlass.cmake)
|
||||
enable_language(CUDA)
|
||||
set_target_properties(${SD_LIB} PROPERTIES CUDA_STANDARD 17 CUDA_STANDARD_REQUIRED ON)
|
||||
if (SD_CUDA_CUTLASS_PATH)
|
||||
target_sources(${SD_LIB} PRIVATE src/core/cuda/cute_attention.cu src/core/cuda/i8_projection.cu src/core/cuda/i8_gated_residual.cu src/core/cuda/i8_gemm.cu src/core/cuda/bf16_gemm.cu)
|
||||
set_source_files_properties(src/core/cuda/cute_attention.cu src/core/cuda/i8_projection.cu src/core/cuda/i8_gated_residual.cu src/core/cuda/i8_gemm.cu src/core/cuda/bf16_gemm.cu PROPERTIES
|
||||
INCLUDE_DIRECTORIES "${SD_CUDA_CUTLASS_PATH}/include"
|
||||
COMPILE_OPTIONS "--use_fast_math;--extended-lambda;--expt-relaxed-constexpr")
|
||||
if (MSVC)
|
||||
set_property(SOURCE src/core/cuda/cute_attention.cu src/core/cuda/i8_projection.cu src/core/cuda/i8_gated_residual.cu src/core/cuda/i8_gemm.cu src/core/cuda/bf16_gemm.cu APPEND PROPERTY COMPILE_OPTIONS -Xcompiler=/Zc:preprocessor,/Zc:__cplusplus)
|
||||
endif()
|
||||
if (GGML_CUDA_FORCE_CUBLAS)
|
||||
set_property(SOURCE src/core/cuda/i8_projection.cu src/core/cuda/i8_gated_residual.cu src/core/cuda/i8_gemm.cu src/core/cuda/bf16_gemm.cu APPEND PROPERTY COMPILE_DEFINITIONS SD_CUDA_FORCE_CUBLAS)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
set(SD_BACKEND_REGISTRATIONS)
|
||||
if (SD_METAL AND NOT SD_USE_UPSTREAM_GGML)
|
||||
add_subdirectory(src/core/metal)
|
||||
target_link_libraries(${SD_LIB} PRIVATE "-framework Foundation" "-framework Metal")
|
||||
list(APPEND SD_BACKEND_REGISTRATIONS sd_ggml_register_metal_i8)
|
||||
endif()
|
||||
if (NOT SD_USE_UPSTREAM_GGML AND (SD_CUDA OR SD_HIPBLAS OR SD_MUSA))
|
||||
if(SD_HIPBLAS)
|
||||
include(cmake/hip.cmake)
|
||||
endif()
|
||||
add_subdirectory(src/core/cuda)
|
||||
foreach(VENDOR CUDA HIP MUSA)
|
||||
if(SD_${VENDOR} OR (VENDOR STREQUAL "HIP" AND SD_HIPBLAS))
|
||||
string(TOLOWER "${VENDOR}" NAMESPACE)
|
||||
list(APPEND SD_BACKEND_REGISTRATIONS "sd_${NAMESPACE}::register_extensions")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
if (SD_VULKAN AND NOT SD_USE_UPSTREAM_GGML)
|
||||
add_subdirectory(src/core/vulkan)
|
||||
list(APPEND SD_BACKEND_REGISTRATIONS sd_ggml_register_vulkan_rope sd_ggml_register_vulkan_i8 sd_ggml_register_vulkan_modulate sd_ggml_register_vulkan_rms_affine)
|
||||
endif()
|
||||
if(SD_BACKEND_REGISTRATIONS)
|
||||
string(REPLACE ";" ",\n " SD_BACKEND_REGISTRATIONS "${SD_BACKEND_REGISTRATIONS}")
|
||||
configure_file(cmake/backend_extensions.h.in generated/sd_backend_extensions.h @ONLY)
|
||||
set_property(SOURCE src/core/ggml_extensions.cpp APPEND PROPERTY INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
set_property(SOURCE src/core/ggml_extensions.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_BACKEND_EXTENSIONS)
|
||||
endif()
|
||||
if (SD_HIPBLAS)
|
||||
find_package(hip REQUIRED CONFIG HINTS "$ENV{ROCM_PATH}" /opt/rocm)
|
||||
|
||||
@@ -29,6 +29,18 @@ PRs should include:
|
||||
- Linked issue/PR context when applicable.
|
||||
- Screenshots or sample outputs for UI/visual behavior changes.
|
||||
|
||||
## Documentation
|
||||
|
||||
Write model usage guides in `docs/` for users. Focus on supported weights,
|
||||
setup, runnable commands, parameter meanings, output behavior, and practical
|
||||
limits such as memory requirements. Include technical details only when they
|
||||
help users choose settings or troubleshoot a problem.
|
||||
|
||||
Keep tensor layouts, internal class names, kernel implementation details,
|
||||
algorithm derivations, and development history out of usage guides. Keep task-specific
|
||||
profiling logs, numerical comparison procedures, and test artifacts local, and
|
||||
summarize relevant verification results in the PR description.
|
||||
|
||||
## README UI Listings
|
||||
|
||||
The [UIs list in the README](README.md#uis) only includes open-source projects that use `stable-diffusion.cpp` as a backend. Do not submit PRs to add closed-source UIs.
|
||||
|
||||
@@ -59,6 +59,7 @@ API and command-line option may change frequently.***
|
||||
- [Z-Image](./docs/z_image.md)
|
||||
- [Z-Image L2P](./docs/z_image_l2p.md)
|
||||
- [MiniT2I](./docs/minit2i.md)
|
||||
- [P-H3-Image](./docs/p_h3_image.md)
|
||||
- [SenseNova U1.5](./docs/sensenova_u1.md)
|
||||
- [Ovis-Image](./docs/ovis_image.md)
|
||||
- [Anima](./docs/anima.md)
|
||||
@@ -87,6 +88,9 @@ API and command-line option may change frequently.***
|
||||
- [LTX-2.3/LTX-2.5](./docs/ltx2.md)
|
||||
- [HunyuanVideo 1.5](./docs/hunyuan_video.md)
|
||||
- [LingBot-Video](./docs/lingbot_video.md)
|
||||
- Video Upscale Models
|
||||
- [RelayVSR](./docs/relay_vsr.md)
|
||||
- [SeedVR2](./docs/seedvr2.md)
|
||||
- [PhotoMaker](./docs/photo_maker.md) support.
|
||||
- [IP-Adapter](./docs/ip_adapter.md) support (SD 1.5 and SDXL, including Plus)
|
||||
- Control Net support with SD 1.5
|
||||
@@ -197,6 +201,9 @@ These projects use `stable-diffusion.cpp` as a backend for their image generatio
|
||||
- [Neural-Pixel](https://github.com/Luiz-Alcantara/Neural-Pixel)
|
||||
- [KoboldCpp](https://github.com/LostRuins/koboldcpp)
|
||||
- [Kokon](https://github.com/GabrieleLozupone/kokon)
|
||||
- [NodeTool](https://github.com/nodetool-ai/nodetool)
|
||||
- [Quo](https://github.com/quo-ai/quo)
|
||||
- [Prisme](https://github.com/d3Ita/prisme)
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef __SD_BACKEND_EXTENSIONS_H__
|
||||
#define __SD_BACKEND_EXTENSIONS_H__
|
||||
|
||||
#include "core/cuda/registration.h"
|
||||
|
||||
void sd_ggml_register_metal_i8(ggml_backend_dev_t device);
|
||||
|
||||
void sd_ggml_register_vulkan_rope(ggml_backend_dev_t device);
|
||||
void sd_ggml_register_vulkan_i8(ggml_backend_dev_t device);
|
||||
void sd_ggml_register_vulkan_rms_affine(ggml_backend_dev_t device);
|
||||
void sd_ggml_register_vulkan_modulate(ggml_backend_dev_t device);
|
||||
|
||||
static void (*const sd_backend_extensions[])(ggml_backend_dev_t) = {
|
||||
@SD_BACKEND_REGISTRATIONS@
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
set(sd_cutlass_default "${PROJECT_SOURCE_DIR}/thirdparty/cutlass")
|
||||
if(NOT DEFINED SD_CUDA_CUTLASS_PATH AND DEFINED GGML_CUDA_CUTLASS_PATH)
|
||||
get_filename_component(sd_old_cutlass_default "${SD_GGML_SOURCE_DIR}/src/ggml-cuda/vendors/cutlass" ABSOLUTE)
|
||||
if(NOT GGML_CUDA_CUTLASS_PATH STREQUAL sd_old_cutlass_default)
|
||||
set(sd_cutlass_default "${GGML_CUDA_CUTLASS_PATH}")
|
||||
endif()
|
||||
message(STATUS "Migrating GGML_CUDA_CUTLASS_PATH to SD_CUDA_CUTLASS_PATH")
|
||||
endif()
|
||||
set(SD_CUDA_CUTLASS_PATH "${sd_cutlass_default}" CACHE PATH "sd: CUTLASS source directory for INT8 GEMM and CuTe attention, empty to disable")
|
||||
unset(GGML_CUDA_CUTLASS_PATH CACHE)
|
||||
if(SD_CUDA_CUTLASS_PATH)
|
||||
if(NOT EXISTS "${SD_CUDA_CUTLASS_PATH}/include/cutlass/gemm/kernel/default_gemm_universal_with_visitor.h" OR
|
||||
NOT EXISTS "${SD_CUDA_CUTLASS_PATH}/include/cute/tensor.hpp")
|
||||
message(FATAL_ERROR
|
||||
"CUTLASS headers not found at ${SD_CUDA_CUTLASS_PATH}. "
|
||||
"Run git submodule update --init --recursive from the sd.cpp repository root, "
|
||||
"set SD_CUDA_CUTLASS_PATH to a CUTLASS source tree (tested with 3.9.2), "
|
||||
"or set SD_CUDA_CUTLASS_PATH to an empty string to disable CUTLASS.")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,3 @@
|
||||
file(READ "${INPUT}" SPIRV HEX)
|
||||
string(REGEX REPLACE "(..)(..)(..)(..)" "0x\\4\\3\\2\\1," SPIRV_WORDS "${SPIRV}")
|
||||
file(WRITE "${OUTPUT}" "#include <cstdint>\nnamespace sd_vulkan_shaders {\nstatic const uint32_t ${NAME}[] = {${SPIRV_WORDS}};\n}\n")
|
||||
+12
-10
@@ -9,17 +9,19 @@ if(NOT TARGET ggml)
|
||||
add_library(ggml ALIAS ggml::ggml)
|
||||
endif()
|
||||
|
||||
get_target_property(sd_ggml_imported ggml IMPORTED)
|
||||
if(sd_ggml_imported)
|
||||
set(sd_ggml_private_include "${SD_GGML_SOURCE_DIR}/src")
|
||||
else()
|
||||
get_target_property(sd_ggml_private_include ggml SOURCE_DIR)
|
||||
if(SD_USE_UPSTREAM_GGML)
|
||||
get_target_property(sd_ggml_imported ggml IMPORTED)
|
||||
if(sd_ggml_imported)
|
||||
set(sd_ggml_private_include "${SD_GGML_SOURCE_DIR}/src")
|
||||
else()
|
||||
get_target_property(sd_ggml_private_include ggml SOURCE_DIR)
|
||||
endif()
|
||||
if(NOT EXISTS "${sd_ggml_private_include}/ggml-impl.h")
|
||||
message(FATAL_ERROR "Set SD_GGML_SOURCE_DIR to the source tree matching the selected ggml library (ggml-impl.h is required).")
|
||||
endif()
|
||||
target_include_directories(${SD_LIB} PRIVATE "${sd_ggml_private_include}")
|
||||
set_property(TARGET ${SD_LIB} PROPERTY SD_GGML_PRIVATE_INCLUDE_DIR "${sd_ggml_private_include}")
|
||||
endif()
|
||||
if(NOT EXISTS "${sd_ggml_private_include}/ggml-impl.h")
|
||||
message(FATAL_ERROR "Set SD_GGML_SOURCE_DIR to the source tree matching the selected ggml library (ggml-impl.h is required).")
|
||||
endif()
|
||||
target_include_directories(${SD_LIB} PRIVATE "${sd_ggml_private_include}")
|
||||
set_property(TARGET ${SD_LIB} PROPERTY SD_GGML_PRIVATE_INCLUDE_DIR "${sd_ggml_private_include}")
|
||||
|
||||
if(SD_USE_UPSTREAM_GGML)
|
||||
target_compile_definitions(${SD_LIB} PUBLIC SD_USE_UPSTREAM_GGML)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
if(NOT WIN32 AND NOT CMAKE_CXX_COMPILER MATCHES "hipcc")
|
||||
if(NOT CMAKE_HIP_ARCHITECTURES)
|
||||
if(GPU_TARGETS)
|
||||
set(CMAKE_HIP_ARCHITECTURES ${GPU_TARGETS})
|
||||
elseif(AMDGPU_TARGETS)
|
||||
set(CMAKE_HIP_ARCHITECTURES ${AMDGPU_TARGETS})
|
||||
endif()
|
||||
endif()
|
||||
# HIP objects are linked by the parent target, so initialize its link rules here.
|
||||
enable_language(HIP)
|
||||
set_property(TARGET ${SD_LIB} PROPERTY HIP_ARCHITECTURES "${CMAKE_HIP_ARCHITECTURES}")
|
||||
endif()
|
||||
+1
-1
@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends build-essential
|
||||
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 && \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.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 && \
|
||||
|
||||
+19
-5
@@ -1,7 +1,9 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG CUDA_VERSION=12.6.3
|
||||
ARG UBUNTU_VERSION=24.04
|
||||
|
||||
FROM nvidia/cuda:${CUDA_VERSION}-cudnn-devel-ubuntu${UBUNTU_VERSION} AS build
|
||||
FROM nvidia/cuda:${CUDA_VERSION}-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 && \
|
||||
@@ -9,7 +11,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends build-essential
|
||||
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 && \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.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 && \
|
||||
@@ -25,8 +27,15 @@ ARG CUDA_ARCHITECTURES=""
|
||||
ARG GGML_CUDA_FA_ALL_QUANTS=""
|
||||
ARG GGML_CUDA_ENABLE_DYNAMIC_CPU_BACKENDS=ON
|
||||
|
||||
ENV CCACHE_DIR=/root/.cache/ccache \
|
||||
CCACHE_MAXSIZE=2G \
|
||||
CCACHE_COMPILERCHECK=content
|
||||
|
||||
RUN set -- \
|
||||
-DSD_CUDA=ON; \
|
||||
-DSD_CUDA=ON \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CUDA_COMPILER_LAUNCHER=ccache; \
|
||||
if [ "${GGML_CUDA_ENABLE_DYNAMIC_CPU_BACKENDS}" = "ON" ]; then \
|
||||
set -- "$@" \
|
||||
-DSD_BUILD_SHARED_LIBS=ON \
|
||||
@@ -44,15 +53,20 @@ RUN set -- \
|
||||
set -- "$@" "-DGGML_CUDA_FA_ALL_QUANTS=${GGML_CUDA_FA_ALL_QUANTS}"; \
|
||||
fi; \
|
||||
cmake . -B ./build "$@"
|
||||
RUN cmake --build ./build --config Release -j$(nproc)
|
||||
RUN --mount=type=cache,target=/root/.cache/ccache \
|
||||
ccache --zero-stats && \
|
||||
cmake --build ./build --config Release -j$(nproc) && \
|
||||
ccache --cleanup && \
|
||||
ccache --show-stats
|
||||
|
||||
FROM nvidia/cuda:${CUDA_VERSION}-cudnn-runtime-ubuntu${UBUNTU_VERSION} AS runtime
|
||||
FROM nvidia/cuda:${CUDA_VERSION}-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
|
||||
COPY --from=build /sd.cpp/thirdparty/cutlass/LICENSE.txt /sd.cpp/bin/cutlass.txt
|
||||
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
|
||||
|
||||
@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends ccache cmake gi
|
||||
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 && \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.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 && \
|
||||
|
||||
@@ -7,7 +7,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
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 && \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.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/*
|
||||
|
||||
@@ -9,7 +9,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends cmake ca-certif
|
||||
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 && \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.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 && \
|
||||
|
||||
@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends build-essential
|
||||
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 && \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.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 && \
|
||||
|
||||
+7
-1
@@ -135,7 +135,10 @@ regardless of argument order, even with `--auto-fit on`.
|
||||
|
||||
Auto-fit preserves explicit `--backend` assignments, including per-module
|
||||
assignments and device lists. For modules without a runtime assignment, it chooses
|
||||
the GPU with the largest available memory budget (the first device on a tie).
|
||||
the discrete GPU with the largest positive memory budget (the first device on a
|
||||
tie), falling back to integrated GPUs when no discrete GPU has a positive budget.
|
||||
An integrated GPU's shared system memory does not give it priority over a discrete
|
||||
GPU. Explicit assignments such as `--backend Vulkan1` still select that device.
|
||||
It then derives parameter placements from the model metadata, each module's
|
||||
compute devices, and the remaining memory budgets. The chosen backend
|
||||
specifications are printed.
|
||||
@@ -247,6 +250,9 @@ These are backend-level defaults. GGML still chooses the actual kernel based on
|
||||
weight type, shape, and hardware; enabling direct convolution does not guarantee
|
||||
that every operation uses implicit GEMM. The CUDA-family implicit GEMM paths
|
||||
require F16 weights and can use ordinary direct kernels for other cases.
|
||||
For 3D convolution, a supported direct kernel is also used as a fallback when
|
||||
the im2col decomposition is unsupported for the actual tensor sizes, including
|
||||
when an intermediate reshape or matrix multiplication exceeds backend limits.
|
||||
This option does not change the separately created image upscaler context.
|
||||
|
||||
Repeating `--conv-direct` replaces the previous assignment string. Existing
|
||||
|
||||
+62
-12
@@ -2,20 +2,44 @@
|
||||
|
||||
## Get the Code
|
||||
|
||||
```
|
||||
```shell
|
||||
git clone --recursive https://github.com/leejet/stable-diffusion.cpp
|
||||
cd stable-diffusion.cpp
|
||||
```
|
||||
|
||||
- If you have already cloned the repository, you can use the following command to update the repository to the latest code.
|
||||
If you already cloned the repository without submodules, initialize them from the repository root:
|
||||
|
||||
```shell
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
To update an existing checkout and its submodules:
|
||||
|
||||
```shell
|
||||
cd stable-diffusion.cpp
|
||||
git pull origin master
|
||||
git submodule init
|
||||
git submodule update
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## MSVC compilation parallelism
|
||||
|
||||
MSVC builds limit parallel C/C++ compilation within each project by default.
|
||||
For `n` logical processors, the limit is `n` when `n <= 2`, `n - 1` when
|
||||
`3 <= n <= 8`, and `n - 2` when `n > 8`. The default is calculated when the
|
||||
build directory is first configured with this setting. Override it with
|
||||
`-DSD_MSVC_MP_COUNT=4`, or pass `-USD_MSVC_MP_COUNT` to recalculate it.
|
||||
|
||||
For Visual Studio builds, keep project-level parallelism at one to avoid
|
||||
multiplying the per-project limit:
|
||||
|
||||
```shell
|
||||
cmake --build build --config Release --parallel 1
|
||||
```
|
||||
|
||||
This setting controls MSVC's `/MP` option, not CUDA compiler parallelism or a
|
||||
hard CPU usage limit. Other build tools, such as Ninja, schedule compilation
|
||||
jobs separately; use `cmake --build build --parallel N` to limit those jobs.
|
||||
|
||||
## Selecting a GGML source tree
|
||||
|
||||
By default, sd.cpp builds the patched GGML submodule in `ggml/`. To build with
|
||||
@@ -27,8 +51,10 @@ cmake -S . -B build-upstream -DSD_USE_UPSTREAM_GGML=ON -DSD_GGML_SOURCE_DIR=../g
|
||||
cmake --build build-upstream --config Release
|
||||
```
|
||||
|
||||
The selected source tree supplies both the library and its private headers.
|
||||
Backend options such as `-DSD_CUDA=ON` apply to the selected tree as usual.
|
||||
Upstream compatibility mode uses the selected library and matching private
|
||||
headers. The patched GGML build uses public headers, including `ggml-ext.h` and
|
||||
`ggml-graph.h`. Backend options such as `-DSD_CUDA=ON` apply to the selected tree
|
||||
as usual.
|
||||
|
||||
`SD_USE_UPSTREAM_GGML` defaults to `OFF`, which enables the patched GGML
|
||||
extensions. Set it to `ON` when using upstream GGML; it selects the compatibility
|
||||
@@ -45,10 +71,12 @@ creating an inference context. Ordinary floating-point and shared GGML
|
||||
quantization types remain available, subject to backend operator support.
|
||||
|
||||
`SD_USE_SYSTEM_GGML=ON` instead links an installed GGML CMake package, located
|
||||
with `ggml_DIR` or `CMAKE_PREFIX_PATH`. In that mode, `SD_GGML_SOURCE_DIR` must
|
||||
point to the matching source tree for private headers. The installed library
|
||||
must use the same ABI settings as sd.cpp, including `GGML_MAX_NAME`.
|
||||
Set `SD_USE_UPSTREAM_GGML=ON` as well if the installed package is upstream GGML.
|
||||
with `ggml_DIR` or `CMAKE_PREFIX_PATH`. An installed patched package must include
|
||||
the extension and graph APIs; its source tree is not required for private
|
||||
headers. The installed library must use the same ABI settings as sd.cpp,
|
||||
including `GGML_MAX_NAME`. For an upstream package, also set
|
||||
`SD_USE_UPSTREAM_GGML=ON` and point `SD_GGML_SOURCE_DIR` to its matching source
|
||||
tree for the compatibility layer's private headers.
|
||||
|
||||
## WebP and WebM Support in Examples
|
||||
|
||||
@@ -90,8 +118,8 @@ cmake --build . --config Release
|
||||
|
||||
## Build with CUDA
|
||||
|
||||
Native SageAttention is included when using CUDA with patched GGML
|
||||
(`SD_USE_UPSTREAM_GGML=OFF`).
|
||||
The sd.cpp SageAttention extension is included when using CUDA with patched
|
||||
GGML (`SD_USE_UPSTREAM_GGML=OFF`).
|
||||
See [SageAttention](sage_attention.md) for GPU requirements and `--sage-attn` usage.
|
||||
|
||||
This provides GPU acceleration using NVIDIA GPU. Make sure to have the CUDA toolkit installed. You can download it from your Linux distro's package manager (e.g. `apt install nvidia-cuda-toolkit`) or from here: [CUDA Toolkit](https://developer.nvidia.com/cuda-downloads). Recommended to have at least 4 GB of VRAM.
|
||||
@@ -102,6 +130,28 @@ cmake .. -DSD_CUDA=ON
|
||||
cmake --build . --config Release
|
||||
```
|
||||
|
||||
CUDA extensions use sd.cpp's `thirdparty/cutlass` submodule by default
|
||||
for eligible INT8 GEMM and CuTe attention operations. If its headers are missing,
|
||||
CMake stops with an initialization hint; it does not download them automatically.
|
||||
Run `git submodule update --init --recursive` from the repository root before
|
||||
configuring.
|
||||
|
||||
To build with the existing CUDA implementations without CUTLASS, configure from
|
||||
the repository root with:
|
||||
|
||||
```shell
|
||||
cmake -S . -B build -DSD_CUDA=ON -DSD_CUDA_CUTLASS_PATH:PATH=
|
||||
cmake --build build --config Release
|
||||
```
|
||||
|
||||
Alternatively, set `SD_CUDA_CUTLASS_PATH` to another local CUTLASS source
|
||||
directory (tested with 3.9.2). Existing CMake caches retain their configured path;
|
||||
pass `-USD_CUDA_CUTLASS_PATH` when reconfiguring to restore the bundled default.
|
||||
Existing `GGML_CUDA_CUTLASS_PATH` caches are migrated on first configuration:
|
||||
the old bundled path becomes `thirdparty/cutlass`, while custom paths and an
|
||||
empty disabled value are preserved. An explicitly set `SD_CUDA_CUTLASS_PATH`
|
||||
takes precedence. Non-CUDA builds do not require CUTLASS.
|
||||
|
||||
## Build with HipBLAS
|
||||
|
||||
This provides GPU acceleration using AMD GPU. Make sure to have the ROCm toolkit installed.
|
||||
|
||||
+15
-1
@@ -18,12 +18,26 @@ docker run --rm --init -v /path/to/models:/models -v /path/to/output/:/output -p
|
||||
|
||||
## Building using Docker
|
||||
|
||||
Initialize the submodules from the repository root before building. Docker copies
|
||||
the local checkout, so its build context must include the submodule contents:
|
||||
|
||||
```shell
|
||||
git submodule update --init --recursive
|
||||
docker build -f docker/Dockerfile -t sd .
|
||||
```
|
||||
|
||||
## Building variants using Docker
|
||||
|
||||
### CUDA (NVIDIA GPU):
|
||||
|
||||
```shell
|
||||
docker build -f docker/Dockerfile.cuda -t sd-cuda .
|
||||
```
|
||||
|
||||
To select GPU architectures, pass `--build-arg "CUDA_ARCHITECTURES=89"` (for
|
||||
example, RTX 4090). Run the image on a host configured with NVIDIA Container
|
||||
Toolkit, using `docker run --rm --gpus all ... sd-cuda [args...]`.
|
||||
|
||||
### Vulkan:
|
||||
|
||||
```shell
|
||||
@@ -67,4 +81,4 @@ docker run --rm --device=/dev/dri --device=/dev/kfd --group-add video \
|
||||
# -v ./models:/models -v ./build:/output \
|
||||
# sd -m /models/sd-v1-4.ckpt -v -o /output/output.png \
|
||||
# -p "a lovely cat"
|
||||
```
|
||||
```
|
||||
@@ -0,0 +1,550 @@
|
||||
# ggml operator extensions
|
||||
|
||||
The patched ggml exposes `ggml-ext.h` for application-owned operators, kernel
|
||||
overrides, and graph fusion rules. Registrations are in-process and scoped to a
|
||||
backend device. Register them before capability queries or graph allocation;
|
||||
callback code and user data must remain alive until all dependent graphs and
|
||||
backends have been destroyed. Registration names are unique, and duplicate
|
||||
registrations fail rather than replacing live implementations.
|
||||
|
||||
## Operator definitions
|
||||
|
||||
`ggml_ext_register_op` registers a versioned name, attribute size, and an output
|
||||
metadata inference callback. `ggml_ext_op_create` creates an ordinary ggml tensor
|
||||
with explicit input dependencies. The initial interface supports one contiguous,
|
||||
out-of-place output with an existing `ggml_type`.
|
||||
|
||||
Attributes are copied into the unused portion of `op_params`. They are small,
|
||||
plain values; use `memcpy` when reading attributes that need stricter alignment.
|
||||
Auxiliary tensors must appear in `src`, not in an attribute pointer or user data.
|
||||
Registered operators use `GGML_OP_CUSTOM` internally, with an identifiable
|
||||
callback marker. Unsupported devices do not mistake them for CPU callbacks.
|
||||
These process-local descriptors are not a graph serialization format.
|
||||
|
||||
## Kernel selection
|
||||
|
||||
`ggml_ext_register_kernel(device, &desc)` associates a kernel with a built-in
|
||||
`ggml_op`, or with a registered custom operator. `supports` describes correctness
|
||||
constraints; `preferred` optionally decides whether to override an available
|
||||
native implementation. Higher priorities are considered first. When native
|
||||
execution is unsupported, a supported extension can run regardless of its
|
||||
preference callback.
|
||||
|
||||
Capability queries include registered single-node implementations, so the
|
||||
scheduler can select a device even when its native implementation rejects the
|
||||
operator. Callbacks inspect metadata only and must not recursively call the
|
||||
public capability query for the same device and operator. A fusion-only
|
||||
registration does not claim single-node support.
|
||||
|
||||
`ggml_ext_dev_supports_native_op` queries the original device implementation
|
||||
without extension dispatch. Fusion policies can use it to select a supported
|
||||
extension even when their performance preference normally favors native kernels.
|
||||
|
||||
`prepare` creates optional host-side plan state and reports workspace bytes.
|
||||
It can run during memory measurement, before tensor addresses are assigned, and
|
||||
must not launch work or allocate device buffers. Workspace is allocated by ggml
|
||||
before execution and included in scheduler memory estimates and buffer-size
|
||||
reports. `execute` receives the backend, workspace buffer, and current tensor
|
||||
addresses. `destroy` releases prepared state. A preparation or execution error
|
||||
is propagated; execution never retries a native implementation after launch.
|
||||
|
||||
Workspace is scratch storage shared by sequential extension steps in a plan;
|
||||
its capacity is the maximum request, not the sum across nodes. Contents are not
|
||||
preserved between calls. Queue work in backend execution order, and finish any
|
||||
private-stream use before subsequent backend work can access the workspace.
|
||||
Persistent state belongs in `prepare`/`destroy` state or explicit tensor inputs.
|
||||
|
||||
Overriding a built-in operator preserves its numerical and aliasing contract,
|
||||
including allocator reuse of input storage. Explicit views/in-place nodes are
|
||||
not intercepted by this first interface. Rebuild the scheduler allocation after
|
||||
changing graph metadata, selection policy, or registrations.
|
||||
|
||||
## Fusion
|
||||
|
||||
A `match` callback returns the number of consecutive nodes it can replace.
|
||||
The scheduler freezes this selection before allocating tensors. ggml checks
|
||||
compute flags, external uses, output flags, and views; the initial implementation
|
||||
accepts a conservative set of pure operators with only the final node exposed
|
||||
as the fused output. The rule remains responsible for mathematical equivalence,
|
||||
dtype, shape, layout, and hardware constraints. Internal views are accepted when
|
||||
their storage belongs to the fused subgraph. In-place intermediates are accepted
|
||||
only when their storage owner is an earlier node in the same match. External
|
||||
consumers and output flags still prevent eliminating those results. The final
|
||||
output must own storage, use a terminal `CPY`, or write back to an earlier owning
|
||||
node within the match. Internal writeback must cover exactly the owner's full
|
||||
shape, type, strides and storage offset, without external consumers of that
|
||||
owner. Output flags propagated to that owner are retained. Terminal `CPY` keeps
|
||||
its original destination and write semantics. The extension must handle input/output storage overlap, including
|
||||
allocator reuse, with scratch space when necessary. Other in-place writes are
|
||||
not eligible for extension fusion when they target storage outside the match.
|
||||
|
||||
An optional `optimize` callback runs before matching and allocation. Use
|
||||
`ggml_ext_graph_delay` to move a producer range after independent nodes. The
|
||||
helper rejects dependencies, unknown side effects, and writes that alias moved
|
||||
inputs or outputs. It can cross independent copies and known in-place operators
|
||||
when their storage does not alias the moved inputs or outputs. Owning dtype
|
||||
conversions can also be moved or crossed.
|
||||
Reordering must preserve edges and tensor metadata. Fusion legality is checked
|
||||
again after reordering.
|
||||
|
||||
External inputs stay allocated through the final node. Native graph optimization
|
||||
and execution operate on the remaining contiguous ranges, so native fusion cannot
|
||||
consume nodes selected by an extension. Fused intermediates are excluded from
|
||||
allocation through the backend's `skip_alloc` callback and
|
||||
`ggml_gallocr_set_skipped_tensors`. Their metadata and graph edges remain available,
|
||||
but execution must not access their data or buffers. Storage backing the final
|
||||
output is retained, including an internal owner used by an in-place final write.
|
||||
The skipped set is replaced for each allocation graph; changing which tensors are skipped
|
||||
invalidates cached allocation assignments even when tensor shapes are unchanged.
|
||||
|
||||
Automatic extension fusion is enabled in scheduler execution with one pipeline
|
||||
copy and without evaluation callbacks. Multiple pipeline copies and evaluation
|
||||
callbacks retain individual operations. Direct backend graph computation supports
|
||||
single-node extensions; it does not fuse an already allocated graph. Direct calls
|
||||
use temporary plans and synchronize before releasing them; scheduler plans and
|
||||
workspaces are reused across executions.
|
||||
|
||||
Use the backend graph APIs or the scheduler. The legacy CPU `ggml_graph_compute`
|
||||
and backend `graph_plan_*` interfaces do not dispatch these extensions.
|
||||
|
||||
## Backend services
|
||||
|
||||
Packed ComfyUI INT4 linear layers use `sd.linear_w4_convrot.v1`, built by
|
||||
`ggml_ext_mul_mat_w4_convrot`. The operator consumes byte-packed I8 weights and the
|
||||
explicit output of `sd.quantize_i8_convrot.v1`; F32 callers get a quantization
|
||||
node during graph construction. Logical output dimensions are stored in the
|
||||
operator attributes, so backend copies of the packed activation do not need to
|
||||
retain the producer's op marker. W4A4 uses row scales; W4A8 additionally exposes
|
||||
the codebook, channel scales, and FP8 relative scales as tensor dependencies.
|
||||
CPU, CUDA/HIP/MUSA, and Vulkan register kernels through the same extension API
|
||||
as INT8. No ggml submodule changes are required.
|
||||
|
||||
Services are discovered through `ggml_backend_reg_get_proc_address`:
|
||||
|
||||
| Service | Purpose |
|
||||
| --- | --- |
|
||||
| `ggml_ext_cpu_parallel_v1` | Run an application callback on the CPU backend's configured thread pool, with its abort callback. |
|
||||
| `ggml_ext_cuda_context_v1` | Borrow the current CUDA device and stream, ordered with native work. |
|
||||
| `ggml_ext_cuda_device_v1` | Get the physical CUDA ordinal for capability checks before backend creation, including virtual ggml devices. |
|
||||
| `ggml_ext_cuda_blas_v1` | Borrow the current stream's cuBLAS handle during execution, with host scalar pointers. |
|
||||
| `ggml_ext_gpu_device_v1` | Query CUDA/HIP/MUSA runtime kind, physical ordinal, shared-memory limit, warp size, and INT8 BLAS capability. |
|
||||
| `ggml_ext_gpu_context_v1` | Borrow the matching runtime's device and execution stream. |
|
||||
| `ggml_ext_gpu_blas_v1` | Borrow the matching cuBLAS, hipBLAS, or muBLAS handle on that stream. |
|
||||
| `ggml_ext_metal_device_v1` | Borrow the Metal device for application pipeline compilation. |
|
||||
| `ggml_ext_metal_buffer_v1` | Resolve a backend buffer range to its Metal buffer and offset, including private storage and split allocations. |
|
||||
| `ggml_ext_metal_submit_v1` | Encode application commands on a backend-owned command buffer, ordered with native Metal work and tracked by backend synchronization. |
|
||||
|
||||
The borrowed cuBLAS handle remains owned by the backend. Extensions must not retain
|
||||
or destroy it, or change its stream, pointer mode, math mode, or workspace. This
|
||||
CUDA-specific BLAS service is retained for existing callers; the runtime-aware
|
||||
services support CUDA, HIP, and MUSA with the same ownership rules. Extensions
|
||||
call vendor runtime and BLAS APIs directly. These services do not execute GEMM,
|
||||
quantization, or model fusion on the application's behalf.
|
||||
|
||||
Workspace is a backend buffer. Its base is a CUDA device pointer on CUDA; callers
|
||||
must use the representation appropriate to their backend. Vulkan extensions use
|
||||
the command recording services described below.
|
||||
|
||||
CUDA INT8 Linear, RoPE, ConvRot, RMS affine and INT8 gated residual kernels omit output
|
||||
scratch when allocation guarantees that a fresh owning output cannot overlap
|
||||
live inputs. Existing outputs and views retain overlap protection. Internal
|
||||
GEMM and quantization workspace is still reserved when required; kernels also
|
||||
handle a null workspace when the entire step needs no scratch.
|
||||
|
||||
Compute workspace measurement preserves the storage identities and offsets of
|
||||
external tensors, including views. Separate allocations must not share a synthetic
|
||||
buffer identity: that can invent overlap, overestimate scratch, and repeatedly
|
||||
rebuild a workspace whose actual allocation is smaller than the estimate.
|
||||
|
||||
## sd.cpp integration
|
||||
|
||||
`src/core/ggml_extensions.cpp` and `.h` contain the shared operator schemas,
|
||||
validation, and backend registration entry point. Graph construction helpers live
|
||||
in `src/core/ggml_extend.cpp` and `.h`; they take `GGMLRunnerContext*` so backend
|
||||
selection, compatibility workarounds, and shared ConvRot nodes use the same
|
||||
context as the calling model. Per-call precision, scaling, attention, and padding
|
||||
options remain explicit. Model blocks retain weight loading and adapter
|
||||
orchestration, while the shared helpers own FP8 fallback, embedding lookup,
|
||||
full/partial RoPE, grouped convolution, and backend-dependent repeat handling.
|
||||
LoRA/LoKr construction, n-mode products, and Kronecker products stay in
|
||||
`src/model/adapter/lora_ops.cpp` and `.h`, with the same runner-context interface.
|
||||
CPU kernels and fusion rules live in `src/core/cpu/`, with registration collected
|
||||
in `cpu/registration.cpp`. CUDA-style and Vulkan implementations live in their
|
||||
respective backend directories.
|
||||
|
||||
`src/core/metal/int8.mm` registers Metal INT8 linear and ConvRot kernels. Their
|
||||
Metal source is embedded at build time and compiled for the selected device
|
||||
during registration. The public `ggml-ext-metal.h` services expose borrowed
|
||||
objects without private backend headers. The submit callback only encodes work;
|
||||
GGML owns submission, cancellation checks, command-buffer lifetime, and
|
||||
asynchronous error tracking. Rotation scratch and any overlapping output use
|
||||
the scheduler workspace. No full floating-point weight copy is allocated.
|
||||
When runtime compilation supports Metal 4 TensorOps, INT8 multiplication uses
|
||||
`matmul2d` with INT32 cooperative results and fused scale/bias restoration.
|
||||
The SIMD-group implementation remains the fallback for older systems and
|
||||
devices where the TensorOps pipeline is unavailable.
|
||||
TensorOps multiplies at most 1024 pairs per partial result and combines those
|
||||
results with wrapping integer addition. This preserves exact INT32 semantics
|
||||
even when TensorOps uses floating-point arithmetic internally.
|
||||
|
||||
`ggml_ext_modulate(ctx, x, shift, scale)` emits `sd.modulate.v1` for F32 inputs
|
||||
when the runner context uses CPU, CUDA/HIP/MUSA, or Vulkan.
|
||||
Its explicit inputs are `x`, `scale`, and optional `shift`; it computes
|
||||
`(x + x * scale) + shift` with separate FP32 multiply/add rounding. The output
|
||||
is contiguous and out of place. Inputs may be strided and use ggml's
|
||||
dimension-wise repetition rules. Callers prepare token, batch, or frame layouts
|
||||
before constructing the operation; normalization and residual gates remain
|
||||
separate operations. CPU, CUDA/HIP/MUSA, and Vulkan have standalone kernels.
|
||||
Other backends (including Metal, SYCL, and OpenCL), upstream ggml builds, and
|
||||
other input types retain the original MUL/ADD graph.
|
||||
|
||||
ConvRot fusion matches the Modulate node directly, including scale-only
|
||||
modulation, instead of recognizing its former MUL/ADD/ADD decomposition.
|
||||
CUDA accepts contiguous per-channel parameters for `Modulate -> ConvRot` and
|
||||
`RMSNorm -> Mul(weight) -> Modulate -> ConvRot`. Vulkan limits the first fusion
|
||||
to widths 4096/6144/5376 and the RMSNorm fusion to width 5376.
|
||||
Other layouts execute the standalone operation followed by ConvRot. The
|
||||
gated-residual MUL/ADD fusion remains available for residual branches.
|
||||
|
||||
`src/core/cpu/rope.cpp` registers the CPU implementation of
|
||||
`sd.rope_apply.v1` and a
|
||||
`RMSNorm -> Mul -> RoPE` fusion. The fusion accepts a one-dimensional weight
|
||||
vector. Both interleaved and split-half RoPE, partial rotation, and strided input
|
||||
rows are supported. Model construction uses the custom operator on CPU, CUDA,
|
||||
HIP, MUSA, and Vulkan. Other backends can use the scheduler's CPU fallback.
|
||||
The former `ggml_rope_apply` API, builtin opcode, and CPU implementation have
|
||||
been removed from ggml.
|
||||
|
||||
`src/core/cuda/rope.cu` supplies standalone CUDA RoPE
|
||||
and `RMSNorm -> Mul -> RoPE [-> Scale -> FP16 Copy]` fusion. CuTe attention shares
|
||||
its RMSNorm/RoPE kernel. Partial rotation and arbitrary float-aligned input and
|
||||
position-encoding strides are supported by the standalone kernel; RMSNorm fusion
|
||||
requires contiguous position encoding and weights. Copy fusion uses workspace
|
||||
when the destination overlaps an input. These registrations do not require
|
||||
CUTLASS. The old CUDA/HIP/MUSA RoPE dispatch is removed.
|
||||
|
||||
Shared CUDA-style kernels live in `src/core/cuda/` and are compiled separately
|
||||
for each enabled runtime. `SD_CUDA_NAMESPACE` expands to `sd_cuda`, `sd_hip`, or
|
||||
`sd_musa`, so implementations can coexist. `runtime.h` handles the small runtime
|
||||
differences, including logical 32-lane shuffles on HIP. The application defines
|
||||
and launches its kernels directly; it includes only public ggml headers.
|
||||
CMake generates the registration table from enabled backend options. The shared
|
||||
entry point uses one `SD_BACKEND_EXTENSIONS` macro; backend registrars verify the
|
||||
device kind, and disabled backends add no symbol references.
|
||||
|
||||
`SD_USE_UPSTREAM_GGML` excludes these registrations. The stable-diffusion public C
|
||||
API is unchanged. INT8 ConvRot retains its existing packed activation layout and
|
||||
separate weight scale tensor.
|
||||
|
||||
`src/core/cuda/cute_attention.cu` supplies CuTe Flash Attention on CUDA when
|
||||
CUTLASS headers are configured. It registers an override of `FLASH_ATTN_EXT`,
|
||||
with the existing sequence-length preference and native fallback. It also
|
||||
registers Q preparation (`RMSNorm -> Mul -> RoPE`), output scaling, and packed
|
||||
ConvRot output fusion. These kernels use only the public extension API, borrowed
|
||||
CUDA stream, and scheduler-owned workspace; they do not use ggml's CUDA pool or
|
||||
private conversion helpers. The existing CUDA fusion and CuTe disable environment
|
||||
variables are honored.
|
||||
Aligned contiguous FP16 K/V inputs are read directly without reserving conversion
|
||||
buffers. Fresh F32 outputs are written by the attention epilogue, retaining the
|
||||
original FP16 rounding before output scaling. Packed ConvRot and preallocated or
|
||||
view outputs retain the intermediate half buffer.
|
||||
|
||||
`src/core/cuda/i8_projection.cu` supplies the INT8 GEMM projection epilogues:
|
||||
`GEMM -> Reshape -> RMSNorm -> Mul -> RoPE`, optional scaling and FP16 copy,
|
||||
and K/V prefix concatenation with FP16 output. CuTe can consume a directly
|
||||
produced FP16 Q without materializing the FP32 GEMM or RoPE intermediates.
|
||||
Independent affine operands, including broadcasted constants, are computed before
|
||||
their normalization/projection
|
||||
chain when graph dependencies allow it. The attention optimizer can then move
|
||||
the complete five-node projection when safe.
|
||||
These rules accept custom RoPE nodes. They preserve the original
|
||||
large-matrix selection thresholds, full interleaved head dimension 128, packed
|
||||
activation layout, and separate weight scales. External consumers prevent fusion;
|
||||
aliasing terminal copies use scheduler workspace. The CUDA projection kernels
|
||||
and matchers now live in sd.cpp. `GGML_CUDA_FORCE_CUBLAS` disables these epilogues.
|
||||
|
||||
`src/core/ggml_extensions.cpp` defines `sd.linear_i8.v1` and
|
||||
`sd.quantize_i8_convrot.v1`. `src/core/cpu/int8.cpp` supplies their CPU
|
||||
implementations. The CPU kernels use the
|
||||
configured thread pool and scheduler workspace; the old CPU INT8 GEMM and ConvRot
|
||||
implementations have been removed from ggml.
|
||||
Model code keeps calling `ggml_ext_linear_i8_tensorwise`,
|
||||
`ggml_ext_mul_mat_i8_tensorwise`, and `ggml_ext_quantize_i8_convrot`. The latter two
|
||||
constructors are declared and implemented in sd.cpp and create custom nodes.
|
||||
Their former ggml declarations, implementations, ConvRot opcode, and native
|
||||
backend paths have been removed. CPU, CUDA/HIP/MUSA, and Vulkan register kernels
|
||||
and fusion rules through the extension interfaces.
|
||||
|
||||
For already quantized activations, `ggml_ext_mul_mat_i8_tensorwise` accepts ordinary
|
||||
I8 weight and activation tensors. Its optional final `input_scale` parameter is a
|
||||
separate F32 tensor with one value per activation row. Weight scales have one
|
||||
value per output channel; weight scales and bias can be null. Existing calls
|
||||
omit `input_scale` and retain their original argument order. It computes `float(int32_dot(W, X)) * X_scale * W_scale + bias`, in that
|
||||
order. All scales are explicit graph dependencies; there is no quantized-tensor
|
||||
wrapper. Shapes use ggml's low-to-high dimension order. Call
|
||||
`sd_ggml_register_extensions` before scheduling these custom nodes.
|
||||
|
||||
`ggml_ext_mul_mat_i8_tensorwise` also accepts F32 activations (quantized into temporary
|
||||
workspace) or the output of `ggml_ext_quantize_i8_convrot`. ConvRot keeps the existing
|
||||
packed I8 activation rows, padded to a multiple of four rows, followed by F32 row
|
||||
scales. Weight scales remain separate. CPU ConvRot groups are 1, 4, 16, 64, or 256.
|
||||
Unsupported layouts and malformed scale shapes are rejected. Separate CPU quantize
|
||||
and GEMM phases propagate cancellation, and overlapping outputs use workspace.
|
||||
|
||||
`src/core/cuda/int8.cu` implements the same custom schemas on CUDA. It uses the
|
||||
borrowed cuBLAS handle for I8-by-I8-to-I32 GEMM and application-owned quantization,
|
||||
ConvRot, and rescaling kernels. F32 inputs are quantized into workspace; ordinary
|
||||
I8 inputs retain separate scale tensors and use a padded temporary only when
|
||||
needed by cuBLAS. Packed ConvRot inputs are consumed directly. Large ConvRot rows
|
||||
use multiple kernels when they exceed shared memory. Aliasing outputs use scratch.
|
||||
CUDA GEMM requires Turing or newer, contiguous tensors, K/N divisible by four,
|
||||
float-aligned inputs, and row counts that fit a padded signed 32-bit dimension;
|
||||
CUDA ConvRot uses groups of 256.
|
||||
Unsupported custom nodes can fall back to CPU through the scheduler.
|
||||
|
||||
When CUTLASS is configured, `src/core/cuda/i8_gemm.cu` supplies dense and SwiGLU
|
||||
INT8 GEMM kernels on Ampere or newer. It preserves the native tile selection,
|
||||
small-row policy, Stream-K scheduling, Ada column bands, and separate scaling
|
||||
order. The same extension matches `GEMM -> SwiGLU [-> ConvRot]` and row
|
||||
concatenation for custom GEMM nodes. Weight scales remain explicit
|
||||
inputs, including the gate/up scale permutation recorded on every SwiGLU launch.
|
||||
Two independent INT8 GEMMs sharing a quantized input can also fuse with split
|
||||
SwiGLU. A paired-column iterator reads the separate gate/up weight allocations,
|
||||
and the GEMM epilogue writes only the activated output. Both intermediate
|
||||
activations are omitted through the generic fusion allocation interface; weights
|
||||
are neither copied nor concatenated. This path requires bias-free branches,
|
||||
compatible layouts and alignments, and no external intermediate consumers.
|
||||
Only interleaved weight scales and any Stream-K workspace remain temporary.
|
||||
Plans reserve bounded Stream-K workspace, cache occupancy on first execution,
|
||||
and initialize barriers on every launch, so CUDA Graph replay observes updated
|
||||
inputs and scales. External consumers retain ordinary execution; overlapping
|
||||
outputs use workspace. For fresh outputs, the CUTLASS SwiGLU epilogue writes
|
||||
an allocator-managed tensor that the subsequent ConvRot launch consumes. This
|
||||
keeps the same GPU launches while allowing its storage to be reused by other
|
||||
operations. The combined SwiGLU/ConvRot path retains its workspace intermediate
|
||||
for other cases. Shapes or alignments unsuitable for CUTLASS use cuBLAS.
|
||||
Custom I8 nodes also retain cuBLAS when the reduction could overflow I32, to
|
||||
preserve their previous accumulation behavior. Fusion disable and force-cuBLAS
|
||||
settings are honored. No CUTLASS headers are needed for the cuBLAS path.
|
||||
|
||||
The native CUDA/HIP INT8 GEMM, standalone ConvRot, and associated fusion paths
|
||||
have been removed from ggml. The shared application implementation calls cuBLAS
|
||||
or hipBLAS directly. HIP retains the existing architecture eligibility policy;
|
||||
MUSA does not advertise INT8 BLAS support and uses scheduler fallback for GEMM.
|
||||
Standalone ConvRot kernels are compiled for all three runtimes. CUTLASS
|
||||
implementations remain CUDA-specific.
|
||||
|
||||
`src/core/cuda/i8_gated_residual.cu` implements
|
||||
`INT8 GEMM -> [Scale] -> Mul(gate) -> Add(residual) -> [Concat]` as a registered
|
||||
CUDA fusion, for packed ConvRot inputs and ordinary I8 nodes with separate activation
|
||||
scales. The corresponding native CUDA fusion rules and CUTLASS residual epilogue
|
||||
have been removed from ggml. Its optimizer moves independent gate producers ahead
|
||||
of GEMM and optional scaling through `ggml_ext_graph_delay`, before fusion matching.
|
||||
The former native INT8 affine reorder rule has also moved into this extension.
|
||||
It preserves the original tile selection, small-row
|
||||
threshold, Stream-K scheduling, Ada column bands, and separate FP32 rounding of
|
||||
gate multiplication and residual addition. The plan bounds Stream-K workspace
|
||||
over possible occupancy values using host metadata; execution verifies the
|
||||
actual requirement against that bound. Device occupancy is cached on first
|
||||
execution; each launch rebuilds parameters for the current tensor addresses and
|
||||
initializes its Stream-K barriers. Overlapping outputs use scratch, except
|
||||
for an exact in-place residual without concatenation. External consumers and
|
||||
unsupported layouts retain ordinary operator execution. Fusion and force-cuBLAS
|
||||
disable settings are honored.
|
||||
|
||||
`src/core/cuda/convrot.cu` registers CUDA fusion for `Scale -> ConvRot`,
|
||||
`Norm -> Mul -> ConvRot`, `Modulate -> ConvRot`,
|
||||
`RMSNorm -> Mul(weight) -> Modulate -> ConvRot`, `SwiGLU -> ConvRot`, and
|
||||
`SiLU/Sigmoid -> Mul -> ConvRot`.
|
||||
ConvRot outputs are custom nodes. SwiGLU accepts packed,
|
||||
swapped, or separate gate/up inputs. Independent modulation and up-branch
|
||||
producers are moved ahead of their consumers through the safe graph-delay API.
|
||||
Reordering keeps an owning producer together with in-place consumers, such as
|
||||
Krea2's RMSNorm weight multiplication, so the alias checks can preserve their
|
||||
storage relationship.
|
||||
The application kernel preserves the original reduction order, thread selection,
|
||||
ConvRot row padding, and separate scale tail. Prepared plans cache operation
|
||||
metadata, use the borrowed CUDA stream, and reserve scheduler workspace for
|
||||
overlapping outputs. Long rows or external intermediate consumers retain
|
||||
ordinary execution. These fusions do not require CUTLASS and honor
|
||||
`GGML_CUDA_DISABLE_FUSION`.
|
||||
|
||||
The corresponding scale, normalization, and unary-activation fusion rules have
|
||||
been removed from ggml for CUDA and HIP. GEMM/SwiGLU/ConvRot chains are selected by the
|
||||
application INT8 extension before the standalone activation fusion.
|
||||
|
||||
`src/core/cuda/affine.cu` registers FP32 `Mul(gate) -> Add(residual)`,
|
||||
`Concat -> [Permute] -> FP16 Copy`, and `Contiguous -> Scale -> Permute -> FP16
|
||||
Copy` fusions. Gate multiplication and residual addition retain separate FP32
|
||||
rounding. Copy layouts and alignment requirements match the original CUDA
|
||||
kernels; unsupported layouts, external intermediate consumers, self-residual
|
||||
Add inputs, and fusion disabling retain ordinary operators. Internal in-place
|
||||
Add outputs are accepted when they overwrite the complete fused Mul allocation. Exact in-place residual
|
||||
or input addresses are safe for the pointwise kernel; partial overlap, broadcast
|
||||
gate overlap, and FP16 copy overlap use scheduler workspace. These registrations
|
||||
require no CUTLASS, compile for CUDA/HIP/MUSA, and support reusable CUDA Graph
|
||||
replay. Their former native fusion implementations have been removed from ggml.
|
||||
|
||||
CUDA kernels can opt into device replay with `ggml_ext_register_kernel_ex` and
|
||||
`GGML_EXT_KERNEL_CAPTURE_SAFE`. The original registration function retains its
|
||||
per-execution callback behavior, and the descriptor ABI is unchanged. Opting in
|
||||
means the callback enqueues replayable device work: it has no required
|
||||
per-execution host side effects or decisions based on tensor contents, and any
|
||||
private streams rejoin the borrowed stream. Lazy setup must complete during the
|
||||
first ordinary execution. Tensor metadata, attributes, and host plan parameters
|
||||
remain frozen until scheduler reallocation; tensor contents may change normally.
|
||||
|
||||
When every selected extension opts in, the CUDA backend can capture the whole
|
||||
scheduled plan, including its native fragments, into one CUDA Graph. The first
|
||||
execution warms up kernels and library state while retaining native fragment
|
||||
caches. The next stable execution captures
|
||||
and launches the graph; later calls replay it. Changing a tensor address or the
|
||||
execution stream invalidates the capture and repeats warmup, even when the graph
|
||||
UID is unchanged. Capture failures propagate without retrying partially executed
|
||||
work. An enclosing stream capture records the plan directly instead of starting
|
||||
a nested capture.
|
||||
|
||||
`GGML_EXT_KERNEL_REPLAY_REUSABLE` additionally permits reuse across scheduler
|
||||
plan rebuilds and requires `CAPTURE_SAFE`. Recorded work must depend only on
|
||||
complete tensor metadata, addresses, the device and immutable registration data.
|
||||
Captured resources must belong to the backend or the supplied workspace, never
|
||||
per-plan host state. Workspace contents must be initialized by recorded device
|
||||
work on each execution. Prepare/destroy still run for every rebuilt host plan.
|
||||
The backend cache retains only the executor and workspace; it compares graph
|
||||
structure, tensor metadata/addresses, selected kernels and workspace requirements
|
||||
exactly, without dereferencing old graph objects. A new metadata arena can reuse
|
||||
an equivalent capture. Each backend retains at most 16 retired plans and 256 MiB
|
||||
of their workspaces, evicting the oldest entries first. Larger workspaces retain
|
||||
plan-local capture only. Backend destruction synchronizes and releases the cache.
|
||||
This allows sd.cpp's per-step scheduler rebuilds to retain device replay.
|
||||
|
||||
Mixed plans containing an extension without the flag retain ordinary dispatch.
|
||||
Evaluation callbacks, multiple pipeline copies, and direct backend graph calls
|
||||
also retain their previous behavior. `GGML_CUDA_DISABLE_GRAPHS` disables whole-plan
|
||||
capture as well as native CUDA Graph caching. Native operations that cannot be
|
||||
captured retain the existing fragment execution path. Whole-plan replay requires
|
||||
CUDA VMM so native scratch addresses remain valid for the backend lifetime;
|
||||
legacy scratch pools retain the existing fragment path. HIP and MUSA do not yet
|
||||
provide this plan executor. sd.cpp's CUDA registrations opt in to reusable replay.
|
||||
|
||||
## Vulkan execution services
|
||||
|
||||
`ggml-ext-vulkan.h` exposes `ggml_ext_vulkan_v1` through the Vulkan backend
|
||||
registry. The versioned function table reports device limits and subgroup
|
||||
capabilities, resolves tensor/workspace byte ranges, dispatches application
|
||||
SPIR-V, and copies nonoverlapping buffer ranges. Extensions do not include
|
||||
private ggml Vulkan headers or own backend command buffers and descriptors.
|
||||
|
||||
Shaders use the `main` entry point, consecutive storage-buffer bindings starting
|
||||
at zero, and consecutive 32-bit specialization constants. Dispatch accepts actual
|
||||
workgroup counts. The backend validates ranges and dispatch limits, caches the
|
||||
complete shader/configuration per device, and releases pipelines with that
|
||||
device. Push constants and specialization values are copied; the backend does
|
||||
not retain application execution state. Pipeline creation occurs on execution,
|
||||
never during host-only plan preparation.
|
||||
|
||||
Bindings identify either a tensor or a backend buffer, with a byte offset and
|
||||
optional byte count. `buffer_info` returns an opaque storage identity and byte
|
||||
range for alias checks. Vulkan descriptor offsets are rounded down to the
|
||||
device's required alignment; shaders must add the returned `shader_offset`.
|
||||
Workspace bindings follow the same rules. Invalid or foreign-device ranges are
|
||||
rejected before recording commands. Kernel bounds and shader capability
|
||||
requirements remain the extension's responsibility.
|
||||
|
||||
Dispatch and copy use the backend's existing compute command context, descriptor
|
||||
pool, transfer waits, and memory barriers. They preserve ordering with native
|
||||
fragments and invalidate cached converted operands after writes. Normal backend
|
||||
synchronization completes queued work before host access or resource release.
|
||||
Vulkan copy ranges must be equal in size, nonoverlapping, and aligned to four
|
||||
bytes. There is no per-dispatch host wait or extension-owned queue.
|
||||
|
||||
Scheduled Vulkan plans share submission counters and FLOP thresholds across
|
||||
native fragments and extension callbacks. Native scratch is initialized once
|
||||
per plan invocation, with its offsets retained across fragments. Fragment
|
||||
boundaries do not force a
|
||||
queue submission, and the final pending commands are submitted when the plan
|
||||
returns. Extension dispatches and copies count toward the device's node limit.
|
||||
Every callback still executes on every invocation; this batching neither
|
||||
captures device replay nor retains plan state across scheduler rebuilds.
|
||||
Callback errors propagate without retry. Evaluation callbacks, direct backend
|
||||
calls, synchronous devices, serialized diagnostics, and timestamp profiling
|
||||
retain fragment execution.
|
||||
|
||||
`src/core/vulkan/rope.cpp` registers the custom RoPE implementation,
|
||||
plus `RMSNorm -> Mul -> RoPE` and `RMSNorm -> Mul -> RoPE -> Scale -> FP16 Copy`
|
||||
fusion. These kernels retain the original strides, partial-rotation behavior,
|
||||
128-channel subgroup reduction, and separate arithmetic rounding. Their SPIR-V
|
||||
is generated from application shaders with the native RoPE compilation policy;
|
||||
SPIR-V optimization is disabled because it changes the reduction results.
|
||||
Safe graph-delay rules bring independent producers ahead of fusion candidates.
|
||||
External intermediate consumers retain ordinary operators, and overlapping
|
||||
outputs use plan workspace. `GGML_VK_DISABLE_FUSION` is honored.
|
||||
|
||||
The corresponding Vulkan shader sources and native fusion rules have been
|
||||
removed from ggml. Unsupported custom nodes retain scheduler fallback to
|
||||
another supported backend.
|
||||
|
||||
RoPE Apply, INT8/ConvRot, CuTe attention, SageAttention, and Sol-Attn use
|
||||
application-owned implementations. This interface does not add dynamic dtypes.
|
||||
|
||||
`src/core/vulkan/int8.cpp` owns Vulkan INT8 GEMM, ConvRot, and their fusion rules.
|
||||
The Vulkan compute metadata service exposes shared-memory limits, subgroup
|
||||
arithmetic, and cooperative-matrix properties; shape selection stays in sd.cpp.
|
||||
Shader variants preserve the former tiling and compilation policy, including
|
||||
SwiGLU, residual, row-concatenation, and modulation paths. Kernels use public
|
||||
Vulkan dispatch, buffer, copy, and workspace services without private backend
|
||||
headers. Vulkan currently handles packed group-256 activations with separate
|
||||
weight scales; unsupported custom nodes fall back to CPU.
|
||||
|
||||
## SageAttention
|
||||
|
||||
The `ggml_ext_sage_attn` entry point is declared and implemented in sd.cpp's
|
||||
graph construction helpers. It constructs `sd.sage_attn.v1` with explicit Q/K/V inputs
|
||||
and scale/mode attributes. ggml has no Sage-specific opcode, allocation rule, or
|
||||
backend dispatch branch.
|
||||
|
||||
`src/core/cuda/sage_attention.cu` and its `sageattention/` device headers own the
|
||||
CUDA implementation. Quantization and temporary attention output use scheduler
|
||||
workspace, rather than an oversized output tensor. The implementation obtains
|
||||
the backend stream through the public GPU service and converts the final FP16
|
||||
output with a local CUDA kernel. It retains the SM80 compatibility path and the
|
||||
SageAttention2/2++ toolkit and architecture requirements. Capability checks use
|
||||
the application's compiled CUDA target as well as the physical device.
|
||||
|
||||
CPU, HIP, MUSA, and Vulkan do not implement this NVIDIA-specific kernel. Existing
|
||||
model-level capability checks retain flash/default attention for unsupported
|
||||
layers; a custom Sage node itself does not silently change algorithms.
|
||||
|
||||
## Sol-Attn
|
||||
|
||||
The `ggml_ext_sol_attn` entry point lives in the common sd.cpp graph construction
|
||||
files and constructs `sd.sol_attn.v1`. It accepts contiguous F32 Q/K/V tensors
|
||||
with shape `[128, tokens, heads, batch]`, a positive finite scale, and finite tau.
|
||||
The output remains F32 `[128, heads, tokens, batch]`. No current sd.cpp model or
|
||||
CLI path calls this entry point; the migration preserves its low-level behavior.
|
||||
|
||||
`src/core/cuda/sol_attention.cu` owns packing, block summaries, threshold
|
||||
selection, and the BF16 tensor-core kernel. Temporary storage is separate
|
||||
scheduler workspace, so the output allocation contains only the output tensor.
|
||||
The kernel uses the public backend stream service and direct CUDA/PTX operations.
|
||||
Registration requires CUDA 12 or newer, an SM80-or-newer device, and an application
|
||||
kernel compiled for SM80 or newer. CPU, HIP, MUSA, and Vulkan do not advertise
|
||||
support. ggml no longer contains a Sol-Attn opcode, constructor, allocation rule,
|
||||
or backend kernel.
|
||||
|
||||
## Public graph access
|
||||
|
||||
`ggml-graph.h` exposes leaf enumeration, append-only leaf construction, recorded
|
||||
use counts, mapped inference-graph copying, use-count copying for segments, and
|
||||
backend execution of a node range. Mapped copies retain node order and consumer
|
||||
counts from the original graph, including consumers outside a graph view. The
|
||||
caller owns tensor copies and remaps their storage and edges; invalid mappings
|
||||
leave the destination graph unchanged. Range execution uses already allocated
|
||||
tensors and retains the original graph's consumer counts.
|
||||
|
||||
Workspace measurement, graph cuts, and evaluation callbacks use these APIs.
|
||||
Normal sd.cpp builds require only ggml's public headers. Compatibility with an
|
||||
upstream ggml lacking this header is isolated in `src/core/ggml_compat.cpp`; that
|
||||
optional build still requires matching upstream private headers. The compatibility
|
||||
layer also retains the workaround for old Metal backends wrapping failed host
|
||||
mappings. The patched Metal backend returns null directly when mapping fails.
|
||||
@@ -100,10 +100,13 @@ for individual references, identity images, or video control frames.
|
||||
--image-preprocess "target=control-frame,index=2,canny=true"
|
||||
```
|
||||
|
||||
Init and mask sources must have the same dimensions. The mask inherits the
|
||||
init crop, resize, and padding coordinates, while retaining its own filter,
|
||||
padding value, and Canny setting. Conflicting mask geometry is rejected. An
|
||||
omitted mask remains absent until the original pipeline creates its default mask.
|
||||
If init and mask source dimensions differ, the mask is first center-cropped to
|
||||
the init source aspect ratio and resized to the init source dimensions using
|
||||
nearest-exact interpolation. The mask then inherits the init crop, resize, and
|
||||
padding coordinates, while retaining its own filter, padding value, and Canny
|
||||
setting for that transform. Explicit mask geometry is evaluated after this
|
||||
alignment; conflicting geometry is rejected. An omitted mask remains absent
|
||||
until the original pipeline creates its default mask.
|
||||
|
||||
## Downstream behavior
|
||||
|
||||
|
||||
+71
-4
@@ -2,7 +2,11 @@
|
||||
|
||||
sd.cpp can load and execute ComfyUI `int8_tensorwise` safetensors with `convrot` metadata directly. The stored INT8 weights are not converted to another weight type at load time.
|
||||
|
||||
This requires the INT8 tensorwise/convrot extensions in the patched GGML.
|
||||
This uses sd.cpp's INT8 tensorwise/convrot operators through the patched GGML
|
||||
extension API. Schemas and graph builders live in sd.cpp, alongside the
|
||||
backend kernels and fusion rules; GGML supplies generic dispatch and workspace
|
||||
services. See [operator extensions](ggml_extensions.md) for the implementation
|
||||
and the preserved INT8 API entry points.
|
||||
Builds with `SD_USE_UPSTREAM_GGML=ON` reject these files during loading.
|
||||
|
||||
## Checkpoint format
|
||||
@@ -89,13 +93,76 @@ The packed runtime activation tensor contains the I8 activation rows and their f
|
||||
## Backend support
|
||||
|
||||
- CPU provides the portable regular Hadamard, activation quantization, INT8 matrix multiplication, and scale restoration implementations.
|
||||
- NVIDIA CUDA devices with compute capability 7.5 or newer use the native accelerated path. For H256, CUDA fuses the rotation, row-wise maximum reduction, and activation quantization. It uses cuBLAS for I8 x I8 to I32 GEMM and a CUDA kernel for scale restoration and bias addition.
|
||||
- Metal supports INT8 tensorwise and convrot on Apple silicon (Apple GPU family 7 or newer). It keeps weights in INT8 and rotates and quantizes activations on the GPU. On macOS/iOS 26 or newer, it probes Metal TensorOps `matmul2d` with INT8 inputs and INT32 cooperative results. It combines 1024-product partial sums using wrapping integer addition to preserve exact accumulation on older GPUs, then applies scales and bias in registers. Older systems or unavailable TensorOps pipelines use SIMD-group matrix multiplication: INT8 tiles are converted to FP16, and FP32 partial sums cover at most 256 products before being combined as integers, preserving the I32 accumulation contract. Convrot groups 1, 4, 16, 64, and 256 are supported. These kernels require the patched GGML Metal extension services and are enabled by `SD_METAL=ON`.
|
||||
- NVIDIA CUDA devices with compute capability 7.5 or newer use the native accelerated path. For H256, CUDA fuses the rotation, row-wise maximum reduction, and activation quantization. Eligible shapes use the CUTLASS INT8 implementation when enabled; other supported shapes use cuBLAS for I8 x I8 to I32 GEMM and a CUDA kernel for scale restoration and bias addition.
|
||||
- AMD HIP devices in the CDNA, RDNA3 (including RDNA3.5), and RDNA4 families use the same INT8/H256 kernels with hipBLAS for I8 x I8 to I32 GEMM. Other AMD architectures fall back to CPU for INT8 matrix multiplication.
|
||||
- Vulkan provides native H256 activation quantization and INT8 matrix multiplication when the build and device support accelerated packed INT8 dot products. Unsupported configurations and GPU backends without these kernels use the backend scheduler to fall back to CPU.
|
||||
- MUSA shares the CUDA-style H256 activation kernels. INT8 GEMM falls back to CPU because the MUSA backend does not advertise INT8 BLAS support.
|
||||
- Vulkan provides H256 activation quantization and INT8 matrix multiplication when the build and device support accelerated packed INT8 dot products. Unsupported configurations and GPU backends without these kernels use the backend scheduler to fall back to CPU.
|
||||
|
||||
LoRA adapters are applied at runtime without modifying the INT8 weights. The INT8 convrot path computes the base linear output, while LoRA, LoHa, LoKr, and raw weight-difference adapters compute their output corrections from the original, unrotated activation and add them to the base output. `--lora-apply-mode auto` selects this path for models containing INT8 tensorwise weights. If `immediately` is requested, sd.cpp falls back to runtime application because merging an adapter would require dequantizing and rotating its weight update, then recalculating the per-row scales and requantizing the result.
|
||||
|
||||
The dedicated CUDA and HIP convrot activation paths currently require a group size of `256`; other supported group sizes use CPU execution.
|
||||
The shared CUDA/HIP/MUSA convrot activation paths support H64 and H256. Vulkan also supports H64 activation quantization; its INT8 matrix multiplication path currently requires H256.
|
||||
|
||||
## Packed INT4 convrot
|
||||
|
||||
ComfyUI `convrot_w4a4` and `asym_w4a8_int8` safetensors load directly, including
|
||||
checkpoints that mix packed INT4, INT8 tensorwise, and floating-point layers.
|
||||
Packed weights retain their checkpoint byte layout; they are not expanded or
|
||||
converted to GGUF Q4 blocks at load time.
|
||||
|
||||
- `convrot_w4a4`: signed 4-bit weights, low nibble first, with F32 per-output
|
||||
`weight_scale`. Activations use the same INT8 convrot quantization as above;
|
||||
the format name does not select a native INT4 activation kernel.
|
||||
- `asym_w4a8_int8`: 4-bit indices into `weight_codebook` (16 F32 values), F32
|
||||
`weight_s_channel`, and F8_E4M3 `weight_s_rel` per 16 input features. The
|
||||
metadata must specify `group_size: 16`. CPU evaluates the floating-point
|
||||
codebook; GPU kernels requantize it to INT8 with `amax / 127`, introducing a
|
||||
small additional rounding error.
|
||||
|
||||
Both formats imply convrot rotation even without a `convrot` flag; missing
|
||||
`convrot_groupsize` defaults to 256.
|
||||
|
||||
Both formats reuse the convrot activation cache when the input scale is `1` and
|
||||
produce F32 outputs with optional bias. CPU provides a portable implementation.
|
||||
CUDA/HIP/MUSA use shared packed-weight kernels without requiring INT8 BLAS.
|
||||
With CUTLASS enabled, NVIDIA
|
||||
SM80 and newer devices use mixed-input S8 x S4 GEMM for eligible W4A4 shapes,
|
||||
reusing the INT8 scale epilogue without expanding the full weight tensor. W4A8
|
||||
uses CUTLASS integer MMA per 16-value group with FP32 scale restoration.
|
||||
Other CUDA shapes and builds retain the packed byte-dot path. Set
|
||||
`GGML_CUDA_W4_FORCE_DP4A=1` to compare against that path. Supported AMD targets
|
||||
use packed byte-dot instructions, with portable byte arithmetic for other targets.
|
||||
Vulkan requires integer dot product support and at least 13312 bytes of shared
|
||||
memory for W4A4 or 16452 bytes for W4A8. GPU W4 kernels require the logical input
|
||||
width to be divisible by 64; H64 and H256 are supported. When supported by the
|
||||
compiler and device, Vulkan uses NV cooperative matrix integer GEMM. W4A4
|
||||
decodes packed weights directly into cooperative matrices for aligned input
|
||||
and output widths. Complete activation tiles and the final partial tile use
|
||||
separate shader specializations. Other W4A4 shapes use double-buffered weight
|
||||
and activation tiles (20480 bytes of shared memory, or 30720 for larger tiles).
|
||||
W4A8 stages both weights and activations in shared memory in K=64 tiles,
|
||||
reusing them for each 16-value group's integer
|
||||
MMA and FP32 scale restoration. FP8 scales use a shared lookup table, and
|
||||
expanding projections visit activation tiles in groups of weight panels for
|
||||
cache reuse. Adjacent groups share a K=32 activation fragment, with
|
||||
complementary zero padding in the weights to preserve each group's scale.
|
||||
This path uses 17476 bytes of shared memory, or 22596 for larger tiles.
|
||||
Both paths use 256-thread workgroups and keep the model's
|
||||
weights packed; neither expands a full layer in device memory. Other Vulkan
|
||||
devices retain the packed dot-product path; set `GGML_VK_W4_FORCE_DP4A=1` to compare
|
||||
against it. FP32 accumulation order can differ between these paths.
|
||||
Other shapes use CPU fallback. HIP W4A8 selects wave64 WMMA by the active
|
||||
compilation target's intrinsic support, preferring the GFX12 fragment layout
|
||||
over the GFX11 layout.
|
||||
There is no GPU architecture allowlist: newer targets supporting either
|
||||
intrinsic automatically use its matching layout. Selection happens per device
|
||||
image, including multi-architecture builds; targets supporting neither intrinsic
|
||||
retain the integer-dot fallback. W4A4 retains integer dot products on HIP.
|
||||
|
||||
LoRA application remains at runtime, using the original unrotated activations
|
||||
and logical weight dimensions. Packed weights and their codebook/scale tensors
|
||||
are excluded from weight type conversion. `SD_USE_UPSTREAM_GGML` builds reject
|
||||
these formats because they require the application extension API.
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -99,10 +99,88 @@ The C API exposes the same inputs through `ref_images`, `ref_videos`, and
|
||||
`ref_audios` in `sd_vid_gen_params_t`. Each `sd_ref_video_t` supplies its own
|
||||
frame rate and optional soundtrack; non-24-fps inputs are resampled internally.
|
||||
|
||||
## Latent hires second pass (two-stage sampling)
|
||||
|
||||
MiniMax-H3 supports `--hires` with two interchangeable second-stage upscalers:
|
||||
|
||||
- an interpolating latent upscaler (`Latent`, `Latent (nearest)`,
|
||||
`Latent (bicubic)`, ...): after the first pass finishes, the packed AV latent
|
||||
is resampled in latent space to `--hires-width` x `--hires-height` and
|
||||
refined by a second sampling pass;
|
||||
- a model-backed upscaler (`--hires-upscaler <name>` + `--hires-upscalers-dir`):
|
||||
a H3-shaped learned latent upscaler checkpoint runs between the passes,
|
||||
like the LTX spatial upsampler. This is the sd.cpp equivalent of the
|
||||
two-sampler workflows used for H3 in node-based frontends (e.g. the
|
||||
community 3D-conv latent upscaler from
|
||||
[LBH-123-AI/Minimax_h3_latent_Upscaler](https://huggingface.co/LBH-123-AI/Minimax_h3_latent_Upscaler)).
|
||||
|
||||
Draft at a low megapixel count (e.g. 0.4 MP) and refine at a higher one
|
||||
(e.g. 0.8 MP) instead of paying for the full resolution in every step:
|
||||
|
||||
```sh
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\minimax_h3_fl2va-Q4_K_M.gguf --vae ..\models\vae\minimax_h3_video_vae_fp16.safetensors --audio-vae ..\models\vae\minimax_h3_audio_vae_fp32.safetensors --llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf -p "a lovely cat" --cfg-scale 1.0 -v -W 864 -H 480 --diffusion-fa --offload-to-cpu --rng cpu --video-frames 56 --hires --hires-upscaler "Latent (bicubic)" --hires-width 1216 --hires-height 672 --hires-steps 4 --hires-denoising-strength 0.4
|
||||
```
|
||||
|
||||
or with the learned upscaler:
|
||||
|
||||
```sh
|
||||
.\bin\Release\sd-cli.exe -M vid_gen ... --hires --hires-upscalers-dir ..\models\latent_upscale_models --hires-upscaler h3-latent-upscaler-3d --hires-width 1216 --hires-height 672 --hires-steps 4 --hires-denoising-strength 0.4
|
||||
```
|
||||
|
||||
Notes for this mode:
|
||||
|
||||
- The learned upscaler expects a second normalization of the diffusion latent,
|
||||
matching its training convention. The runner applies and reverses this extra
|
||||
transform internally; callers should pass the usual H3 diffusion latents.
|
||||
- The frame count is never changed by the second stage, so the packed audio
|
||||
latent length stays valid and the generated soundtrack is preserved.
|
||||
- Width and height still have to be multiples of 32; the hires target is
|
||||
aligned like any other resolution. The learned upscaler additionally wants
|
||||
an even latent size on both axes, which 32-pixel targets provide.
|
||||
- `--hires-sigmas` can replace `--hires-denoising-strength` when the second
|
||||
stage should resume from an explicit sigma schedule, which is how most
|
||||
two-stage H3 workflows split the sampler.
|
||||
- Keyframe conditioning (`--init-img` / `--end-img`) is not available together
|
||||
with hires yet: those latents are encoded at the first stage resolution and
|
||||
would have to be re-encoded for the hires canvas. Use T2VA or Ref2VA.
|
||||
- The second stage runs at the larger latent, so it is the memory-hungry half;
|
||||
combine it with quantization, `--vae-tiling` and `--offload-to-cpu` on
|
||||
small cards.
|
||||
- The learned upscaler is scale-conditional: the target-to-source ratio is
|
||||
fed into the network as an embedding, 1.2-2.0x is the trained domain, and
|
||||
downscaling is rejected.
|
||||
|
||||
## Shape and runtime notes
|
||||
|
||||
- Width and height are aligned upward to a multiple of 32.
|
||||
- Frame count is aligned upward to the `17k + 5` grid, with a minimum of 5.
|
||||
- MiniMax-H3 runs at 24 fps; another requested value is overridden.
|
||||
- Attention head grouping is controlled by
|
||||
`--model-args minimax_h3_attn_chunk_budget=2`, in GiB (default: `2`).
|
||||
The budget estimates the size of a single FP32 Q/K tensor per group:
|
||||
`head_dim * tokens * batch * 4 bytes * heads_per_group`. The group size adapts
|
||||
to the sequence length, with a minimum of one head; inputs that fit use all
|
||||
heads together. Set `0` to disable grouping, or a smaller budget such as
|
||||
`0.25` to reduce peak activation memory for long sequences at the cost of
|
||||
extra launches and concatenation. Each head still attends to the full sequence.
|
||||
This is a grouping target, not a cap on total attention memory or VRAM usage;
|
||||
other intermediates, backend workspace, and weights require additional memory.
|
||||
`--max-vram` and `--offload-to-cpu` control weight residency, but the active
|
||||
block's working memory must also fit within the VRAM budget.
|
||||
- The default video flow shift is 12. The audio stream is mapped internally to
|
||||
its shift of 3, so the regular samplers can operate on the packed AV latent.
|
||||
|
||||
## HyperFlow H3 Flash
|
||||
|
||||
[HyperFlow H3 Flash](https://huggingface.co/VideoRebirthAI/hyperflow-h3-flash)
|
||||
uses the shared components and input options described in this guide. Replace
|
||||
`--diffusion-model` with the matching HyperFlow FL2VA or Ref2VA checkpoint and
|
||||
use these sampling options:
|
||||
|
||||
```sh
|
||||
--sampling-method euler --cfg-scale 1 --sigmas hyperflow-h3-flash-6
|
||||
```
|
||||
|
||||
Use `hyperflow-h3-flash-4` or `hyperflow-h3-flash-8` for 4 or 8 steps. These
|
||||
presets also work with `--hires-sigmas`; their length determines the step count.
|
||||
They already include video flow shift 12, so keep the default flow shift.
|
||||
@@ -60,6 +60,8 @@ static UNetConfig detect_from_weights(const String2TensorStorage& tensor_storage
|
||||
```
|
||||
|
||||
Use `TensorStorage` metadata, especially `n_dims` and `ne`, to infer shapes.
|
||||
Use `logical_ne0()` for Linear input widths; packed INT4 weights store half of
|
||||
the logical width in `ne[0]`. Keep raw `ne` values for storage layout calculations.
|
||||
Do not load or parse tensor data for config detection.
|
||||
|
||||
Detection should respect `prefix`. For nested weights, construct full names from
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# How to Use
|
||||
|
||||
P-H3-Image supports text-to-image and reference-image editing. It uses a MiniMax-H3 diffusion transformer, a single-frame VAE, and Qwen3-VL-4B with a text connector. Prompt enhancement is not included.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download P-H3-Image
|
||||
- safetensors: https://huggingface.co/PrunaAI/P-H3-Image-Comfy/tree/main/diffusion_models
|
||||
- Download P-H3-Image VAE
|
||||
- safetensors: https://huggingface.co/PrunaAI/P-H3-Image-Comfy/tree/main/vae
|
||||
- Download Qwen3-VL-4B with the text connector
|
||||
- https://huggingface.co/PrunaAI/P-H3-Image-Comfy/tree/main/text_encoders
|
||||
|
||||
Alternatively, reuse Qwen3-VL-4B, including its vision tower, with the [separate text connector](https://huggingface.co/PrunaAI/P-H3-Image/blob/main/text_connector/diffusion_pytorch_model.safetensors). Save the connector as `p-h3-image_text_connector.safetensors` and pass it with `--embeddings-connectors`.
|
||||
|
||||
Use the P-H3 image VAE; the MiniMax-H3 video VAE is not compatible. No audio VAE is needed.
|
||||
|
||||
## Examples
|
||||
|
||||
### Text-to-image
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\p-h3-image_int8_convrot.safetensors --llm ..\models\text_encoders\qwen3vl_4b_bf16.safetensors --embeddings-connectors ..\models\text_encoders\p-h3-image_text_connector.safetensors --vae ..\models\vae\p-h3-image_vae_bf16.safetensors --cfg-scale 1.0 --steps 8 --sampling-method euler --sigmas p-h3-image -p "a lovely cat holding a sign says 'p-h3-image.cpp'" -v --offload-to-cpu --rng cpu -H 2912 -W 2912
|
||||
```
|
||||
|
||||
### Image editing
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\p-h3-image_int8_convrot.safetensors --llm ..\models\text_encoders\qwen3vl_4b_bf16.safetensors --embeddings-connectors ..\models\text_encoders\p-h3-image_text_connector.safetensors --vae ..\models\vae\p-h3-image_vae_bf16.safetensors --cfg-scale 1.0 --steps 8 --sampling-method euler --sigmas p-h3-image -r source.png -r reference.png -p "Edit <Picture 1>. Give the cat the scarf from <Picture 2>, preserving its face and the background." -v --offload-to-cpu --rng cpu -H 2912 -W 2912
|
||||
```
|
||||
|
||||
Reference images are numbered in argument order as `<Picture 1>`, `<Picture 2>`, and so on. A plain edit instruction gets `Edit <Picture 1>. ` prepended automatically. Prompts starting with `Edit <Picture N>` or `subject_definitions:` are used directly.
|
||||
|
||||
Use dimensions divisible by 32. The upstream default is 2912 × 2912; smaller canvases reduce memory use. Use CFG 1 and `--sigmas p-h3-image` for the upstream 8-step schedule. Keep the default VAE tiling size of 256 pixels.
|
||||
@@ -159,6 +159,25 @@ allocator/scheduler capacity. Compatible gallocr reservations are reused across
|
||||
graphs; idle workspaces can be reclaimed under pressure and are freed at runner
|
||||
completion. Cross-graph caches survive individual graphs, but cut buffers do not.
|
||||
|
||||
On discrete Vulkan devices, runners reuse released device buffers of the same
|
||||
size to reduce allocation overhead during segmented execution. This idle buffer
|
||||
cache is limited to 1 GiB per device and is reclaimed before allocating a new
|
||||
size or at runner completion. It is outside the managed `--max-vram` budget,
|
||||
so physical VRAM usage can be higher. Set `GGML_VK_REUSE_BUFFERS=0` to disable
|
||||
it. Unified-memory and host-memory modes do not use this cache.
|
||||
|
||||
Discrete NVIDIA and non-GCN AMD Vulkan devices use a separate transfer queue by
|
||||
default when available and graphics queue use is disabled, so weight prefetch can overlap compute. Set
|
||||
`GGML_VK_ASYNC_USE_TRANSFER_QUEUE=0` to disable it.
|
||||
|
||||
On discrete NVIDIA devices, allocation chunks can grow up to 4 GiB, bounded by the device's buffer and allocation
|
||||
limits, to reduce fragmentation within large graph workspaces. Other devices
|
||||
retain the 1 GiB default. `GGML_VK_SUBALLOCATION_BLOCK_SIZE` overrides this limit
|
||||
in bytes on any vendor; for example, `1073741824` selects 1 GiB and `4294967296`
|
||||
selects 4 GiB, subject to device limits. This is a soft chunk limit, not a reservation
|
||||
or a change to `--max-vram`: a single oversized tensor or exhausting the allocator's
|
||||
chunk count can require a larger allocation, which may fail.
|
||||
|
||||
The recommended shape for "biggest model my card can host" is:
|
||||
|
||||
```shell
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# RelayVSR image and video super-resolution
|
||||
|
||||
[RelayVSR](https://github.com/kopperx/RelayVSR) upscales images and videos by
|
||||
2x or 4x with `-M vid_upscale`.
|
||||
|
||||
## Weights
|
||||
|
||||
[ComfyUI safetensors weights](https://huggingface.co/Comfy-Org/RelayVSR) load
|
||||
directly. Select the matching pair for the requested scale:
|
||||
|
||||
| `--upscale-factor` | `--diffusion-model` | `--vae` |
|
||||
| --- | --- | --- |
|
||||
| 2 | `relayvsr_x2_lightx2v_int8_convrot.safetensors` | `relayvsr_flash_vae_x2_fp32.safetensors` |
|
||||
| 4 | `relayvsr_x4_int8_convrot.safetensors` | `relayvsr_flash_vae_x4_fp32.safetensors` |
|
||||
|
||||
No separate text encoder or ordinary Wan VAE is needed.
|
||||
|
||||
## Usage
|
||||
|
||||
For a video, extract numbered frames into a directory, then run:
|
||||
|
||||
```sh
|
||||
sd-cli -M vid_upscale \
|
||||
--diffusion-model models/diffusion_models/relayvsr_x2_lightx2v_int8_convrot.safetensors \
|
||||
--vae models/vae/relayvsr_flash_vae_x2_fp32.safetensors \
|
||||
--input-frames input_frames --upscale-factor 2 --fps 24 \
|
||||
--vsr-window-size 16 -s 42 -o output.avi
|
||||
```
|
||||
|
||||
Frames are read in filename order. Set `--fps` to the source video's frame rate.
|
||||
Video outputs support AVI, WebM, and animated WebP. To save individual frames,
|
||||
use an output pattern such as `-o output/%04d.png`.
|
||||
|
||||
For a single image, replace `--input-frames input_frames` with `-i input.png`
|
||||
and use `-o output.png` (or JPEG/WebP). No FPS setting is needed for an image.
|
||||
`-i` / `--init-img` and `--input-frames` are mutually exclusive.
|
||||
|
||||
## Settings and limits
|
||||
|
||||
- `--upscale-factor` is required. Use 2 or 4 with the corresponding weights.
|
||||
Output width and height are exactly the input dimensions times the scale.
|
||||
- `--vsr-window-size` sets the reference-frame window, including both endpoints.
|
||||
The default 16 selects reference frames 0, 15, 30, and so on, and always
|
||||
includes the final frame. Every input frame appears once in the output.
|
||||
- `--flash-attn auto` and `--conv-direct auto` are enabled by default. See
|
||||
[backend selection](backend.md) to choose a device or weight placement.
|
||||
- Sampler, scheduler, and step settings do not configure RelayVSR.
|
||||
`--vsr-temporal-chunk-size` is a SeedVR2 option.
|
||||
- Long videos require enough system memory to hold the input and output frames.
|
||||
Audio is not copied to the output.
|
||||
@@ -4,8 +4,9 @@
|
||||
the high-noise diffusion model when present. Python, PyTorch, and Triton are not
|
||||
required at build time or runtime.
|
||||
|
||||
The CUDA backend automatically selects a kernel supported by both the GPU and
|
||||
the compiled CUDA toolkit:
|
||||
The sd.cpp CUDA extension registers SageAttention through GGML's generic
|
||||
operator API. It selects a kernel supported by the GPU, compiled CUDA target,
|
||||
and toolkit:
|
||||
|
||||
| GPU / toolkit | Implementation |
|
||||
| --- | --- |
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# SeedVR2 image and video super-resolution
|
||||
|
||||
SeedVR2 upscales images and videos with `-M vid_upscale`. Support is experimental.
|
||||
|
||||
## Weights
|
||||
|
||||
These [ComfyUI safetensors weights](https://huggingface.co/Comfy-Org/SeedVR2)
|
||||
load directly, without conversion or a separate text encoder:
|
||||
|
||||
| Model | `--diffusion-model` | `--vae` |
|
||||
| --- | --- | --- |
|
||||
| 3B | `seedvr2_3b_int8_convrot.safetensors` | `seedvr2_ema_vae_fp16.safetensors` |
|
||||
| 7B Sharp | `seedvr2_7b_sharp_int8_convrot.safetensors` | `seedvr2_ema_vae_fp16.safetensors` |
|
||||
|
||||
Use the matching SeedVR2 VAE for either model. The 7B model requires more memory.
|
||||
|
||||
## Upscale an image
|
||||
|
||||
```sh
|
||||
sd-cli -M vid_upscale \
|
||||
--diffusion-model models/diffusion_models/seedvr2_3b_int8_convrot.safetensors \
|
||||
--vae models/vae/seedvr2_ema_vae_fp16.safetensors \
|
||||
-i input.png --upscale-factor 2 -s 42 -o restored.png
|
||||
```
|
||||
|
||||
`-i` / `--init-img` accepts a single image. Outputs can be PNG, JPEG, or WebP.
|
||||
No FPS or temporal chunk setting is needed for an image.
|
||||
|
||||
## Upscale a video
|
||||
|
||||
Extract the video into numbered frames, then pass the frame directory:
|
||||
|
||||
```sh
|
||||
mkdir frames
|
||||
ffmpeg -i input.mp4 -fps_mode passthrough frames/%06d.png
|
||||
sd-cli -M vid_upscale \
|
||||
--diffusion-model models/diffusion_models/seedvr2_3b_int8_convrot.safetensors \
|
||||
--vae models/vae/seedvr2_ema_vae_fp16.safetensors \
|
||||
--input-frames frames --upscale-factor 2 --fps 24 \
|
||||
--vsr-temporal-chunk-size 1 -s 42 -o restored.avi
|
||||
```
|
||||
|
||||
Set `--fps` to the source video's frame rate; 24 is only the example above.
|
||||
Frames are read in filename order. Use either `-i` or `--input-frames`, not both.
|
||||
|
||||
Video outputs support AVI, WebM, and animated WebP. To save individual frames,
|
||||
use an output pattern such as `-o restored/%06d.png`.
|
||||
|
||||
## Settings and limits
|
||||
|
||||
- `--upscale-factor` is required and must be a positive integer. Output width
|
||||
and height are exactly the input dimensions multiplied by this value.
|
||||
- `--vsr-temporal-chunk-size` controls how many video frames are processed
|
||||
together. Valid values are 1, 5, 9, 13, and so on; the default is 5. A value of
|
||||
1 processes each frame independently and uses less memory. Larger values use
|
||||
neighboring frames but require more memory. Chunk boundaries may be visible.
|
||||
- Frame count and order are preserved, including a shorter final chunk. Audio
|
||||
is not copied to the output.
|
||||
- If you run out of GPU memory, try `--vsr-temporal-chunk-size 1`. A five-frame
|
||||
chunk at 1728x960 output resolution exceeded 24 GB of GPU memory in testing.
|
||||
- `--flash-attn auto` and `--conv-direct auto` are enabled by default. See
|
||||
[backend selection](backend.md) to choose a device or weight placement.
|
||||
- SeedVR2 does not use sampler, scheduler, or step settings.
|
||||
`--vsr-window-size` is a RelayVSR option and does not affect SeedVR2.
|
||||
|
||||
3B has been tested on CUDA and CPU; 7B has been tested on CUDA with small inputs.
|
||||
Other backends have not been tested with this integration.
|
||||
|
||||
Model reference: [ByteDance-Seed/SeedVR](https://github.com/ByteDance-Seed/SeedVR).
|
||||
+87
-6
@@ -3,6 +3,7 @@
|
||||
#include <time.h>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <climits>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
@@ -33,10 +34,14 @@ const char* previews_str[] = {
|
||||
std::regex format_specifier_regex("(?:[^%]|^)(?:%%)*(%\\d{0,3}d)");
|
||||
|
||||
struct SDCliParams {
|
||||
SDMode mode = IMG_GEN;
|
||||
std::string output_path = "output.png";
|
||||
int output_begin_idx = -1;
|
||||
int compression_quality = 90;
|
||||
std::string input_frames;
|
||||
int video_upscale_factor = 0;
|
||||
int vsr_window_size = 16;
|
||||
int vsr_temporal_chunk_size = 5;
|
||||
SDMode mode = IMG_GEN;
|
||||
std::string output_path = "output.png";
|
||||
int output_begin_idx = -1;
|
||||
int compression_quality = 90;
|
||||
std::string image_path;
|
||||
std::string metadata_format = "text";
|
||||
|
||||
@@ -62,6 +67,7 @@ struct SDCliParams {
|
||||
ArgOptions options;
|
||||
|
||||
options.string_options = {
|
||||
{"", "--input-frames", "directory of ordered RGB frames for vid_upscale (use -i for a single image)", 0, &input_frames},
|
||||
{"-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",
|
||||
@@ -90,6 +96,9 @@ struct SDCliParams {
|
||||
};
|
||||
|
||||
options.int_options = {
|
||||
{"", "--upscale-factor", "required positive video upscale factor (RelayVSR: 2 or 4; SeedVR2: integer)", &video_upscale_factor},
|
||||
{"", "--vsr-window-size", "RelayVSR window including both endpoints (default: 16)", &vsr_window_size},
|
||||
{"", "--vsr-temporal-chunk-size", "SeedVR2 input frames per independent chunk: 1 or 4n+1 (default: 5)", &vsr_temporal_chunk_size},
|
||||
{"",
|
||||
"--preview-interval",
|
||||
"preview interval: in each sampling pass, positive N updates every Nth denoiser step and -N previews only completed logical step N; 0 previews the final completed step of the first pass (base-resolution or high-noise). Default: 1",
|
||||
@@ -190,7 +199,7 @@ struct SDCliParams {
|
||||
options.manual_options = {
|
||||
{"-M",
|
||||
"--mode",
|
||||
"run mode, one of [img_gen, adetailer, vid_gen, upscale, convert, metadata], default: img_gen",
|
||||
"run mode, one of [img_gen, adetailer, vid_gen, vid_upscale, upscale, convert, metadata], default: img_gen",
|
||||
on_mode_arg},
|
||||
{"",
|
||||
"--preview",
|
||||
@@ -220,6 +229,10 @@ struct SDCliParams {
|
||||
}
|
||||
|
||||
bool validate() {
|
||||
if (mode == VID_UPSCALE && video_upscale_factor <= 0) {
|
||||
LOG_ERROR("vid_upscale requires a positive --upscale-factor");
|
||||
return false;
|
||||
}
|
||||
if (mode != METADATA) {
|
||||
if (output_path.length() == 0) {
|
||||
LOG_ERROR("error: the following arguments are required: output_path");
|
||||
@@ -529,7 +542,9 @@ bool save_results(const SDCliParams& cli_params,
|
||||
return sucessful_reults != 0;
|
||||
}
|
||||
|
||||
if (cli_params.mode == VID_GEN && num_results > 1) {
|
||||
const bool video_upscale_output = cli_params.mode == VID_UPSCALE &&
|
||||
(gen_params.init_image_path.empty() || ext_lower == ".avi" || ext_lower == ".webm");
|
||||
if ((cli_params.mode == VID_GEN && num_results > 1) || video_upscale_output) {
|
||||
if (ext_lower != ".avi" && ext_lower != ".webp" && ext_lower != ".webm")
|
||||
ext = ".avi";
|
||||
std::string params = gen_params.embed_image_metadata
|
||||
@@ -664,6 +679,72 @@ int main(int argc, const char* argv[]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cli_params.mode == VID_UPSCALE) {
|
||||
if (cli_params.input_frames.empty() == gen_params.init_image_path.empty()) {
|
||||
LOG_ERROR("vid_upscale requires exactly one input: -i/--init-img or --input-frames");
|
||||
return 1;
|
||||
}
|
||||
ctx_params.prepare_backend_assignments();
|
||||
if (ctx_params.diffusion_model_path.empty() || ctx_params.vae_path.empty() || gen_params.fps <= 0) {
|
||||
LOG_ERROR("vid_upscale requires --diffusion-model, --vae and positive --fps");
|
||||
return 1;
|
||||
}
|
||||
std::vector<SDImageOwner> owned_frames;
|
||||
if (!gen_params.init_image_path.empty()) {
|
||||
owned_frames.emplace_back();
|
||||
if (!load_sd_image_from_file(owned_frames.back().put(), gen_params.init_image_path.c_str(), 0, 0, 3)) {
|
||||
LOG_ERROR("load image from '%s' failed", gen_params.init_image_path.c_str());
|
||||
return 1;
|
||||
}
|
||||
} else if (!load_images_from_dir(cli_params.input_frames, owned_frames) || owned_frames.empty()) {
|
||||
LOG_ERROR("vid_upscale input contains no readable frames");
|
||||
return 1;
|
||||
}
|
||||
if (owned_frames.size() > size_t(INT_MAX))
|
||||
return 1;
|
||||
std::vector<sd_image_t> frames;
|
||||
for (const auto& frame : owned_frames)
|
||||
frames.push_back(frame.get());
|
||||
sd_video_upscaler_ctx_params_t params;
|
||||
sd_video_upscaler_ctx_params_init(¶ms);
|
||||
params.diffusion_model_path = ctx_params.diffusion_model_path.c_str();
|
||||
params.vae_path = ctx_params.vae_path.c_str();
|
||||
params.upscale_factor = cli_params.video_upscale_factor;
|
||||
params.n_threads = ctx_params.n_threads;
|
||||
params.backend = ctx_params.effective_backend.c_str();
|
||||
params.params_backend = ctx_params.effective_params_backend.c_str();
|
||||
params.flash_attn_mode = ctx_params.flash_attn_mode.c_str();
|
||||
std::string conv_direct_mode = ctx_params.conv_direct_mode;
|
||||
if (conv_direct_mode.empty()) {
|
||||
conv_direct_mode = "auto";
|
||||
if (ctx_params.diffusion_conv_direct)
|
||||
conv_direct_mode += ",diffusion=on";
|
||||
if (ctx_params.vae_conv_direct)
|
||||
conv_direct_mode += ",vae=on";
|
||||
}
|
||||
params.conv_direct_mode = conv_direct_mode.c_str();
|
||||
std::unique_ptr<video_upscaler_ctx_t, decltype(&free_video_upscaler_ctx)> upscaler(
|
||||
new_video_upscaler_ctx(¶ms),
|
||||
free_video_upscaler_ctx);
|
||||
if (!upscaler)
|
||||
return 1;
|
||||
sd_video_generation_result_t* output = nullptr;
|
||||
sd_video_upscale_params_t upscale_params;
|
||||
sd_video_upscale_params_init(&upscale_params);
|
||||
upscale_params.frames = frames.data();
|
||||
upscale_params.frame_count = int(frames.size());
|
||||
upscale_params.fps = gen_params.fps;
|
||||
upscale_params.window_size = cli_params.vsr_window_size;
|
||||
upscale_params.temporal_chunk_size = cli_params.vsr_temporal_chunk_size;
|
||||
upscale_params.seed = gen_params.seed;
|
||||
if (!upscale_video(upscaler.get(), &upscale_params, &output))
|
||||
return 1;
|
||||
std::string video_info = output->generation_info;
|
||||
SDImageVec results;
|
||||
results.adopt_video(output);
|
||||
return save_results(cli_params, ctx_params, gen_params, results, int(results.size()), nullptr, video_info) ? 0 : 1;
|
||||
}
|
||||
|
||||
if (!gen_params.ad_model_path.empty() && cli_params.mode != IMG_GEN && cli_params.mode != ADETAILER) {
|
||||
LOG_ERROR("--ad-model is only supported in image generation and adetailer modes");
|
||||
return 1;
|
||||
|
||||
@@ -35,6 +35,7 @@ const char* const modes_str[] = {
|
||||
"convert",
|
||||
"upscale",
|
||||
"metadata",
|
||||
"vid_upscale",
|
||||
};
|
||||
|
||||
static sd_vae_format_t str_to_vae_format(const std::string& value) {
|
||||
@@ -464,7 +465,7 @@ ArgOptions SDContextParams::get_options() {
|
||||
&uncond_diffusion_model_path},
|
||||
{"",
|
||||
"--embeddings-connectors",
|
||||
"path to LTXAV embeddings connectors",
|
||||
"path to embeddings connectors (LTXAV or P-H3-Image)",
|
||||
0,
|
||||
&embeddings_connectors_path},
|
||||
{"",
|
||||
@@ -1135,7 +1136,7 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
&extra_ad_args},
|
||||
{"-i",
|
||||
"--init-img",
|
||||
"path to the init image",
|
||||
"path to the init image, or a single input image for vid_upscale",
|
||||
0,
|
||||
&init_image_path},
|
||||
{"",
|
||||
@@ -1187,6 +1188,13 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
"under --hires-upscalers-dir (default: Latent)",
|
||||
0,
|
||||
&hires_upscaler},
|
||||
{"",
|
||||
"--control-type",
|
||||
"kind of the control image, one of [openpose, depth, scribble, canny, normal, segment, tile, repaint]. "
|
||||
"Required by an SDXL Control Net Union model (tile and repaint only exist in its ProMax variant), ignored by any other Control Net. "
|
||||
"Also accepted: hed, softedge, pidi, ted (scribble); lineart, anime_lineart, mlsd (canny); seg (segment); inpaint, outpaint (repaint)",
|
||||
0,
|
||||
&control_type},
|
||||
{"",
|
||||
"--extra-sample-args",
|
||||
"extra sampler/scheduler/guidance args, key=value list. CFG supports guidance_schedule; APG supports apg_eta, apg_momentum, apg_norm_threshold, apg_norm_threshold_smoothing; SLG supports slg_uncond; lcm supports noise_clip_std, noise_scale_start, noise_scale_end; flux supports base_shift, max_shift; ltx2 supports max_shift, base_shift, stretch, terminal; euler_ge supports gamma; beta scheduler supports alpha, beta; logit_normal supports mu, std, logsnr_min, logsnr_max, resolution_aware; llada_image supports uniform; lms supports lms_max_order, lms_shift, lms_divisions; noise-injecting samplers support noise_sampler with value iid (default except for dpm++2m_sde_bt) or brownian_tree; brownian_tree_rng supports cpu (default), cuda, std_default or sampler_rng",
|
||||
@@ -1530,6 +1538,14 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
std::string sigmas_str = value;
|
||||
if (sigmas_str == "qwen-image-2.1-turbo") {
|
||||
sigmas_str = "1.0,0.978453,0.95418,0.926626,0.89508,0.845148,0.704534,0.414568,0.0";
|
||||
} else if (sigmas_str == "p-h3-image") {
|
||||
sigmas_str = "1.0,0.984753,0.967905,0.951688,0.925372,0.881923,0.810083,0.528718,0.0";
|
||||
} else if (sigmas_str == "hyperflow-h3-flash-4") {
|
||||
sigmas_str = "1.0,0.962575853,0.755781889,0.312497854,0.0";
|
||||
} else if (sigmas_str == "hyperflow-h3-flash-6") {
|
||||
sigmas_str = "1.0,0.982655942,0.914642572,0.674755692,0.385223746,0.159055337,0.0";
|
||||
} else if (sigmas_str == "hyperflow-h3-flash-8") {
|
||||
sigmas_str = "1.0,0.993909776,0.9842875,0.966063678,0.923076928,0.834942341,0.696852028,0.468753338,0.0";
|
||||
}
|
||||
if (!sigmas_str.empty() && sigmas_str.front() == '[') {
|
||||
sigmas_str.erase(0, 1);
|
||||
@@ -1777,11 +1793,11 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
on_scheduler_arg},
|
||||
{"",
|
||||
"--sigmas",
|
||||
"custom sigma values for the sampler, comma-separated (e.g., \"14.61,7.8,3.5,0.0\") or preset: qwen-image-2.1-turbo.",
|
||||
"custom sigma values for the sampler, comma-separated (e.g., \"14.61,7.8,3.5,0.0\") or preset: qwen-image-2.1-turbo, p-h3-image, hyperflow-h3-flash-4, hyperflow-h3-flash-6, hyperflow-h3-flash-8.",
|
||||
on_sigmas_arg},
|
||||
{"",
|
||||
"--hires-sigmas",
|
||||
"custom sigma values for the highres fix second pass, comma-separated (e.g., \"0.85,0.725,0.421875,0.0\") or preset: qwen-image-2.1-turbo.",
|
||||
"custom sigma values for the highres fix second pass, comma-separated (e.g., \"0.85,0.725,0.421875,0.0\") or preset: qwen-image-2.1-turbo, p-h3-image, hyperflow-h3-flash-4, hyperflow-h3-flash-6, hyperflow-h3-flash-8.",
|
||||
on_hires_sigmas_arg},
|
||||
{"",
|
||||
"--skip-layers",
|
||||
@@ -2247,6 +2263,7 @@ bool SDGenerationParams::from_json_str(
|
||||
|
||||
load_if_exists("strength", strength);
|
||||
load_if_exists("control_strength", control_strength);
|
||||
load_if_exists("control_type", control_type);
|
||||
load_if_exists("ip_adapter_strength", ip_adapter_strength);
|
||||
load_if_exists("moe_boundary", moe_boundary);
|
||||
load_if_exists("vace_strength", vace_strength);
|
||||
@@ -2636,6 +2653,15 @@ bool SDGenerationParams::resolve(const std::string& lora_model_dir, const std::s
|
||||
sample_params.sample_steps = std::clamp(sample_params.sample_steps, 1, 100);
|
||||
}
|
||||
|
||||
resolved_control_type = SD_CONTROL_TYPE_NONE;
|
||||
if (!control_type.empty()) {
|
||||
resolved_control_type = str_to_sd_control_type(control_type.c_str());
|
||||
if (resolved_control_type == SD_CONTROL_TYPE_COUNT) {
|
||||
LOG_ERROR("invalid control type '%s' (openpose, depth, scribble, canny, normal, segment, tile, repaint)", control_type.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
hires_upscaler_model_path.clear();
|
||||
if (hires_enabled) {
|
||||
if (hires_upscaler.empty()) {
|
||||
@@ -2875,6 +2901,7 @@ sd_img_gen_params_t SDGenerationParams::to_sd_img_gen_params_t() {
|
||||
params.qwen_image_layers = qwen_image_layers;
|
||||
params.control_image = control_image.get();
|
||||
params.control_strength = control_strength;
|
||||
params.control_type = resolved_control_type;
|
||||
params.ip_adapter_image = ip_adapter_image.get();
|
||||
params.ip_adapter_strength = ip_adapter_strength;
|
||||
params.pm_params = pm_params;
|
||||
@@ -3080,6 +3107,7 @@ std::string SDGenerationParams::to_string() const {
|
||||
<< " vace_strength: " << vace_strength << ",\n"
|
||||
<< " strength: " << strength << ",\n"
|
||||
<< " control_strength: " << control_strength << ",\n"
|
||||
<< " control_type: \"" << control_type << "\",\n"
|
||||
<< " seed: " << seed << ",\n"
|
||||
<< " upscale_repeats: " << upscale_repeats << ",\n"
|
||||
<< " upscale_tile_size: " << upscale_tile_size << ",\n"
|
||||
|
||||
@@ -22,7 +22,7 @@ std::string argv_to_utf8(int index, const char** argv);
|
||||
#define BOOL_STR(b) ((b) ? "true" : "false")
|
||||
|
||||
extern const char* const modes_str[];
|
||||
#define SD_ALL_MODES_STR "img_gen, adetailer, vid_gen, convert, upscale, metadata"
|
||||
#define SD_ALL_MODES_STR "img_gen, adetailer, vid_gen, convert, upscale, metadata, vid_upscale"
|
||||
|
||||
enum SDMode {
|
||||
IMG_GEN,
|
||||
@@ -31,6 +31,7 @@ enum SDMode {
|
||||
CONVERT,
|
||||
UPSCALE,
|
||||
METADATA,
|
||||
VID_UPSCALE,
|
||||
MODE_COUNT
|
||||
};
|
||||
|
||||
@@ -227,6 +228,7 @@ struct SDGenerationParams {
|
||||
std::string end_image_path;
|
||||
std::string mask_image_path;
|
||||
std::string control_image_path;
|
||||
std::string control_type;
|
||||
std::string ip_adapter_image_path;
|
||||
std::vector<std::string> ref_image_paths;
|
||||
std::vector<std::string> ref_video_paths;
|
||||
@@ -291,6 +293,7 @@ struct SDGenerationParams {
|
||||
std::string prompt_with_lora; // for metadata record only
|
||||
std::vector<sd_lora_t> lora_vec;
|
||||
sd_hires_upscaler_t resolved_hires_upscaler;
|
||||
sd_control_type_t resolved_control_type = SD_CONTROL_TYPE_NONE;
|
||||
|
||||
// Owned execution payload.
|
||||
SDImageOwner init_image;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "generation_metadata.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
|
||||
@@ -70,6 +71,7 @@ std::string generation_info_to_webui(const std::string& text) {
|
||||
const auto& sampling = info.at("sample_params");
|
||||
std::string result = info.value("prompt", "");
|
||||
if (info.contains("loras")) {
|
||||
bool prompt_trimmed = false;
|
||||
for (const auto& lora : info.at("loras")) {
|
||||
// WebUI tags cannot restrict a LoRA to the high-noise model.
|
||||
if (lora.value("is_high_noise", false))
|
||||
@@ -83,8 +85,15 @@ std::string generation_info_to_webui(const std::string& text) {
|
||||
name.erase(extension);
|
||||
if (name.empty() || name.find_first_of(":<>\r\n") != std::string::npos)
|
||||
continue;
|
||||
if (!result.empty())
|
||||
result += " ";
|
||||
if (!result.empty()) {
|
||||
if (!prompt_trimmed) {
|
||||
while (!result.empty() && std::isspace(static_cast<unsigned char>(result.back())))
|
||||
result.pop_back();
|
||||
prompt_trimmed = true;
|
||||
}
|
||||
if (!result.empty())
|
||||
result += " ";
|
||||
}
|
||||
result += "<lora:" + name + ":" + lora.at("multiplier").dump() + ">";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1499,7 +1499,7 @@ bool load_wav_from_memory(const uint8_t* data,
|
||||
const size_t remaining = size - chunk_data_pos; // safe: chunk_data_pos <= size
|
||||
// Chunk sizes come from the payload, so compare against the remaining bytes
|
||||
// instead of adding them to offsets (avoids size_t wrap on 32-bit builds).
|
||||
const bool data_fits = static_cast<size_t>(chunk_size) <= remaining;
|
||||
const bool data_fits = static_cast<size_t>(chunk_size) <= remaining;
|
||||
|
||||
if (std::memcmp(data + pos, "fmt ", 4) == 0) {
|
||||
if (chunk_size < 16 || !data_fits) {
|
||||
|
||||
@@ -76,6 +76,16 @@ Recipes declaring `video_frames` also switch the form to Video Generation;
|
||||
other recipes switch to Image Generation. Generation is enabled after the
|
||||
selected recipe is loaded. Changing a selection does not reload the model
|
||||
until you click Load.
|
||||
|
||||
The generation status bar shows the current stage and elapsed time even when
|
||||
Preview Mode is `none`. During sampling it shows completed steps for the current
|
||||
pass; prompt encoding and VAE decoding show an activity indicator when their
|
||||
total work is unknown. Sampling reaching 100% does not mean the job is finished:
|
||||
decoding, ADetailer, and output encoding may still follow.
|
||||
The canvas keeps the submitted output aspect ratio, and stage changes keep the
|
||||
same layout. Editing the next request's resolution does not resize the current
|
||||
preview or result.
|
||||
|
||||
The server can also start with the existing model arguments shown below;
|
||||
existing API clients then work without selecting a recipe. The UI automatically
|
||||
selects **Command-line model (all features)** and uses the startup generation
|
||||
@@ -89,8 +99,8 @@ FLUX 1/2/3, Chroma, Qwen Image and editing variants; Z-Image, Anima, ERNIE,
|
||||
LENS, PiD and other supported image families; AnimateDiff, Wan, LTX,
|
||||
HunyuanVideo, LingBot Video and MiniMax-H3. Model-specific text encoder
|
||||
labels identify the expected encoder family and size. Loading a video model enables **Video Generation**; I2V also requires a
|
||||
start image. The Models panel can be collapsed and remembers its open state.
|
||||
Its header shows elapsed loading time from the status API and keeps the final
|
||||
start image. The model button below the prompt opens model and recipe selection.
|
||||
Its status summary shows elapsed loading time from the status API and keeps the final
|
||||
time after success or failure, including after a page refresh.
|
||||
Built-ins are embedded in the
|
||||
binary from [recipes/](recipes/), with one JSON file per recipe. Adding or
|
||||
|
||||
+120
-5
@@ -85,7 +85,7 @@ available during loading. Model lists are empty when no model is loaded;
|
||||
| GET | `/sdcpp/v1/model-files` | `{files: [{id, name, category, root, relative_path}], errors: [...]}` |
|
||||
| POST | `/sdcpp/v1/models/refresh` | Rescan recipes and files; return `{errors: [...]}` |
|
||||
| GET | `/sdcpp/v1/models/status` | Current state, recipe, selection, error, loading time and optional revision |
|
||||
| POST | `/sdcpp/v1/models/load` | Load the submitted recipe and selection; return status after completion |
|
||||
| POST | `/sdcpp/v1/models/load` | Load the submitted recipe and selection; return status after completion. A load is a no-op returning the current status with `already_loaded: true` only when the state is ready and the fully resolved selection, the complete recipe object, and the resolved component paths all match the active model; a catalog refresh with modified recipe metadata or a retargeted model path therefore reloads even with an unchanged recipe ID and selection |
|
||||
| POST | `/sdcpp/v1/models/unload` | Release the active context; return empty status |
|
||||
|
||||
Load request:
|
||||
@@ -244,6 +244,7 @@ the same one-recipe-per-file schema:
|
||||
"id": "my-flux",
|
||||
"name": "My FLUX",
|
||||
"description": "FLUX.1 dev",
|
||||
"size_multiple": 16,
|
||||
"model_url": "https://huggingface.co/black-forest-labs/FLUX.1-dev",
|
||||
"slots": [
|
||||
{"id": "diffusion_model", "required": true},
|
||||
@@ -259,7 +260,8 @@ the same one-recipe-per-file schema:
|
||||
|
||||
IDs contain 1–128 ASCII letters, digits, dots, underscores or hyphens.
|
||||
`name`, `schema_version: 1` and `slots` are required; `description` and
|
||||
`model_url`, `required_any`, `defaults`, `context`, `features` and `disabled_features` are optional. Each slot requires only `id` and boolean `required`.
|
||||
`model_url`, `required_any`, `defaults`, `context`, `features`, `disabled_features`
|
||||
and `size_multiple`, `video_constraints` are optional. Each slot requires only `id` and boolean `required`.
|
||||
The server supplies the default label and search directories for each slot.
|
||||
An optional nonempty `label` overrides the display name, for example
|
||||
`{"id": "diffusion_model", "label": "Low-noise diffusion model", "required": true}`.
|
||||
@@ -267,6 +269,53 @@ Legacy `category` fields remain accepted, but must match one of the slot's
|
||||
supported directories; they cannot redefine or restrict its search directories.
|
||||
At least one required `model` or `diffusion_model` slot is necessary.
|
||||
|
||||
An optional top-level `models` object maps slot IDs to file IDs, providing
|
||||
recipe-local defaults for component selection. A load request may omit
|
||||
`models` entirely and load with the recipe's defaults. Selections sent in the
|
||||
request always override recipe defaults, slot by slot. An empty string
|
||||
disables a slot's recipe default (omitting the slot selects it); the empty
|
||||
key is then normalized out of the reported selection, so for slots without a
|
||||
recipe default an empty selection loads identically to an omitted one.
|
||||
|
||||
`size_multiple` is a positive integer from 1 to 4096 that declares the width and
|
||||
height increment used by the web UI. It defaults to `8` when omitted; the UI also
|
||||
uses `8` before a recipe is selected. For example, `"size_multiple": 16` makes
|
||||
generated size presets multiples of 16. Exact dimensions that do not match the
|
||||
increment require correction before submission. This is recipe metadata, not a
|
||||
generation parameter, and does not change the core model's alignment behavior.
|
||||
|
||||
`video_constraints` is optional metadata for the web UI's Frames / Duration
|
||||
controls. It does not override the runtime's model-specific rules. For example:
|
||||
|
||||
```json
|
||||
"video_constraints": {
|
||||
"frame_step": 4,
|
||||
"frame_offset": 1,
|
||||
"frame_rounding": "floor",
|
||||
"min_frames": 1
|
||||
}
|
||||
```
|
||||
|
||||
Legal frame counts are `frame_step * n + frame_offset`, with integer `n >= 0`,
|
||||
within the inclusive `min_frames` / `max_frames` range. All fields are optional:
|
||||
|
||||
| Field | Default | Accepted values |
|
||||
| --- | --- | --- |
|
||||
| `frame_step` | `1` | Integer 1–4096 |
|
||||
| `frame_offset` | `0` | Integer 0 to `frame_step - 1` |
|
||||
| `frame_rounding` | `"floor"` | `"floor"` or `"ceil"` |
|
||||
| `min_frames` | `1` | Integer 1–4096 |
|
||||
| `max_frames` | `4096` | Integer 1–4096, at least `min_frames` |
|
||||
| `fixed_fps` | Unset | Integer 1–240; locks the FPS control |
|
||||
|
||||
The range must contain at least one legal frame count. Unknown keys are rejected.
|
||||
Omitting the object adds no frame alignment in the UI. `defaults.fps` only sets
|
||||
the initial FPS; it does not lock it. The UI multiplies requested duration by FPS,
|
||||
aligns using the declared rounding direction, then clamps to the legal range.
|
||||
Duration stays in the UI; generation requests still send only `video_frames` and
|
||||
`fps`. Switching input modes preserves the aligned length. Changing FPS keeps
|
||||
frames in Frames mode, and keeps the requested seconds in Duration mode.
|
||||
|
||||
`model_url` is an optional HTTPS Hugging Face model repository link, shown beside
|
||||
the recipe description. Built-ins link to the model's author/publisher, not repacks.
|
||||
`required_any` contains groups of declared slot IDs. At least one model in each
|
||||
@@ -726,6 +775,7 @@ Field types:
|
||||
| `queue_position` | `integer` |
|
||||
| `result` | `object \| null` |
|
||||
| `error` | `object \| null` |
|
||||
| `progress` | `object` |
|
||||
| `preview` | `object \| null` |
|
||||
|
||||
`created`, `started`, and `completed` are integer Unix timestamps in
|
||||
@@ -733,6 +783,57 @@ Field types:
|
||||
the elapsed generation time in seconds, including output encoding and excluding
|
||||
queue time.
|
||||
|
||||
`progress` is available independently of the preview setting, for both image
|
||||
and video jobs. Continue polling until `status` is terminal: a sampling pass
|
||||
can finish before decoding, ADetailer, or output encoding finishes.
|
||||
|
||||
| Field | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `progress.stage` | `string` | Current execution stage; see below |
|
||||
| `progress.detail` | `string` | Stage detail, or an empty string |
|
||||
| `progress.pass` | `integer` | Sampling pass number within the job; zero before sampling |
|
||||
| `progress.completed` | `integer \| null` | Completed units in the current stage/pass |
|
||||
| `progress.total` | `integer \| null` | Actual total units, or null when unknown |
|
||||
| `progress.unit` | `string \| null` | `steps` for sampling, `images` for image output encoding; null when uncounted |
|
||||
| `progress.stage_elapsed_ms` | `integer` | Elapsed time in the current stage/detail/pass |
|
||||
| `progress.elapsed_ms` | `integer` | Job execution time, excluding queue time; zero before execution |
|
||||
|
||||
Stages include `queued`, `waiting` (waiting for the execution lock), `preparing`,
|
||||
`conditioning`, `sampling`, `vae_encode`, `vae_decode`, `audio_decode`,
|
||||
`upscaling`, `adetailer`, and `encoding`. Stages may repeat or be skipped.
|
||||
Details include `positive_prompt` / `negative_prompt` / `image_condition` for
|
||||
conditioning, `high_noise` / `low_noise` for two-stage sampling, and `image` /
|
||||
`video` for output encoding. During ADetailer, `detail` identifies the inner
|
||||
stage, such as `conditioning` or `sampling`. Clients should tolerate unknown
|
||||
stage, detail, and unit values.
|
||||
|
||||
Sampling counts completed logical steps using the actual schedule, including
|
||||
strength adjustments. Each new sampling pass increments `pass` and starts at
|
||||
zero, including batch items, highres passes, high/low-noise passes, and ADetailer
|
||||
passes. The ratio is **current-pass progress**, not overall job completion.
|
||||
The progress snapshot is published atomically. Preview metadata still describes
|
||||
the preview frame, which may be older than the current progress.
|
||||
|
||||
Unknown totals are represented by null counts, not an estimated percentage.
|
||||
For example, prompt encoding can report:
|
||||
|
||||
```json
|
||||
"progress": {
|
||||
"stage": "conditioning",
|
||||
"detail": "positive_prompt",
|
||||
"pass": 0,
|
||||
"completed": null,
|
||||
"total": null,
|
||||
"unit": null,
|
||||
"stage_elapsed_ms": 3200,
|
||||
"elapsed_ms": 4100
|
||||
}
|
||||
```
|
||||
|
||||
Elapsed times freeze when a job completes, fails, or is cancelled; the last
|
||||
stage is retained for inspection. Use `status` to determine the final outcome.
|
||||
Elapsed time alone does not indicate that computation is advancing.
|
||||
|
||||
`preview` sub-fields:
|
||||
|
||||
| Field | Type | Notes |
|
||||
@@ -887,6 +988,7 @@ LTX and Wan preserve causal state between temporal tiles. Hunyuan Video and TAEH
|
||||
| `ref_image_args` | `string` |
|
||||
| `increase_ref_index` | `boolean` |
|
||||
| `control_strength` | `number` |
|
||||
| `control_type` | `string` |
|
||||
| `ip_adapter_strength` | `number` |
|
||||
| `hires` | `object` |
|
||||
| `hires.enabled` | `boolean` |
|
||||
@@ -1067,6 +1169,7 @@ Example:
|
||||
"ref_image_args": "",
|
||||
"increase_ref_index": false,
|
||||
"control_strength": 0.9,
|
||||
"control_type": "",
|
||||
"ip_adapter_strength": 1.0,
|
||||
"embed_image_metadata": true,
|
||||
|
||||
@@ -1192,6 +1295,7 @@ Top-level scalar fields:
|
||||
| `image_preprocess` | `string \| array<string>` |
|
||||
| `increase_ref_index` | `boolean` |
|
||||
| `control_strength` | `number` |
|
||||
| `control_type` | `string` |
|
||||
| `ip_adapter_strength` | `number` |
|
||||
| `embed_image_metadata` | `boolean` |
|
||||
| `preview` | `string` |
|
||||
@@ -1693,15 +1797,26 @@ When omitted, backend defaults apply to these fields:
|
||||
|
||||
### Frame Count Semantics
|
||||
|
||||
`video_frames` is the requested target length, but the current core video path internally normalizes the effective frame count to the largest `4n + 1` value that does not exceed the requested count.
|
||||
`video_frames` is the requested target length. The core normalizes it according
|
||||
to the loaded model:
|
||||
|
||||
Examples:
|
||||
| Model family | Frame alignment | Fixed FPS |
|
||||
| --- | --- | --- |
|
||||
| Wan, HunyuanVideo, LingBot | `4n + 1`, round down | Wan 2.2 S2V: 16 |
|
||||
| LTX | `8n + 1`, round down | None |
|
||||
| MiniMax-H3 | `17n + 5`, round up, minimum 5 | 24 |
|
||||
| AnimateDiff | Whole frames, maximum 32 | None |
|
||||
|
||||
Wan examples:
|
||||
|
||||
- `video_frames = 33` stays `33`
|
||||
- `video_frames = 34` becomes `33`
|
||||
- `video_frames = 32` becomes `29`
|
||||
|
||||
The completed job payload includes the actual decoded `frame_count`.
|
||||
At 16 FPS, a 5-second target requests 80 frames and aligns to 77 frames for Wan,
|
||||
or approximately 4.813 seconds. Audio-driven generation and temporal upscaling
|
||||
can change the final length. Use the completed job's actual decoded `frame_count`
|
||||
and `fps` for the final playback duration.
|
||||
|
||||
### Completion Result
|
||||
|
||||
|
||||
@@ -92,6 +92,63 @@ static void set_preview_callback(AsyncGenerationJob& job) {
|
||||
sd_set_preview_callback(preview_callback, mode, interval, true, false, &job);
|
||||
}
|
||||
|
||||
static void update_job_progress(AsyncGenerationJob& job, const char* stage, const char* detail = "", int sample_pass = -1, int64_t completed = 0, int64_t total = 0, const char* unit = "") {
|
||||
std::lock_guard<std::mutex> lock(job.progress_mutex);
|
||||
auto& progress = job.progress;
|
||||
if (sample_pass < 0) {
|
||||
sample_pass = progress.sample_pass;
|
||||
}
|
||||
if (progress.stage != stage || progress.detail != detail || progress.sample_pass != sample_pass) {
|
||||
progress.stage_started = std::chrono::steady_clock::now();
|
||||
}
|
||||
progress.stage = stage;
|
||||
progress.detail = detail;
|
||||
progress.sample_pass = sample_pass;
|
||||
progress.completed = completed;
|
||||
progress.total = total;
|
||||
progress.unit = unit;
|
||||
}
|
||||
|
||||
static void finish_job_progress(AsyncGenerationJob& job) {
|
||||
std::lock_guard<std::mutex> lock(job.progress_mutex);
|
||||
job.progress.finished = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
class JobCallbacksGuard {
|
||||
sd_ctx_t* ctx_;
|
||||
AsyncGenerationJob& job_;
|
||||
bool detailing_ = false;
|
||||
|
||||
static void progress_callback(const sd_generation_progress_t* progress, void* data) {
|
||||
auto& guard = *static_cast<JobCallbacksGuard*>(data);
|
||||
update_job_progress(guard.job_, guard.detailing_ ? "adetailer" : progress->stage,
|
||||
guard.detailing_ ? progress->stage : progress->detail,
|
||||
progress->sample_pass, progress->completed, progress->total, progress->unit);
|
||||
}
|
||||
|
||||
public:
|
||||
JobCallbacksGuard(sd_ctx_t* ctx, AsyncGenerationJob& job)
|
||||
: ctx_(ctx), job_(job) {
|
||||
clear_preview_callback();
|
||||
set_preview_callback(job);
|
||||
sd_set_generation_progress_callback(ctx_, progress_callback, this);
|
||||
}
|
||||
|
||||
~JobCallbacksGuard() {
|
||||
sd_set_generation_progress_callback(ctx_, nullptr, nullptr);
|
||||
clear_preview_callback();
|
||||
}
|
||||
|
||||
void begin_detailing() {
|
||||
clear_preview_callback();
|
||||
detailing_ = true;
|
||||
update_job_progress(job_, "adetailer");
|
||||
}
|
||||
|
||||
JobCallbacksGuard(const JobCallbacksGuard&) = delete;
|
||||
JobCallbacksGuard& operator=(const JobCallbacksGuard&) = delete;
|
||||
};
|
||||
|
||||
const char* async_job_kind_name(AsyncJobKind kind) {
|
||||
switch (kind) {
|
||||
case AsyncJobKind::ImgGen:
|
||||
@@ -186,6 +243,7 @@ bool cancel_queued_job(AsyncJobManager& manager, AsyncGenerationJob& job) {
|
||||
job.result_fps = 0;
|
||||
job.error_code = "cancelled";
|
||||
job.error_message = "job cancelled by client";
|
||||
finish_job_progress(job);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -200,6 +258,24 @@ json make_async_job_json(const AsyncJobManager& manager, const AsyncGenerationJo
|
||||
result["completed"] = job.completed_at == 0 ? json(nullptr) : json(job.completed_at);
|
||||
result["queue_position"] = 0;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(job.progress_mutex);
|
||||
const auto& progress = job.progress;
|
||||
const auto end = progress.finished == std::chrono::steady_clock::time_point{}
|
||||
? std::chrono::steady_clock::now()
|
||||
: progress.finished;
|
||||
result["progress"] = {
|
||||
{"stage", progress.stage},
|
||||
{"detail", progress.detail},
|
||||
{"pass", progress.sample_pass},
|
||||
{"completed", progress.total > 0 ? json(progress.completed) : json(nullptr)},
|
||||
{"total", progress.total > 0 ? json(progress.total) : json(nullptr)},
|
||||
{"unit", progress.unit.empty() ? json(nullptr) : json(progress.unit)},
|
||||
{"stage_elapsed_ms", std::chrono::duration_cast<std::chrono::milliseconds>(end - progress.stage_started).count()},
|
||||
{"elapsed_ms", job.started_at == 0 ? 0 : std::max<int64_t>(0, (job.completed_at == 0 ? unix_timestamp_now_ms() : job.completed_at) - job.started_at)},
|
||||
};
|
||||
}
|
||||
|
||||
if (job.status == AsyncJobStatus::Queued) {
|
||||
size_t position = 1;
|
||||
for (const auto& queued_id : manager.queue) {
|
||||
@@ -278,17 +354,19 @@ bool execute_img_gen_job(ServerRuntime& runtime,
|
||||
return false;
|
||||
}
|
||||
JobCancellationMonitor cancellation(runtime.sd_ctx, job);
|
||||
set_preview_callback(job);
|
||||
JobCallbacksGuard callbacks(runtime.sd_ctx, job);
|
||||
sd_image_generation_result_t* generated = nullptr;
|
||||
generate_image(runtime.sd_ctx, ¶ms, &generated);
|
||||
results.adopt(generated, runtime.ctx_params->lora_model_dir);
|
||||
clear_preview_callback();
|
||||
if (job.cancel_requested.load()) {
|
||||
return false;
|
||||
}
|
||||
if (!results.empty() && !apply_server_adetailer(runtime, job.img_gen, params, results)) {
|
||||
error_message = "ADetailer failed";
|
||||
return false;
|
||||
if (!results.empty() && job.img_gen.adetailer_enabled && !runtime.default_gen_params->ad_model_path.empty()) {
|
||||
callbacks.begin_detailing();
|
||||
if (!apply_server_adetailer(runtime, job.img_gen, params, results)) {
|
||||
error_message = "ADetailer failed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,6 +383,7 @@ bool execute_img_gen_job(ServerRuntime& runtime,
|
||||
encoded_format = EncodedImageFormat::WEBP;
|
||||
}
|
||||
|
||||
update_job_progress(job, "encoding", "image", -1, 0, num_results, "images");
|
||||
for (int i = 0; i < num_results; ++i) {
|
||||
if (results[i].data == nullptr) {
|
||||
continue;
|
||||
@@ -325,6 +404,7 @@ bool execute_img_gen_job(ServerRuntime& runtime,
|
||||
}
|
||||
output_images.push_back(base64_encode(image_bytes));
|
||||
output_info.push_back(json::parse(results.generation_info(i)));
|
||||
update_job_progress(job, "encoding", "image", -1, i + 1, num_results, "images");
|
||||
}
|
||||
|
||||
if (output_images.empty()) {
|
||||
@@ -356,7 +436,7 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
return false;
|
||||
}
|
||||
JobCancellationMonitor cancellation(runtime.sd_ctx, job);
|
||||
set_preview_callback(job);
|
||||
JobCallbacksGuard callbacks(runtime.sd_ctx, job);
|
||||
sd_video_generation_result_t* generated = nullptr;
|
||||
if (generate_video(runtime.sd_ctx, ¶ms, &generated)) {
|
||||
output_fps = generated->fps;
|
||||
@@ -367,7 +447,6 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
if (job.vid_gen.gen_params.embed_image_metadata)
|
||||
str_params = results.generation_info(0);
|
||||
}
|
||||
clear_preview_callback();
|
||||
}
|
||||
|
||||
num_results = results.count();
|
||||
@@ -377,6 +456,7 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
return false;
|
||||
}
|
||||
|
||||
update_job_progress(job, "encoding", "video");
|
||||
std::vector<uint8_t> video_bytes = create_video_from_sd_images_to_vector(job.vid_gen.output_format,
|
||||
results.data(),
|
||||
num_results,
|
||||
@@ -425,6 +505,7 @@ void async_job_worker(ServerRuntime& runtime) {
|
||||
job = it->second;
|
||||
job->status = AsyncJobStatus::Generating;
|
||||
job->started_at = unix_timestamp_now_ms();
|
||||
update_job_progress(*job, "waiting");
|
||||
}
|
||||
|
||||
std::vector<std::string> output_images;
|
||||
@@ -459,6 +540,7 @@ void async_job_worker(ServerRuntime& runtime) {
|
||||
}
|
||||
|
||||
job->completed_at = unix_timestamp_now_ms();
|
||||
finish_job_progress(*job);
|
||||
if (job->cancel_requested.load()) {
|
||||
job->status = AsyncJobStatus::Cancelled;
|
||||
job->error_code = "cancelled";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
@@ -29,6 +30,17 @@ enum class AsyncJobStatus {
|
||||
const char* async_job_kind_name(AsyncJobKind kind);
|
||||
const char* async_job_status_name(AsyncJobStatus status);
|
||||
|
||||
struct AsyncJobProgress {
|
||||
std::string stage = "queued";
|
||||
std::string detail;
|
||||
std::string unit;
|
||||
int sample_pass = 0;
|
||||
int64_t completed = 0;
|
||||
int64_t total = 0;
|
||||
std::chrono::steady_clock::time_point stage_started = std::chrono::steady_clock::now();
|
||||
std::chrono::steady_clock::time_point finished{};
|
||||
};
|
||||
|
||||
struct AsyncGenerationJob {
|
||||
std::string id;
|
||||
AsyncJobKind kind = AsyncJobKind::ImgGen;
|
||||
@@ -47,6 +59,8 @@ struct AsyncGenerationJob {
|
||||
int result_fps = 0;
|
||||
std::string error_code;
|
||||
std::string error_message;
|
||||
mutable std::mutex progress_mutex;
|
||||
AsyncJobProgress progress;
|
||||
mutable std::mutex preview_mutex;
|
||||
std::string preview_b64;
|
||||
int preview_pass = 0;
|
||||
|
||||
+1
-1
Submodule examples/server/frontend updated: 9157320a3b...b07665f893.
@@ -66,6 +66,14 @@ static void clear_model_paths(SDContextParams& params) {
|
||||
}
|
||||
}
|
||||
|
||||
bool component_paths_match(const SDContextParams& a, const SDContextParams& b) {
|
||||
for (const auto& slot : k_model_slots) {
|
||||
if ((a.*slot.field) != (b.*slot.field))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string model_path_key(const fs::path& path) {
|
||||
#ifdef _WIN32
|
||||
const auto value = path.generic_wstring();
|
||||
@@ -369,20 +377,44 @@ json model_generation_features(const ServerRuntime& runtime) {
|
||||
if (runtime.models->active_recipe.value("source", "") == "command_line")
|
||||
return features;
|
||||
// Optional components only become usable once selected and loaded.
|
||||
features["ip_adapter"] = features.value("ip_adapter", false) && !runtime.ctx_params->ip_adapter_path.empty();
|
||||
features["controlnet"] = features.value("controlnet", false) && !runtime.ctx_params->control_net_path.empty();
|
||||
features["high_noise"] = features.value("high_noise", false) && !runtime.ctx_params->high_noise_diffusion_model_path.empty();
|
||||
features["preview_tae"] = features.value("preview_tae", false) && !runtime.ctx_params->taesd_path.empty();
|
||||
features["ref_audios"] = features.value("ref_audios", false) &&
|
||||
(!runtime.ctx_params->audio_vae_path.empty() || !runtime.ctx_params->audio_encoder_path.empty());
|
||||
features["ip_adapter"] = features.value("ip_adapter", false) && !runtime.ctx_params->ip_adapter_path.empty();
|
||||
features["controlnet"] = features.value("controlnet", false) && !runtime.ctx_params->control_net_path.empty();
|
||||
features["high_noise"] = features.value("high_noise", false) && !runtime.ctx_params->high_noise_diffusion_model_path.empty();
|
||||
features["preview_tae"] = features.value("preview_tae", false) && !runtime.ctx_params->taesd_path.empty();
|
||||
features["ref_audios"] = features.value("ref_audios", false) &&
|
||||
(!runtime.ctx_params->audio_vae_path.empty() || !runtime.ctx_params->audio_encoder_path.empty());
|
||||
features["ref_video_audios"] = features.value("ref_video_audios", false) && !runtime.ctx_params->audio_vae_path.empty();
|
||||
if (!runtime.ctx_params->pulid_weights_path.empty())
|
||||
features["slg"] = false;
|
||||
return features;
|
||||
}
|
||||
|
||||
static void validate_video_constraints(const json& constraints) {
|
||||
check_keys(constraints, {"frame_step", "frame_offset", "frame_rounding", "min_frames", "max_frames", "fixed_fps"});
|
||||
for (const auto* key : {"frame_step", "frame_offset", "min_frames", "max_frames", "fixed_fps"}) {
|
||||
const int minimum = std::string(key) == "frame_offset" ? 0 : 1;
|
||||
const int maximum = std::string(key) == "fixed_fps" ? 240 : 4096;
|
||||
if (constraints.contains(key) &&
|
||||
(!constraints[key].is_number_integer() || constraints[key] < minimum || constraints[key] > maximum)) {
|
||||
throw std::runtime_error(std::string("video_constraints.") + key + " is outside its integer range");
|
||||
}
|
||||
}
|
||||
const int step = constraints.value("frame_step", 1);
|
||||
const int offset = constraints.value("frame_offset", 0);
|
||||
const int minimum = constraints.value("min_frames", 1);
|
||||
const int maximum = constraints.value("max_frames", 4096);
|
||||
if (offset >= step || minimum > maximum ||
|
||||
offset + std::max(0, (minimum - offset + step - 1) / step) * step > maximum) {
|
||||
throw std::runtime_error("video_constraints must describe a nonempty frame range with frame_offset < frame_step");
|
||||
}
|
||||
if (constraints.contains("frame_rounding") &&
|
||||
constraints["frame_rounding"] != "floor" && constraints["frame_rounding"] != "ceil") {
|
||||
throw std::runtime_error("video_constraints.frame_rounding must be floor or ceil");
|
||||
}
|
||||
}
|
||||
|
||||
static void validate_recipe(const json& recipe) {
|
||||
check_keys(recipe, {"schema_version", "id", "name", "description", "model_url", "slots", "required_any", "defaults", "context", "features", "disabled_features"});
|
||||
check_keys(recipe, {"schema_version", "id", "name", "description", "model_url", "slots", "required_any", "models", "defaults", "context", "features", "disabled_features", "size_multiple", "video_constraints"});
|
||||
if (!recipe.at("schema_version").is_number_integer() || recipe.at("schema_version") != 1 ||
|
||||
!valid_identifier(recipe.at("id").get<std::string>()) || recipe.at("name").get<std::string>().empty()) {
|
||||
throw std::runtime_error("invalid schema_version, id or name");
|
||||
@@ -390,6 +422,13 @@ static void validate_recipe(const json& recipe) {
|
||||
if (recipe.contains("description") && !recipe["description"].is_string()) {
|
||||
throw std::runtime_error("description must be a string");
|
||||
}
|
||||
if (recipe.contains("size_multiple") &&
|
||||
(!recipe["size_multiple"].is_number_integer() || recipe["size_multiple"] < 1 || recipe["size_multiple"] > 4096)) {
|
||||
throw std::runtime_error("size_multiple must be an integer from 1 to 4096");
|
||||
}
|
||||
if (recipe.contains("video_constraints")) {
|
||||
validate_video_constraints(recipe["video_constraints"]);
|
||||
}
|
||||
if (recipe.contains("model_url")) {
|
||||
const auto url = recipe.at("model_url").get<std::string>();
|
||||
const std::string prefix = "https://huggingface.co/";
|
||||
@@ -418,6 +457,19 @@ static void validate_recipe(const json& recipe) {
|
||||
if (!has_required_model) {
|
||||
throw std::runtime_error("a required model or diffusion_model slot is needed");
|
||||
}
|
||||
// Optional recipe-local model defaults: slot ID to file ID mappings that
|
||||
// prefill slots a load request does not select itself.
|
||||
if (recipe.contains("models")) {
|
||||
const auto& recipe_models = recipe.at("models");
|
||||
if (!recipe_models.is_object() || recipe_models.size() > 32)
|
||||
throw std::runtime_error("models must be an object of at most 32 slot entries");
|
||||
std::set<std::string> default_ids;
|
||||
for (auto it = recipe_models.begin(); it != recipe_models.end(); ++it) {
|
||||
if (!ids.count(it.key() == "taesd" ? "tae" : it.key()) || !it.value().is_string() || it.value().get<std::string>().empty() ||
|
||||
!default_ids.insert(it.key() == "taesd" ? "tae" : it.key()).second)
|
||||
throw std::runtime_error("unknown or invalid default model: " + it.key());
|
||||
}
|
||||
}
|
||||
if (recipe.contains("required_any")) {
|
||||
const auto& groups = recipe.at("required_any");
|
||||
if (!groups.is_array() || groups.size() > 32)
|
||||
@@ -442,6 +494,7 @@ static void validate_recipe(const json& recipe) {
|
||||
|
||||
static json normalize_recipe(json recipe) {
|
||||
validate_recipe(recipe);
|
||||
recipe["size_multiple"] = recipe.value("size_multiple", 8);
|
||||
recipe["effective_features"] = resolve_recipe_features(recipe);
|
||||
for (auto& slot : recipe.at("slots")) {
|
||||
const auto& spec = *find_model_slot(slot.at("id").get<std::string>());
|
||||
@@ -605,6 +658,7 @@ void initialize_server_models(ServerRuntime& runtime) {
|
||||
{"description", "Uses the loaded command-line model and its generation defaults."},
|
||||
{"slots", json::array()},
|
||||
{"defaults", json::object()},
|
||||
{"size_multiple", 8},
|
||||
{"features", features},
|
||||
};
|
||||
models.active_recipe["effective_features"] = resolve_recipe_features(models.active_recipe);
|
||||
@@ -613,10 +667,11 @@ void initialize_server_models(ServerRuntime& runtime) {
|
||||
refresh_model_catalog(runtime);
|
||||
}
|
||||
|
||||
void prepare_model_load(ServerRuntime& runtime, const json& body, SDContextParams& context, SDGenerationParams& defaults, json* loaded_recipe) {
|
||||
void prepare_model_load(ServerRuntime& runtime, json& body, SDContextParams& context, SDGenerationParams& defaults, json* loaded_recipe) {
|
||||
check_keys(body, {"recipe", "models"});
|
||||
const auto recipe_id = body.at("recipe").get<std::string>();
|
||||
auto selection = body.at("models");
|
||||
// `models` is optional; unselected slots may fall back to recipe defaults below.
|
||||
auto selection = body.value("models", json::object());
|
||||
if (!selection.is_object())
|
||||
throw std::runtime_error("models must be an object");
|
||||
auto& models = *runtime.models;
|
||||
@@ -631,6 +686,20 @@ void prepare_model_load(ServerRuntime& runtime, const json& body, SDContextParam
|
||||
defaults = models.base_defaults;
|
||||
apply_recipe_context(recipe.value("context", json::object()), context);
|
||||
apply_recipe_defaults(recipe.value("defaults", json::object()), defaults);
|
||||
// Recipe-provided default models fill any slot the request did not select.
|
||||
// An explicit selection blocks the default for that slot, including an
|
||||
// empty string (a deliberate opt-out of the default). The tae/taesd pair
|
||||
// is one slot: an explicit request selection of either spelling overrides
|
||||
// the recipe default for that slot.
|
||||
if (recipe.contains("models")) {
|
||||
for (auto it = recipe.at("models").begin(); it != recipe.at("models").end(); ++it) {
|
||||
if (selection.contains(it.key()))
|
||||
continue;
|
||||
if ((it.key() == "tae" || it.key() == "taesd") && (selection.contains("tae") || selection.contains("taesd")))
|
||||
continue;
|
||||
selection[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
std::set<std::string> allowed;
|
||||
for (const auto& slot : recipe.at("slots")) {
|
||||
const auto id = slot.at("id").get<std::string>();
|
||||
@@ -642,6 +711,9 @@ void prepare_model_load(ServerRuntime& runtime, const json& body, SDContextParam
|
||||
if (selection.contains(id))
|
||||
throw std::runtime_error("specify only one of tae and taesd");
|
||||
selection[id] = selection.at(alias);
|
||||
// Keep only the declared spelling: equivalent loads must resolve
|
||||
// to an identical selection for the idempotent-load comparison.
|
||||
selection.erase(alias);
|
||||
}
|
||||
}
|
||||
const std::string file_id = selection.value(id, std::string());
|
||||
@@ -665,6 +737,20 @@ void prepare_model_load(ServerRuntime& runtime, const json& body, SDContextParam
|
||||
if (!allowed.count(it.key()))
|
||||
throw std::runtime_error("unknown model slot: " + it.key());
|
||||
}
|
||||
// Omitting an optional slot and sending it as an empty string resolve to
|
||||
// the same context; drop the empty key so equivalent loads compare
|
||||
// identically instead of rebuilding the context. The explicit empty
|
||||
// string already overrode any recipe default during the merge, so its
|
||||
// intent is preserved.
|
||||
for (auto it = selection.begin(); it != selection.end();) {
|
||||
if (!it->is_string() || !it->get<std::string>().empty()) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
it = selection.erase(it);
|
||||
}
|
||||
// Echo the fully resolved selection (recipe defaults merged in) for status reporting.
|
||||
body["models"] = selection;
|
||||
for (const auto& group : recipe.value("required_any", json::array())) {
|
||||
if (std::none_of(group.begin(), group.end(), [&](const json& value) {
|
||||
return !selection.value(value.get<std::string>(), std::string()).empty();
|
||||
|
||||
@@ -51,6 +51,14 @@ void initialize_server_models(ServerRuntime& runtime);
|
||||
json resolve_recipe_features(const json& recipe);
|
||||
json model_generation_features(const ServerRuntime& runtime);
|
||||
|
||||
void prepare_model_load(ServerRuntime& runtime, const json& body, SDContextParams& context, SDGenerationParams& defaults, json* loaded_recipe = nullptr);
|
||||
// Resolves the load request into context/generation params. `body` is updated
|
||||
// so `models` reflects the fully resolved selection (recipe defaults merged in),
|
||||
// letting the caller report exactly which components got loaded.
|
||||
void prepare_model_load(ServerRuntime& runtime, json& body, SDContextParams& context, SDGenerationParams& defaults, json* loaded_recipe = nullptr);
|
||||
|
||||
// True when both parameter sets resolve to the same component files. File IDs
|
||||
// stay stable if a model directory symlink is retargeted inside the configured
|
||||
// root, so the idempotent-load check compares the resolved paths too.
|
||||
bool component_paths_match(const SDContextParams& a, const SDContextParams& b);
|
||||
|
||||
#endif
|
||||
@@ -77,3 +77,35 @@ These are built-in catalog rules. Users may keep personal presets in
|
||||
`<model-dir>/recipes/`; they need not add those presets to the built-in list.
|
||||
See [the recipe schema and feature reference](../api.md#user-recipe-format)
|
||||
for supported fields.
|
||||
|
||||
## Output dimension alignment
|
||||
|
||||
Set `size_multiple` when the runtime's output dimension alignment differs from
|
||||
the default of `8`. Match `GenerationRequest::align_image_size` in
|
||||
[`src/pipeline/request.cpp`](../../../src/pipeline/request.cpp): the VAE scale
|
||||
factor multiplied by the model definition's `diffusion_down_factor`. The scale
|
||||
comes from [`VAE::get_scale_factor`](../../../src/model/vae/vae.hpp), with the
|
||||
PiD override in [`diffusion_engine.cpp`](../../../src/pipeline/diffusion_engine.cpp);
|
||||
model geometry is defined under [`src/model/integrations`](../../../src/model/integrations/).
|
||||
|
||||
Use the runtime's output alignment, not the nominal patch size: some models pad
|
||||
patches internally and crop their output back to the requested size. Pixel-space
|
||||
models with an alignment of `1` explicitly use `size_multiple: 1`. MiniT2I also
|
||||
has this alignment, but its separate fixed `512 × 512` output restriction still
|
||||
applies. Reference-image preprocessing constraints are independent of this field.
|
||||
|
||||
## Video length constraints
|
||||
|
||||
Video recipes declare `video_constraints` to match `align_video_frames` in
|
||||
[`diffusion_engine.cpp`](../../../src/pipeline/diffusion_engine.cpp) and the FPS
|
||||
and AnimateDiff overrides in
|
||||
[`request.cpp`](../../../src/pipeline/request.cpp). Wan, HunyuanVideo and LingBot
|
||||
round down to `4n + 1`; LTX rounds down to `8n + 1`. MiniMax-H3 rounds up to
|
||||
`17n + 5`, with at least 5 frames and fixed 24 FPS. AnimateDiff accepts whole
|
||||
frames up to 32. Wan 2.2 S2V fixes FPS at 16.
|
||||
|
||||
Declare fixed FPS only where the runtime enforces it; recommended FPS belongs
|
||||
in `defaults.fps`. These constraints guide UI conversion and estimates, not
|
||||
runtime behavior. Audio-driven output and temporal upscaling may still change
|
||||
the final frame count. See the [recipe schema](../api.md#user-recipe-format)
|
||||
for field defaults and validation rules.
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "animatediff-sd15",
|
||||
"name": "AnimateDiff SD 1.5",
|
||||
"size_multiple": 64,
|
||||
"description": "Use Video Generation with an SD 1.5 checkpoint and compatible motion module.",
|
||||
"model_url": "https://huggingface.co/guoyww/animatediff",
|
||||
"slots": [
|
||||
@@ -22,6 +23,13 @@
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"video_constraints": {
|
||||
"frame_step": 1,
|
||||
"frame_offset": 0,
|
||||
"frame_rounding": "floor",
|
||||
"min_frames": 1,
|
||||
"max_frames": 32
|
||||
},
|
||||
"defaults": {
|
||||
"sampling_method": "euler",
|
||||
"video_frames": 8,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "chroma-radiance",
|
||||
"name": "Chroma Radiance",
|
||||
"size_multiple": 1,
|
||||
"description": "Pixel-space generation without a VAE. Also accepts the x0 and experimental patch-32 variants.",
|
||||
"model_url": "https://huggingface.co/lodestones/Chroma1-Radiance",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "cosxl-edit",
|
||||
"name": "CosXL Edit",
|
||||
"size_multiple": 64,
|
||||
"description": "Supply the image to edit as a reference image.",
|
||||
"model_url": "https://huggingface.co/stabilityai/cosxl",
|
||||
"slots": [
|
||||
|
||||
@@ -42,6 +42,8 @@ For community edit adapters and other optional extensions, these are the base-mo
|
||||
| `flux3-klein` | 1 | 16 | 1024 × 1024 | [Source](https://raw.githubusercontent.com/black-forest-labs/flux-3-image-klein/main/README.md) |
|
||||
| `flux3-klein-base` | 4 | 20 | 1024 × 1024 | [Source](https://raw.githubusercontent.com/black-forest-labs/flux-3-image-klein/main/src/flux3/pipeline.py) |
|
||||
| `hidream-o1-dev` | 1 | 20 | 1024 × 1024 | [Source](https://huggingface.co/HiDream-ai/HiDream-O1-Image-Dev) |
|
||||
| `hyperflow-h3-flash-fl2va` | 1 | 6 | 1344 × 768 | [Source](https://huggingface.co/VideoRebirthAI/hyperflow-h3-flash) Two-time FL2VA checkpoint; native text and first/last-frame generation. Fixed 6-step video sigma grid with shift 12, 124 frames at 24 FPS. Reuses MiniMax-H3 components. |
|
||||
| `hyperflow-h3-flash-ref2va` | 1 | 6 | 1344 × 768 | [Source](https://huggingface.co/VideoRebirthAI/hyperflow-h3-flash) Separate two-time Ref2VA checkpoint; image references. Same schedule and components as FL2VA; video/audio references remain unverified. |
|
||||
| `hunyuan-video-15-i2v` | 6 | 20 | 1280 × 720 | [Source](https://huggingface.co/tencent/HunyuanVideo-1.5) 720p base I2V defaults; CFG/step-distilled and 480p checkpoints use different settings. |
|
||||
| `hunyuan-video-15-t2v` | 6 | 20 | 1280 × 720 | [Source](https://huggingface.co/tencent/HunyuanVideo-1.5) 720p base T2V defaults; CFG-distilled / 480p checkpoints need their published settings. |
|
||||
| `ideogram4` | 7 | 20 | 1024 × 1024 | [Source](https://raw.githubusercontent.com/ideogram-oss/ideogram4/main/docs/inference.md) Constant-guidance pipeline defaults; this recipe does not reproduce the CLI’s per-step guidance schedule. |
|
||||
@@ -71,6 +73,7 @@ For community edit adapters and other optional extensions, these are the base-mo
|
||||
| `minit2i` | 2.5 | 20 | 512 × 512 | [Source](https://huggingface.co/MiniT2I/MiniT2I/raw/main/pipeline.py) B/16 example and native model size; L/16 uses CFG 6. |
|
||||
| `moss-image-base` | 3 | 20 | 1024 × 1024 | [Source](https://huggingface.co/OpenMOSS-Team/MOSS-Image-Base) |
|
||||
| `moss-image-sft` | 3 | 20 | 1024 × 1024 | [Source](https://huggingface.co/OpenMOSS-Team/MOSS-Image-SFT) |
|
||||
| `p-h3-image` | 1 | 8 | 1024 × 1024 | [Runtime guide](../../../docs/p_h3_image.md) Fixed P-H3 sigma schedule; upstream 2912 × 2912 reduced to the catalog default. |
|
||||
| `nitrosd-realism` | 1 | 1 | 1024 × 1024 | [Source](https://huggingface.co/ChenDY/NitroFusion) |
|
||||
| `ovis-image` | 5 | 20 | 1024 × 1024 | [Source](https://huggingface.co/ATH-MaaS/Ovis-Image-7B) |
|
||||
| `pid` | 1 | 4 | 2048 × 2048 | [Source](https://huggingface.co/nvidia/PiD) Standalone PiD decoding at 2K; guidance is handled by the upstream generator. |
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "ernie-image-turbo",
|
||||
"name": "ERNIE Image Turbo",
|
||||
"size_multiple": 16,
|
||||
"model_url": "https://huggingface.co/baidu/ERNIE-Image-Turbo",
|
||||
"slots": [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "ernie-image",
|
||||
"name": "ERNIE Image",
|
||||
"size_multiple": 16,
|
||||
"model_url": "https://huggingface.co/baidu/ERNIE-Image",
|
||||
"slots": [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "fibo2-dev",
|
||||
"name": "Fibo 2.0 Dev",
|
||||
"size_multiple": 16,
|
||||
"description": "Text-to-image generation and native reference-image editing. Select a separate vision projector for editing when it is not bundled in the text encoder.",
|
||||
"model_url": "https://huggingface.co/briaai/Fibo-2.0-dev",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "fibo2-edit-turbo",
|
||||
"name": "Fibo 2.0 Edit Turbo",
|
||||
"size_multiple": 16,
|
||||
"description": "Dedicated reference-image editing checkpoint; supply reference images. Select a separate vision projector when it is not bundled in the text encoder.",
|
||||
"model_url": "https://huggingface.co/briaai/Fibo-2.0-edit-turbo",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "flux2-dev",
|
||||
"name": "FLUX.2 dev",
|
||||
"size_multiple": 16,
|
||||
"description": "Supports text-to-image and reference-image editing.",
|
||||
"model_url": "https://huggingface.co/black-forest-labs/FLUX.2-dev",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "flux2-klein-4b",
|
||||
"name": "FLUX.2 Klein 4B",
|
||||
"size_multiple": 16,
|
||||
"description": "Supports reference-image editing. The FLUX.2 small decoder is an alternative VAE.",
|
||||
"model_url": "https://huggingface.co/black-forest-labs/FLUX.2-klein-4B",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "flux2-klein-9b",
|
||||
"name": "FLUX.2 Klein 9B",
|
||||
"size_multiple": 16,
|
||||
"description": "Supports text-to-image and reference-image editing.",
|
||||
"model_url": "https://huggingface.co/black-forest-labs/FLUX.2-klein-9B",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "flux2-klein-base-4b",
|
||||
"name": "FLUX.2 Klein Base 4B",
|
||||
"size_multiple": 16,
|
||||
"description": "Supports text-to-image and reference-image editing.",
|
||||
"model_url": "https://huggingface.co/black-forest-labs/FLUX.2-klein-base-4B",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "flux2-klein-base-9b",
|
||||
"name": "FLUX.2 Klein Base 9B",
|
||||
"size_multiple": 16,
|
||||
"description": "Use the 9B base transformer with Qwen 3 8B. Supports reference-image editing.",
|
||||
"model_url": "https://huggingface.co/black-forest-labs/FLUX.2-klein-base-9B",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "flux3-klein-base",
|
||||
"name": "FLUX.3 Image Klein Base",
|
||||
"size_multiple": 16,
|
||||
"model_url": "https://huggingface.co/black-forest-labs/flux-3-image-klein-base",
|
||||
"slots": [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "flux3-klein",
|
||||
"name": "FLUX.3 Image Klein",
|
||||
"size_multiple": 16,
|
||||
"description": "Supports text-to-image and reference-image editing.",
|
||||
"model_url": "https://huggingface.co/black-forest-labs/flux-3-image-klein",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "hidream-o1-dev",
|
||||
"name": "HiDream O1 Image dev",
|
||||
"size_multiple": 1,
|
||||
"description": "Bundled checkpoint; also supports reference-image editing.",
|
||||
"model_url": "https://huggingface.co/HiDream-ai/HiDream-O1-Image-Dev",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "hunyuan-video-15-i2v",
|
||||
"name": "HunyuanVideo 1.5 I2V",
|
||||
"size_multiple": 16,
|
||||
"description": "Select HunyuanVideo 1.5 I2V weights and supply a start image in Video Generation.",
|
||||
"model_url": "https://huggingface.co/tencent/HunyuanVideo-1.5",
|
||||
"slots": [
|
||||
@@ -36,6 +37,12 @@
|
||||
"tae"
|
||||
]
|
||||
],
|
||||
"video_constraints": {
|
||||
"frame_step": 4,
|
||||
"frame_offset": 1,
|
||||
"frame_rounding": "floor",
|
||||
"min_frames": 1
|
||||
},
|
||||
"defaults": {
|
||||
"sampling_method": "euler",
|
||||
"video_frames": 33,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "hunyuan-video-15-t2v",
|
||||
"name": "HunyuanVideo 1.5 T2V",
|
||||
"size_multiple": 16,
|
||||
"description": "Select HunyuanVideo 1.5 T2V weights and use Video Generation. Use the separate I2V recipe for I2V weights.",
|
||||
"model_url": "https://huggingface.co/tencent/HunyuanVideo-1.5",
|
||||
"slots": [
|
||||
@@ -36,6 +37,12 @@
|
||||
"tae"
|
||||
]
|
||||
],
|
||||
"video_constraints": {
|
||||
"frame_step": 4,
|
||||
"frame_offset": 1,
|
||||
"frame_rounding": "floor",
|
||||
"min_frames": 1
|
||||
},
|
||||
"defaults": {
|
||||
"sampling_method": "euler",
|
||||
"video_frames": 33,
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"id": "hyperflow-h3-flash-fl2va",
|
||||
"name": "HyperFlow H3 Flash FL2VA",
|
||||
"size_multiple": 32,
|
||||
"description": "Generate video and audio from text, optionally with start/end images. Use the HyperFlow H3 Flash FL2VA transformer. Reuses MiniMax-H3 components with the HyperFlow 6-step schedule. Audio VAE is optional for silent video.",
|
||||
"model_url": "https://huggingface.co/VideoRebirthAI/hyperflow-h3-flash",
|
||||
"slots": [
|
||||
{
|
||||
"id": "diffusion_model",
|
||||
"required": true,
|
||||
"label": "HyperFlow H3 Flash FL2VA Transformer"
|
||||
},
|
||||
{
|
||||
"id": "llm",
|
||||
"required": true,
|
||||
"label": "MiniMax-H3 Qwen 3 VL 32B Text Encoder"
|
||||
},
|
||||
{
|
||||
"id": "vae",
|
||||
"required": false,
|
||||
"label": "MiniMax H3 Video VAE"
|
||||
},
|
||||
{
|
||||
"id": "tae",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "audio_vae",
|
||||
"required": false,
|
||||
"label": "MiniMax H3 Audio VAE"
|
||||
}
|
||||
],
|
||||
"required_any": [
|
||||
[
|
||||
"vae",
|
||||
"tae"
|
||||
]
|
||||
],
|
||||
"video_constraints": {
|
||||
"frame_step": 17,
|
||||
"frame_offset": 5,
|
||||
"frame_rounding": "ceil",
|
||||
"min_frames": 5,
|
||||
"fixed_fps": 24
|
||||
},
|
||||
"defaults": {
|
||||
"video_frames": 124,
|
||||
"fps": 24,
|
||||
"cfg_scale": 1,
|
||||
"width": 1344,
|
||||
"height": 768,
|
||||
"sampling_method": "euler",
|
||||
"steps": 6,
|
||||
"flow_shift": 12,
|
||||
"custom_sigmas": [
|
||||
1.0,
|
||||
0.982655942,
|
||||
0.914642572,
|
||||
0.674755692,
|
||||
0.385223746,
|
||||
0.159055337,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"features": [
|
||||
"end_image",
|
||||
"flow_shift",
|
||||
"init_image"
|
||||
],
|
||||
"disabled_features": [
|
||||
"img2img",
|
||||
"mask_image"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"id": "hyperflow-h3-flash-ref2va",
|
||||
"name": "HyperFlow H3 Flash Ref2VA",
|
||||
"size_multiple": 32,
|
||||
"description": "Generate video and audio with reference images. Use the HyperFlow H3 Flash Ref2VA transformer; video/audio references are not validated for these weights. Reuses MiniMax-H3 components with the HyperFlow 6-step schedule. Audio VAE is optional for silent video.",
|
||||
"model_url": "https://huggingface.co/VideoRebirthAI/hyperflow-h3-flash",
|
||||
"slots": [
|
||||
{
|
||||
"id": "diffusion_model",
|
||||
"required": true,
|
||||
"label": "HyperFlow H3 Flash REF2VA Transformer"
|
||||
},
|
||||
{
|
||||
"id": "llm",
|
||||
"required": true,
|
||||
"label": "MiniMax-H3 Qwen 3 VL 32B Text Encoder"
|
||||
},
|
||||
{
|
||||
"id": "vae",
|
||||
"required": false,
|
||||
"label": "MiniMax H3 Video VAE"
|
||||
},
|
||||
{
|
||||
"id": "tae",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "audio_vae",
|
||||
"required": false,
|
||||
"label": "MiniMax H3 Audio VAE"
|
||||
}
|
||||
],
|
||||
"required_any": [
|
||||
[
|
||||
"vae",
|
||||
"tae"
|
||||
]
|
||||
],
|
||||
"video_constraints": {
|
||||
"frame_step": 17,
|
||||
"frame_offset": 5,
|
||||
"frame_rounding": "ceil",
|
||||
"min_frames": 5,
|
||||
"fixed_fps": 24
|
||||
},
|
||||
"defaults": {
|
||||
"video_frames": 124,
|
||||
"fps": 24,
|
||||
"cfg_scale": 1,
|
||||
"width": 1344,
|
||||
"height": 768,
|
||||
"sampling_method": "euler",
|
||||
"steps": 6,
|
||||
"flow_shift": 12,
|
||||
"custom_sigmas": [
|
||||
1.0,
|
||||
0.982655942,
|
||||
0.914642572,
|
||||
0.674755692,
|
||||
0.385223746,
|
||||
0.159055337,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"features": [
|
||||
"flow_shift",
|
||||
"ref_images"
|
||||
],
|
||||
"disabled_features": [
|
||||
"img2img",
|
||||
"mask_image"
|
||||
]
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "ideogram4",
|
||||
"name": "Ideogram 4",
|
||||
"size_multiple": 16,
|
||||
"description": "Select the matching unconditional transformer for classifier-free guidance.",
|
||||
"model_url": "https://huggingface.co/ideogram-ai/ideogram-4-fp8",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "instruct-pix2pix",
|
||||
"name": "Instruct-Pix2Pix",
|
||||
"size_multiple": 64,
|
||||
"description": "Supply the image to edit as a reference image.",
|
||||
"model_url": "https://huggingface.co/timbrooks/instruct-pix2pix",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "iris-3b",
|
||||
"name": "Iris 3B",
|
||||
"size_multiple": 16,
|
||||
"description": "Pixel-space model; no VAE is required.",
|
||||
"model_url": "https://huggingface.co/speridlabs/iris-3b",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "lens-turbo",
|
||||
"name": "LENS Turbo",
|
||||
"size_multiple": 16,
|
||||
"model_url": "https://huggingface.co/microsoft/Lens-Turbo",
|
||||
"slots": [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "lens",
|
||||
"name": "LENS",
|
||||
"size_multiple": 16,
|
||||
"model_url": "https://huggingface.co/microsoft/Lens",
|
||||
"slots": [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "lingbot-video-dense",
|
||||
"name": "LingBot Video Dense 1.3B",
|
||||
"size_multiple": 16,
|
||||
"description": "Use Video Generation.",
|
||||
"model_url": "https://huggingface.co/robbyant/lingbot-video-dense-1.3b",
|
||||
"slots": [
|
||||
@@ -30,6 +31,12 @@
|
||||
"tae"
|
||||
]
|
||||
],
|
||||
"video_constraints": {
|
||||
"frame_step": 4,
|
||||
"frame_offset": 1,
|
||||
"frame_rounding": "floor",
|
||||
"min_frames": 1
|
||||
},
|
||||
"defaults": {
|
||||
"video_frames": 33,
|
||||
"cfg_scale": 3,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "lingbot-video-moe",
|
||||
"name": "LingBot Video MoE 30B A3B",
|
||||
"size_multiple": 16,
|
||||
"description": "Use Video Generation.",
|
||||
"model_url": "https://huggingface.co/robbyant/lingbot-video-moe-30b-a3b",
|
||||
"slots": [
|
||||
@@ -30,6 +31,12 @@
|
||||
"tae"
|
||||
]
|
||||
],
|
||||
"video_constraints": {
|
||||
"frame_step": 4,
|
||||
"frame_offset": 1,
|
||||
"frame_rounding": "floor",
|
||||
"min_frames": 1
|
||||
},
|
||||
"defaults": {
|
||||
"video_frames": 33,
|
||||
"cfg_scale": 3,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "llada-image-turbo",
|
||||
"name": "LLaDA Image Turbo",
|
||||
"size_multiple": 16,
|
||||
"description": "Use matching turbo text encoder and connectors. Editing needs connectors that include the SigVQ encoder.",
|
||||
"model_url": "https://huggingface.co/inclusionAI/LLaDA-Image-Turbo",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "llada-image",
|
||||
"name": "LLaDA Image (base)",
|
||||
"size_multiple": 16,
|
||||
"description": "Use matching base transformer, text encoder and connectors; do not mix them with Turbo components. Editing needs connectors with SigVQ and 1024x1024 output.",
|
||||
"model_url": "https://huggingface.co/inclusionAI/LLaDA-Image",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "ltx23-dev",
|
||||
"name": "LTX 2.3 dev",
|
||||
"size_multiple": 32,
|
||||
"description": "Supports text, start-image and start/end-image video. Audio VAE is optional for silent video. Configure latent upscaling separately in generation settings. Optional distillation LoRAs use this recipe; select the LoRA and adjust the sampling settings separately.",
|
||||
"model_url": "https://huggingface.co/Lightricks/LTX-2.3",
|
||||
"slots": [
|
||||
@@ -39,6 +40,12 @@
|
||||
"tae"
|
||||
]
|
||||
],
|
||||
"video_constraints": {
|
||||
"frame_step": 8,
|
||||
"frame_offset": 1,
|
||||
"frame_rounding": "floor",
|
||||
"min_frames": 1
|
||||
},
|
||||
"defaults": {
|
||||
"sampling_method": "euler",
|
||||
"video_frames": 33,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "ltx23-distilled",
|
||||
"name": "LTX 2.3 Distilled 1.1",
|
||||
"size_multiple": 32,
|
||||
"description": "Supports text, start-image and start/end-image video. Audio VAE is optional for silent video. Configure latent upscaling separately in generation settings.",
|
||||
"model_url": "https://huggingface.co/Lightricks/LTX-2.3",
|
||||
"slots": [
|
||||
@@ -39,6 +40,12 @@
|
||||
"tae"
|
||||
]
|
||||
],
|
||||
"video_constraints": {
|
||||
"frame_step": 8,
|
||||
"frame_offset": 1,
|
||||
"frame_rounding": "floor",
|
||||
"min_frames": 1
|
||||
},
|
||||
"defaults": {
|
||||
"sampling_method": "euler",
|
||||
"video_frames": 121,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "ltx25-dev",
|
||||
"name": "LTX 2.5 dev",
|
||||
"size_multiple": 32,
|
||||
"description": "Use the conv video VAE and LTX-specific Gemma 4 with bundled projection. Audio VAE is optional for silent video.",
|
||||
"model_url": "https://huggingface.co/Lightricks/LTX-2.5",
|
||||
"slots": [
|
||||
@@ -35,6 +36,12 @@
|
||||
"tae"
|
||||
]
|
||||
],
|
||||
"video_constraints": {
|
||||
"frame_step": 8,
|
||||
"frame_offset": 1,
|
||||
"frame_rounding": "floor",
|
||||
"min_frames": 1
|
||||
},
|
||||
"defaults": {
|
||||
"sampling_method": "euler",
|
||||
"fps": 24,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "mage-flow-edit",
|
||||
"name": "Mage Flow Edit",
|
||||
"size_multiple": 16,
|
||||
"description": "Supply reference images. Select a separate vision projector when it is not bundled in the text encoder.",
|
||||
"model_url": "https://huggingface.co/microsoft/Mage-Flow-Edit",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "mage-flow-turbo",
|
||||
"name": "Mage Flow Turbo",
|
||||
"size_multiple": 16,
|
||||
"model_url": "https://huggingface.co/microsoft/Mage-Flow-Turbo",
|
||||
"slots": [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "mage-flow",
|
||||
"name": "Mage Flow",
|
||||
"size_multiple": 16,
|
||||
"model_url": "https://huggingface.co/microsoft/Mage-Flow",
|
||||
"slots": [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "ming-image",
|
||||
"name": "Ming Image 0.1",
|
||||
"size_multiple": 16,
|
||||
"model_url": "https://huggingface.co/inclusionAI/Ming-Image-0.1-Design",
|
||||
"slots": [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "minimax-h3-fl2va",
|
||||
"name": "MiniMax H3 FL2VA",
|
||||
"size_multiple": 32,
|
||||
"description": "Generate video and audio, optionally with start/end images. Supports full and pruned FL2VA weights. Audio VAE is optional for silent video.",
|
||||
"model_url": "https://huggingface.co/MiniMaxAI/MiniMax-H3",
|
||||
"slots": [
|
||||
@@ -35,6 +36,13 @@
|
||||
"tae"
|
||||
]
|
||||
],
|
||||
"video_constraints": {
|
||||
"frame_step": 17,
|
||||
"frame_offset": 5,
|
||||
"frame_rounding": "ceil",
|
||||
"min_frames": 5,
|
||||
"fixed_fps": 24
|
||||
},
|
||||
"defaults": {
|
||||
"video_frames": 56,
|
||||
"fps": 24,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "minimax-h3-ref2va",
|
||||
"name": "MiniMax H3 Ref2VA",
|
||||
"size_multiple": 32,
|
||||
"description": "Supply image, video or audio references with compatible Ref2VA weights. Audio VAE is optional for silent video.",
|
||||
"model_url": "https://huggingface.co/MiniMaxAI/MiniMax-H3",
|
||||
"slots": [
|
||||
@@ -35,6 +36,13 @@
|
||||
"tae"
|
||||
]
|
||||
],
|
||||
"video_constraints": {
|
||||
"frame_step": 17,
|
||||
"frame_offset": 5,
|
||||
"frame_rounding": "ceil",
|
||||
"min_frames": 5,
|
||||
"fixed_fps": 24
|
||||
},
|
||||
"defaults": {
|
||||
"video_frames": 56,
|
||||
"fps": 24,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "minit2i",
|
||||
"name": "MiniT2I",
|
||||
"size_multiple": 1,
|
||||
"description": "Pixel-space model; no VAE is required.",
|
||||
"model_url": "https://huggingface.co/MiniT2I/MiniT2I",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "moss-image-base",
|
||||
"name": "MOSS Image Base",
|
||||
"size_multiple": 16,
|
||||
"description": "Select the MOSS-Image-Base transformer with its matching text encoder and FLUX.2 VAE or TAE.",
|
||||
"model_url": "https://huggingface.co/OpenMOSS-Team/MOSS-Image-Base",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "moss-image-sft",
|
||||
"name": "MOSS Image SFT",
|
||||
"size_multiple": 16,
|
||||
"description": "Select the MOSS-Image-SFT transformer with its matching text encoder and FLUX.2 VAE or TAE.",
|
||||
"model_url": "https://huggingface.co/OpenMOSS-Team/MOSS-Image-SFT",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "nitrosd-realism",
|
||||
"name": "NitroSD Realism",
|
||||
"size_multiple": 64,
|
||||
"model_url": "https://huggingface.co/ChenDY/NitroFusion",
|
||||
"slots": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"id": "p-h3-image",
|
||||
"name": "P-H3-Image",
|
||||
"size_multiple": 32,
|
||||
"description": "Supports text-to-image and reference-image editing with the fixed 8-step schedule. Use the P-H3 single-frame VAE and Qwen3-VL-4B with its vision tower. Supply the P-H3 text connector separately unless it is included in the text encoder.",
|
||||
"model_url": "https://huggingface.co/PrunaAI/P-H3-Image",
|
||||
"slots": [
|
||||
{
|
||||
"id": "diffusion_model",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "llm",
|
||||
"required": true,
|
||||
"label": "Qwen 3 VL 4B Text Encoder"
|
||||
},
|
||||
{
|
||||
"id": "llm_vision",
|
||||
"required": false,
|
||||
"label": "Vision projector (required when not included in the text encoder)"
|
||||
},
|
||||
{
|
||||
"id": "embeddings_connectors",
|
||||
"required": false,
|
||||
"label": "P-H3 Text Connector (required when not included in the text encoder)"
|
||||
},
|
||||
{
|
||||
"id": "vae",
|
||||
"required": true,
|
||||
"label": "P-H3 Image VAE"
|
||||
}
|
||||
],
|
||||
"defaults": {
|
||||
"sampling_method": "euler",
|
||||
"cfg_scale": 1,
|
||||
"steps": 8,
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"custom_sigmas": [
|
||||
1.0,
|
||||
0.984753,
|
||||
0.967905,
|
||||
0.951688,
|
||||
0.925372,
|
||||
0.881923,
|
||||
0.810083,
|
||||
0.528718,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"features": [
|
||||
"ref_images"
|
||||
]
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "pid-15-flux2",
|
||||
"name": "PiD 1.5 (Flux.2 VAE)",
|
||||
"size_multiple": 1,
|
||||
"description": "Supply a reference image; the first image is used as the source latent. Select a matching Flux.2 PiD checkpoint and VAE.",
|
||||
"model_url": "https://huggingface.co/nvidia/PiD",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "pid-15-qwen-image",
|
||||
"name": "PiD 1.5 (Qwen Image VAE)",
|
||||
"size_multiple": 1,
|
||||
"description": "Supply a reference image; the first image is used as the source latent. Select a matching Qwen Image PiD checkpoint and VAE. Use the Qwen Image 2D VAE.",
|
||||
"model_url": "https://huggingface.co/nvidia/PiD",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "pid-15-sd3",
|
||||
"name": "PiD 1.5 (SD3 VAE)",
|
||||
"size_multiple": 1,
|
||||
"description": "Supply a reference image; the first image is used as the source latent. Select a matching SD3 PiD checkpoint and VAE.",
|
||||
"model_url": "https://huggingface.co/nvidia/PiD",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "pid-15",
|
||||
"name": "PiD 1.5 (Flux / Z-Image VAE)",
|
||||
"size_multiple": 1,
|
||||
"description": "Supply a reference image; the first image is used as the source latent. Select a Flux/Z-Image PiD checkpoint and matching Flux VAE.",
|
||||
"model_url": "https://huggingface.co/nvidia/PiD",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "pid-flux2",
|
||||
"name": "PiD (Flux.2 VAE)",
|
||||
"size_multiple": 1,
|
||||
"description": "Supply a reference image; the first image is used as the source latent. Select a matching Flux.2 PiD checkpoint and VAE.",
|
||||
"model_url": "https://huggingface.co/nvidia/PiD",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "pid-qwen-image",
|
||||
"name": "PiD (Qwen Image VAE)",
|
||||
"size_multiple": 1,
|
||||
"description": "Supply a reference image; the first image is used as the source latent. Select a matching Qwen Image PiD checkpoint and VAE. Use the Qwen Image 2D VAE.",
|
||||
"model_url": "https://huggingface.co/nvidia/PiD",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "pid-sd3",
|
||||
"name": "PiD (SD3 VAE)",
|
||||
"size_multiple": 1,
|
||||
"description": "Supply a reference image; the first image is used as the source latent. Select a matching SD3 PiD checkpoint and VAE.",
|
||||
"model_url": "https://huggingface.co/nvidia/PiD",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "pid",
|
||||
"name": "PiD (Flux / Z-Image VAE)",
|
||||
"size_multiple": 1,
|
||||
"description": "Supply a reference image; the first image is used as the source latent. Select a Flux/Z-Image PiD checkpoint and matching Flux VAE.",
|
||||
"model_url": "https://huggingface.co/nvidia/PiD",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "pixart-alpha-1024",
|
||||
"name": "PixArt Alpha XL 1024",
|
||||
"size_multiple": 16,
|
||||
"description": "Use the matching PixArt Alpha transformer and SD 1.x-style VAE. Resolution/aspect-ratio micro-conditioning is not currently applied by the runtime.",
|
||||
"model_url": "https://huggingface.co/PixArt-alpha/PixArt-XL-2-1024-MS",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "pixart-alpha-512",
|
||||
"name": "PixArt Alpha XL 512",
|
||||
"size_multiple": 16,
|
||||
"description": "Use the matching PixArt Alpha transformer and SD 1.x-style VAE. Resolution/aspect-ratio micro-conditioning is not currently applied by the runtime.",
|
||||
"model_url": "https://huggingface.co/PixArt-alpha/PixArt-XL-2-512x512",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "pixart-sigma",
|
||||
"name": "PixArt Sigma XL 1024",
|
||||
"size_multiple": 16,
|
||||
"model_url": "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS",
|
||||
"slots": [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "qwen-image-21-turbo",
|
||||
"name": "Qwen Image 2.1 Turbo",
|
||||
"size_multiple": 32,
|
||||
"description": "Use the standalone Turbo diffusion weights for generation and editing with the fixed 8-step schedule. Select the Qwen Image 2.1 VAE or TAEQI2.1, and supply a vision projector when needed.",
|
||||
"model_url": "https://huggingface.co/Qwen/Qwen-Image-2.1-Turbo",
|
||||
"slots": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"id": "qwen-image-21",
|
||||
"name": "Qwen Image 2.1",
|
||||
"size_multiple": 32,
|
||||
"description": "Supports generation and editing. Select the full Qwen Image 2.1 VAE or TAEQI2.1. Supply a separate vision projector when needed. Select acceleration LoRAs and adjust their settings separately.",
|
||||
"model_url": "https://huggingface.co/Qwen/Qwen-Image-2.1",
|
||||
"slots": [
|
||||
|
||||
Loaded 100 of 325 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user