mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-10-14 05:10:44 +08:00
Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46362fc4c0 | ||
|
|
d763dae3b8 | ||
|
|
60423ab5f6 | ||
|
|
7e3e20a50b | ||
|
|
d637cfb935 | ||
|
|
792bcd344c | ||
|
|
2988060a43 | ||
|
|
1b0ba10893 | ||
|
|
b7a74e68d7 | ||
|
|
05845fde41 | ||
|
|
0e1d733b7a | ||
|
|
ce95c09ce1 | ||
|
|
f89d9b13d7 | ||
|
|
f2f168fa70 | ||
|
|
7867f6da4a | ||
|
|
228c707fde | ||
|
|
e16d26a96a | ||
|
|
a4a966990d | ||
|
|
a1ded76da5 | ||
|
|
8cc5ad8f1c | ||
|
|
4a892293ac | ||
|
|
fd43b542a6 | ||
|
|
0963357214 | ||
|
|
1da55ebde8 | ||
|
|
fb8ee71f55 | ||
|
|
96f67fe631 | ||
|
|
203a882a28 | ||
|
|
019bcfc0ae | ||
|
|
c150a6bafe | ||
|
|
f4b5326da7 | ||
|
|
bbdcad48dd | ||
|
|
b6947b8bdc | ||
|
|
9db0db6570 | ||
|
|
d4bfcd5d78 | ||
|
|
3f8527a46c | ||
|
|
42ab1c12f1 | ||
|
|
27f7c430c0 | ||
|
|
ede32a621e | ||
|
|
47e83d7136 | ||
|
|
f8890b9db3 | ||
|
|
61a83b85cc | ||
|
|
9947eebec8 | ||
|
|
168f7b88b9 | ||
|
|
2f886889e6 |
@@ -451,8 +451,8 @@ jobs:
|
||||
runs-on: windows-2022
|
||||
|
||||
env:
|
||||
ROCM_VERSION: "7.14.0"
|
||||
GPU_TARGETS: "gfx1010;gfx1011;gfx1012;gfx1030;gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036;gfx1100;gfx1101;gfx1102;gfx1103;gfx1150;gfx1151;gfx1152;gfx1153;gfx1200;gfx1201"
|
||||
ROCM_VERSION: "10.1.0"
|
||||
gpu_targets: "gfx1010;gfx1011;gfx1012;gfx1030;gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036;gfx1100;gfx1101;gfx1102;gfx1103;gfx1150;gfx1151;gfx1152;gfx1153;gfx1200;gfx1201"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -498,7 +498,7 @@ jobs:
|
||||
|
||||
write-host "Installing ROCm wheels for multi-arch support"
|
||||
# Install ROCm wheels for multi-arch support (this may take several minutes)
|
||||
python -m pip install --index-url https://repo.amd.com/rocm/whl-multi-arch/ "rocm[libraries,devel]==${{env.ROCM_VERSION}}"
|
||||
python -m pip install --index-url https://stable.repo.amd.com/rocm/whl-next/ "rocm[libraries,devel]==${{env.ROCM_VERSION}}"
|
||||
|
||||
# Pre-expand the devel tree so it is included in the cache
|
||||
write-host "Initializing ROCm devel tree"
|
||||
@@ -560,6 +560,18 @@ jobs:
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: pr-mpt/actions-commit-hash@v2
|
||||
|
||||
- name: Bundle HIP runtime libraries
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$binPath = (rocm-sdk path --bin).Trim()
|
||||
if (-not $binPath) { throw "rocm-sdk path --bin returned empty" }
|
||||
foreach ($dll in @("amdhip64_7.dll", "rocm_kpack.dll", "amd_comgr.dll")) {
|
||||
$f = Get-ChildItem -Path $binPath -Filter $dll -ErrorAction SilentlyContinue
|
||||
if (-not $f) { throw "no match for $dll in $binPath" }
|
||||
Copy-Item $f.FullName -Destination .\build\bin\ -Force
|
||||
}
|
||||
|
||||
- name: Pack artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
run: |
|
||||
@@ -582,9 +594,9 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- ROCM_VERSION: "7.14.0"
|
||||
- ROCM_VERSION: "10.1.0"
|
||||
gpu_targets: "gfx900;gfx906;gfx908;gfx90a;gfx90c;gfx942;gfx950;gfx1010;gfx1011;gfx1012;gfx1030;gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036;gfx1100;gfx1101;gfx1102;gfx1103;gfx1150;gfx1151;gfx1152;gfx1153;gfx1200;gfx1201"
|
||||
build: x64
|
||||
build: 'x64'
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
@@ -598,6 +610,16 @@ jobs:
|
||||
with:
|
||||
key: ubuntu-rocm-cmake-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
|
||||
evict-old-files: 1d
|
||||
max-size: "2G"
|
||||
|
||||
- name: Tune ccache for reinstalled ROCm toolchain
|
||||
run: |
|
||||
# ROCm is pip-installed fresh each run, so the clang binary's mtime
|
||||
# changes every time. With the default compiler_check=mtime that
|
||||
# invalidates the cache; hash compiler contents instead so warm
|
||||
# builds hit.
|
||||
ccache --set-config=compiler_check=content
|
||||
ccache --set-config=sloppiness=time_macros,include_file_mtime,include_file_ctime
|
||||
|
||||
- name: Dependencies
|
||||
id: depends
|
||||
@@ -634,7 +656,7 @@ jobs:
|
||||
# libraries = HIP runtime and CMake configs needed for linking
|
||||
# devel = compilers, headers, static libs
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --index-url https://repo.amd.com/rocm/whl-multi-arch/ "rocm[libraries,devel]==${{matrix.ROCM_VERSION}}"
|
||||
python -m pip install --index-url https://stable.repo.amd.com/rocm/whl-next/ "rocm[libraries,devel]==${{matrix.ROCM_VERSION}}"
|
||||
|
||||
# Get ROCm installation paths using the rocm-sdk CLI tool
|
||||
ROCM_PATH=$(rocm-sdk path --root)
|
||||
|
||||
@@ -225,6 +225,8 @@ file(GLOB SD_LIB_SOURCES CONFIGURE_DEPENDS
|
||||
"src/core/*.h"
|
||||
"src/core/*.cpp"
|
||||
"src/core/*.hpp"
|
||||
"src/core/probe/*.h"
|
||||
"src/core/probe/*.cpp"
|
||||
"src/extensions/*.h"
|
||||
"src/extensions/*.cpp"
|
||||
"src/extensions/*.hpp"
|
||||
@@ -342,6 +344,14 @@ if (SD_CUDA)
|
||||
target_link_libraries(${SD_LIB} PUBLIC CUDA::cuda_driver)
|
||||
set_property(SOURCE src/core/ggml_extend_backend.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_USE_CUDA)
|
||||
endif()
|
||||
if (SD_HIPBLAS)
|
||||
find_package(hip REQUIRED CONFIG HINTS "$ENV{ROCM_PATH}" /opt/rocm)
|
||||
target_link_libraries(${SD_LIB} PRIVATE hip::host)
|
||||
set_property(SOURCE src/core/ggml_extend_backend.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_USE_HIP)
|
||||
if (GGML_HIP_MMQ_MFMA)
|
||||
set_property(SOURCE src/core/ggml_extend_backend.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_HIP_MFMA)
|
||||
endif()
|
||||
endif()
|
||||
target_include_directories(${SD_LIB} PUBLIC . src include)
|
||||
target_include_directories(${SD_LIB} PRIVATE src/core)
|
||||
target_include_directories(${SD_LIB} PUBLIC . thirdparty)
|
||||
|
||||
@@ -29,6 +29,10 @@ PRs should include:
|
||||
- Linked issue/PR context when applicable.
|
||||
- Screenshots or sample outputs for UI/visual behavior changes.
|
||||
|
||||
## README UI Listings
|
||||
|
||||
The [UIs list in the README](README.md#uis) only includes open-source projects that use `stable-diffusion.cpp` as a backend. Do not submit PRs to add closed-source UIs.
|
||||
|
||||
## Code Style
|
||||
|
||||
Format code according to the repository style before submitting changes.
|
||||
|
||||
@@ -52,6 +52,7 @@ API and command-line option may change frequently.***
|
||||
- [PiD](./docs/pid.md)
|
||||
- [LongCat Image](./docs/longcat_image.md)
|
||||
- [Z-Image](./docs/z_image.md)
|
||||
- [Z-Image L2P](./docs/z_image_l2p.md)
|
||||
- [MiniT2I](./docs/minit2i.md)
|
||||
- [SenseNova U1.5](./docs/sensenova_u1.md)
|
||||
- [Ovis-Image](./docs/ovis_image.md)
|
||||
@@ -64,7 +65,9 @@ API and command-line option may change frequently.***
|
||||
- [HiDream-O1-Image](./docs/hidream_o1_image.md)
|
||||
- [Ideogram4](./docs/ideogram4.md)
|
||||
- [LLaDA-Image](./docs/llada_image.md)
|
||||
- [Ming-Image Design](./docs/ming_image.md)
|
||||
- [PixArt](./docs/pixart.md)
|
||||
- [Iris-3B](./docs/iris.md)
|
||||
- [Image Edit Models](./docs/edit.md)
|
||||
- [FLUX.1-Kontext-dev](./docs/kontext.md)
|
||||
- [Qwen Image Edit series](./docs/qwen_image_edit.md)
|
||||
@@ -180,7 +183,6 @@ These projects wrap `stable-diffusion.cpp` for easier use in other languages/fra
|
||||
These projects use `stable-diffusion.cpp` as a backend for their image generation.
|
||||
|
||||
- [GIMP Plugins](https://github.com/themanyone/gimp-plugins)
|
||||
- [Jellybox](https://jellybox.com)
|
||||
- [Stable Diffusion GUI](https://github.com/fszontagh/sd.cpp.gui.wx)
|
||||
- [Stable Diffusion CLI-GUI](https://github.com/piallai/stable-diffusion.cpp)
|
||||
- [Local Diffusion](https://github.com/rmatif/Local-Diffusion)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
@@ -40,4 +40,6 @@ RUN printf '#!/bin/sh\nexec /sd.cpp/bin/sd-cli "$@"\n' > /sd-cli && \
|
||||
printf '#!/bin/sh\nexec /sd.cpp/bin/sd-server "$@"\n' > /sd-server && \
|
||||
chmod +x /sd-cli /sd-server
|
||||
|
||||
WORKDIR /sd.cpp
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
|
||||
@@ -57,4 +57,6 @@ RUN printf '#!/bin/sh\nexec /sd.cpp/bin/sd-cli "$@"\n' > /sd-cli && \
|
||||
printf '#!/bin/sh\nexec /sd.cpp/bin/sd-server "$@"\n' > /sd-server && \
|
||||
chmod +x /sd-cli /sd-server
|
||||
|
||||
WORKDIR /sd.cpp
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
|
||||
@@ -42,4 +42,6 @@ RUN printf '#!/bin/sh\nexec /sd.cpp/bin/sd-cli "$@"\n' > /sd-cli && \
|
||||
printf '#!/bin/sh\nexec /sd.cpp/bin/sd-server "$@"\n' > /sd-server && \
|
||||
chmod +x /sd-cli /sd-server
|
||||
|
||||
WORKDIR /sd.cpp
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
FROM rocm/dev-ubuntu-24.04 AS build
|
||||
|
||||
# sd-server embeds the web UI at build time, so the build image needs Node/pnpm.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential git cmake ninja-build ccache ca-certificates curl gnupg && \
|
||||
mkdir -p /etc/apt/keyrings && \
|
||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key -o /tmp/nodesource-repo.gpg.key && \
|
||||
gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg /tmp/nodesource-repo.gpg.key && \
|
||||
rm /tmp/nodesource-repo.gpg.key && \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" > /etc/apt/sources.list.d/nodesource.list && \
|
||||
apt-get update && apt-get install -y --no-install-recommends nodejs && \
|
||||
npm install -g pnpm@10.15.1 && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /sd.cpp
|
||||
|
||||
# ROCm's clang is not on PATH in Docker RUN shells (profile scripts are not loaded).
|
||||
ENV PATH="/opt/rocm/llvm/bin:$PATH"
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG AMDGPU_TARGETS=""
|
||||
|
||||
RUN cmake . -B ./build -G Ninja \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DSD_HIPBLAS=ON \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DAMDGPU_TARGETS="${AMDGPU_TARGETS}" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||
RUN cmake --build ./build --config Release --parallel
|
||||
|
||||
FROM rocm/dev-ubuntu-24.04 AS runtime
|
||||
|
||||
# ROCm clang links against its own OpenMP runtime in /opt/rocm/llvm/lib,
|
||||
# and the ROCm runtime libs (hipblas, rocblas, amdhip64) live in /opt/rocm/lib.
|
||||
ENV LD_LIBRARY_PATH="/opt/rocm/llvm/lib:/opt/rocm/lib:$LD_LIBRARY_PATH"
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install --yes --no-install-recommends libgomp1 && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=build /sd.cpp/build/bin /sd.cpp/bin
|
||||
RUN printf '#!/bin/sh\nexec /sd.cpp/bin/sd-cli "$@"\n' > /sd-cli && \
|
||||
printf '#!/bin/sh\nexec /sd.cpp/bin/sd-server "$@"\n' > /sd-server && \
|
||||
chmod +x /sd-cli /sd-server
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
@@ -29,4 +29,6 @@ FROM intel/oneapi-basekit:${SYCL_VERSION}-devel-ubuntu24.04 AS runtime
|
||||
COPY --from=build /sd.cpp/build/bin/sd-cli /sd-cli
|
||||
COPY --from=build /sd.cpp/build/bin/sd-server /sd-server
|
||||
|
||||
WORKDIR /sd.cpp
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
@@ -41,4 +41,6 @@ RUN printf '#!/bin/sh\nexec /sd.cpp/bin/sd-cli "$@"\n' > /sd-cli && \
|
||||
printf '#!/bin/sh\nexec /sd.cpp/bin/sd-server "$@"\n' > /sd-server && \
|
||||
chmod +x /sd-cli /sd-server
|
||||
|
||||
WORKDIR /sd.cpp
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
## Examples
|
||||
|
||||
```sh
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\anima-preview.safetensors --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_3_06b_base.safetensors -p "a lovely cat holding a sign says 'anima.cpp'" --cfg-scale 6.0 --sampling-method euler -v --offload-to-cpu --diffusion-fa
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\anima-preview.safetensors --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_3_06b_base.safetensors -p "a lovely cat holding a sign says 'anima.cpp'" --cfg-scale 6.0 --sampling-method euler -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img alt="anima image example" src="../assets/anima/example.png" />
|
||||
|
||||
+3
-3
@@ -51,7 +51,7 @@ SD 1.5 defaults (euler_a, low CFG) produces noise-like output.
|
||||
.\bin\Release\sd-cli.exe -M vid_gen \
|
||||
--model ..\models\checkpoints\realisticVisionV60B1.safetensors \
|
||||
--motion-module ..\models\animatediff\mm_sd15_v3.safetensors \
|
||||
--offload-to-cpu --diffusion-fa \
|
||||
--offload-to-cpu \
|
||||
-p "a red apple on a wooden table" \
|
||||
--cfg-scale 8.0 --sampling-method euler --scheduler discrete \
|
||||
-H 512 -W 512 --video-frames 8 --fps 8 --steps 20 -s 42 \
|
||||
@@ -66,7 +66,7 @@ use layer streaming:
|
||||
.\bin\Release\sd-cli.exe -M vid_gen \
|
||||
--model ..\models\checkpoints\realisticVisionV60B1.safetensors \
|
||||
--motion-module ..\models\animatediff\mm_sd15_v3.safetensors \
|
||||
--offload-to-cpu --diffusion-fa \
|
||||
--offload-to-cpu \
|
||||
-p "photo of coastline, rocks, storm weather, wind, waves, lightning" \
|
||||
--cfg-scale 8.0 --sampling-method euler --scheduler discrete \
|
||||
-H 384 -W 384 --video-frames 16 --fps 8 --steps 20 -s 42 \
|
||||
@@ -79,7 +79,7 @@ Low-VRAM streaming (verified with a 2 GiB cap on RTX 3060):
|
||||
.\bin\Release\sd-cli.exe -M vid_gen \
|
||||
--model ..\models\checkpoints\realisticVisionV60B1.safetensors \
|
||||
--motion-module ..\models\animatediff\mm_sd15_v3.safetensors \
|
||||
--max-vram 2.0 --diffusion-fa \
|
||||
--max-vram 2.0 \
|
||||
-p "photo of coastline, rocks, storm weather, wind, waves, lightning" \
|
||||
--cfg-scale 8.0 --sampling-method euler --scheduler discrete \
|
||||
-H 384 -W 384 --video-frames 8 --fps 8 --steps 20 -s 42 \
|
||||
|
||||
+114
@@ -215,6 +215,120 @@ If a VAE decode fails, decoding retries with spatial tiling even when `--auto-fi
|
||||
is off; supported video decoders try temporal tiling first and can then add
|
||||
spatial tiling. Spatial retries use half-size tiles along each latent dimension.
|
||||
|
||||
## Direct convolution (`--conv-direct on|off|auto`)
|
||||
|
||||
`--conv-direct` defaults to `auto` and accepts module assignments for
|
||||
`diffusion`, `vae` (including preview VAE), and `controlnet`, using the same
|
||||
aliases and precedence rules as `--flash-attn`:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --conv-direct auto,vae=off
|
||||
sd-cli -m model.safetensors -p "a cat" --conv-direct off,diffusion=on
|
||||
```
|
||||
|
||||
- `on` requests direct 2D and 3D convolution.
|
||||
- `off` uses the existing im2col-based convolution paths.
|
||||
- `auto` enables direct convolution on compute backends with implicit GEMM
|
||||
implementations, without running a benchmark. A metadata-only probe first
|
||||
checks support for a representative F16-weight/F32-input direct convolution
|
||||
graph. Results and decision logs are cached per compute backend and dimension
|
||||
in the context, as with flash attention. Vulkan enables both 2D and 3D.
|
||||
CUDA 2D requires a compiled Turing-or-newer MMA architecture; HIP 2D requires
|
||||
the supported CDNA MFMA or RDNA WMMA architectures described below for flash
|
||||
attention. MUSA 2D has a SIMT implementation. CUDA, HIP, and MUSA 3D also have
|
||||
a SIMT implementation, so they do not require MMA. Other backends default to
|
||||
`off`. Each dimension is selected independently, and all compute devices in
|
||||
a module must qualify. Parameter-storage devices do not affect this choice.
|
||||
|
||||
These are backend-level defaults. GGML still chooses the actual kernel based on
|
||||
weight type, shape, and hardware; enabling direct convolution does not guarantee
|
||||
that every operation uses implicit GEMM. The CUDA-family implicit GEMM paths
|
||||
require F16 weights and can use ordinary direct kernels for other cases.
|
||||
This option does not change the separately created image upscaler context.
|
||||
|
||||
Repeating `--conv-direct` replaces the previous assignment string. Existing
|
||||
`--diffusion-conv-direct` (diffusion and ControlNet) and `--vae-conv-direct`
|
||||
flags are deprecated and emit a warning, but remain supported when
|
||||
`--conv-direct` is omitted. Use `--conv-direct auto,diffusion=on,controlnet=on`
|
||||
and `--conv-direct auto,vae=on`, respectively. An explicit
|
||||
`--conv-direct` takes precedence over both legacy flags regardless of order.
|
||||
Library callers can set the appended `sd_ctx_params_t::conv_direct_mode` field
|
||||
to the same string; null or empty selects `auto` while honoring legacy true
|
||||
flags. The context copies the string.
|
||||
|
||||
## Flash attention (`--flash-attn on|off|auto`)
|
||||
|
||||
`--flash-attn` defaults to `auto`. Selection happens while creating the model
|
||||
context, before inference, and is cached once per compute backend in that context.
|
||||
Parameter-storage backends are not benchmarked.
|
||||
|
||||
The option also accepts module assignments, using the same module aliases as
|
||||
`--backend`:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --flash-attn auto,te=off,vae=on
|
||||
sd-cli -m model.safetensors -p "a cat" --flash-attn off,diffusion=auto
|
||||
```
|
||||
|
||||
Supported modules are `diffusion`, `te`, `clip_vision`, `vae`, `controlnet`, and
|
||||
`audio_encoder`. Modules without an assignment use the default (`auto` if no
|
||||
default is supplied). A bare mode or `all=`, `default=`, or `*=` sets the default;
|
||||
module-specific settings take precedence regardless of their position. Within
|
||||
one assignment string, the last setting for the same module or default wins.
|
||||
Repeating `--flash-attn` replaces the entire previous string.
|
||||
|
||||
Only modules set to `auto` need backend probing. An explicit `on` or `off` for
|
||||
one module does not change another module's decision, even when they share a
|
||||
backend. Modules without configurable attention are rejected instead of silently
|
||||
ignoring their settings.
|
||||
|
||||
- `on` requests flash attention for all models. Unsupported attention operations
|
||||
still fall back to ordinary attention.
|
||||
- `off` disables flash attention, including the flash-attention fallback from
|
||||
SageAttention. SageAttention itself remains controlled by `--sage-attn`.
|
||||
- `auto` enables CPU and supported CUDA MMA configurations without timing a
|
||||
benchmark. CUDA uses both the device's capability and the compiled GGML
|
||||
architectures. HIP also skips timing on known CDNA MFMA architectures
|
||||
(`gfx908`, `gfx90a`, `gfx940`/`gfx941`/`gfx942`, `gfx950`) and RDNA 3/3.5/4
|
||||
WMMA architectures (`gfx11xx`/`gfx12xx`). The CDNA shortcut requires
|
||||
`GGML_HIP_MMQ_MFMA` to be enabled at build time. All shortcuts still check
|
||||
support for the FA probe graph. Vulkan devices whose description identifies
|
||||
NVIDIA RTX hardware (GeForce RTX, TITAN RTX, Quadro RTX and professional RTX)
|
||||
also skip timing. This is a device-name heuristic and needs no CUDA runtime.
|
||||
Unsupported graphs are disabled; other configurations, including older or
|
||||
unknown HIP architectures and other Vulkan devices, are benchmarked.
|
||||
|
||||
The probe compares ordinary and flash attention through the same attention
|
||||
builder used by models, including layout conversion and F16 conversion. It uses
|
||||
two small self-attention graphs (512 tokens with head size 64, and 1024 tokens
|
||||
with head size 128, both with four heads), checks their outputs, warms up each
|
||||
path, then alternates five synchronized measurements. Flash attention is enabled
|
||||
when the sum of its median times is at least 5% lower. These representative
|
||||
shapes are a startup heuristic, not a guarantee for every model or resolution.
|
||||
Allocation/execution failures and inconclusive probes select `off`. Timing stops
|
||||
starting new measurement pairs after one second of measured computation;
|
||||
backend initialization, warmup and an in-flight pair can take longer.
|
||||
|
||||
Different modules can select different results. A multi-device model enables
|
||||
flash attention only if all of its compute devices select `on`, preserving the
|
||||
model's existing single boolean setting. This conservative rule also covers
|
||||
row-split configurations, which can fall back to layer split during initialization.
|
||||
|
||||
`--fa` and `--diffusion-fa` are deprecated and emit a warning when used.
|
||||
Use `--flash-attn on` and `--flash-attn off,diffusion=on`, respectively.
|
||||
Both aliases write the same mode string as `--flash-attn`; the last occurrence
|
||||
of any of these options wins. Library callers
|
||||
can set `sd_ctx_params_t::flash_attn_mode` to the same string, including
|
||||
`"on"`, `"off"`, `"auto"`, or `"auto,te=off,vae=on"`.
|
||||
A null or empty string uses automatic selection unless a
|
||||
legacy `flash_attn` or `diffusion_flash_attn` boolean is set. The context copies
|
||||
the string; no new enum is required.
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --flash-attn auto
|
||||
sd-cli -m model.safetensors -p "a cat" --flash-attn off
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Purpose | Accepted names |
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Adding backend performance probes
|
||||
|
||||
`src/core/probe/backend_probe.h` provides the shared measurement machinery. Operator
|
||||
policy stays separate: `src/core/probe/flash_attn.cpp`, for example, defines attention
|
||||
shapes and its CPU/CUDA/HIP/Vulkan shortcuts, then calls `sd_run_backend_probe` for unknown
|
||||
configurations. `src/core/probe/conv_direct.cpp` uses metadata-only probe graphs
|
||||
to check direct 2D/3D convolution support, then enables known implicit GEMM
|
||||
paths. It does not allocate device tensors, execute kernels, or run measurements.
|
||||
|
||||
For each representative shape, create a `BackendProbeCase`:
|
||||
|
||||
- `build(graph, candidate)` creates inputs and either the reference or candidate
|
||||
graph in `graph.ctx`, registers input tensors in `graph.inputs`, and assigns
|
||||
`graph.output`. Leave the output null if the requested implementation is
|
||||
unavailable; do not silently benchmark its fallback as the candidate.
|
||||
- By default, inputs receive deterministic F32 data, with the same seed for both
|
||||
variants. Supply `initialize` for F16 convolution weights, integer inputs, or
|
||||
operator-specific values. It runs after allocation, outside timed execution.
|
||||
Both variants must receive equivalent logical inputs.
|
||||
- Default output validation requires matching shapes and contiguous F32 outputs,
|
||||
checks finiteness, and applies absolute and relative tolerances. Supply
|
||||
`compare` for other output types or validation rules.
|
||||
|
||||
Graphs must be safe to execute repeatedly without modifying their inputs.
|
||||
`BackendProbeGraph` owns its GGML context and buffer, so all return paths release
|
||||
probe resources. Constructors allocate metadata only; `supported()` can also be
|
||||
used before a known-architecture shortcut, without allocating device tensors.
|
||||
|
||||
`BackendProbeOptions` controls graph metadata capacity, warmup count, measurement
|
||||
count, measured-time budget, minimum free device memory, comparison tolerances,
|
||||
and minimum improvement. Warmup and allocation are excluded from timing. The
|
||||
runner alternates the two variants, synchronizes the backend around execution,
|
||||
and compares sums of per-case medians. The time budget is checked before each
|
||||
measurement pair; it cannot interrupt an in-flight backend operation.
|
||||
|
||||
Only use `candidate_faster` when `completed` is true. On incomplete probes,
|
||||
`error` reports why comparison failed and the timing totals may be partial.
|
||||
The caller handles logging, known architecture rules, fallback policy, and
|
||||
context-lifetime caching. Keep caches separate by operation (or key a shared
|
||||
cache by both operation and backend); an attention result must not select a
|
||||
convolution implementation. If shapes or settings vary within one context,
|
||||
include them in the corresponding cache key as well.
|
||||
|
||||
Convolution results are cached separately for 2D and 3D, once per compute backend
|
||||
in the context, just like attention results. Each probe logs its backend, decision,
|
||||
and reason; mixed-device modules log when their compute devices disagree.
|
||||
|
||||
Module assignment parsing remains in `src/core/ggml_extend_backend.cpp`, next
|
||||
to backend assignment parsing. The probe directory does not parse CLI/API options.
|
||||
+2
-2
@@ -17,7 +17,7 @@ Boogu Image uses a Boogu diffusion transformer, the FLUX VAE, and Qwen3-VL as th
|
||||
### Boogu Image Base
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\boogu_image_base_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --vae ..\models\vae\ae.sft -p "a lovely cat" --diffusion-fa -v --offload-to-cpu
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\boogu_image_base_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --vae ..\models\vae\ae.sft -p "a lovely cat" -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img width="256" alt="Boogu Image Base example" src="../assets/boogu/example.png" />
|
||||
@@ -25,7 +25,7 @@ Boogu Image uses a Boogu diffusion transformer, the FLUX VAE, and Qwen3-VL as th
|
||||
### Boogu Image Edit
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\boogu_image_edit_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --llm_vision ..\models\text_encoders\mmproj-Qwen3VL-8B-Instruct-F16.gguf --vae ..\models\vae\ae.sft --diffusion-fa -v --offload-to-cpu -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'boogu.cpp'"
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\boogu_image_edit_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --llm_vision ..\models\text_encoders\mmproj-Qwen3VL-8B-Instruct-F16.gguf --vae ..\models\vae\ae.sft -v --offload-to-cpu -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'boogu.cpp'"
|
||||
```
|
||||
|
||||
<img width="256" alt="Boogu Image Edit example" src="../assets/boogu/edit_example.png" />
|
||||
+32
-1
@@ -24,12 +24,25 @@ docker build -f docker/Dockerfile -t sd .
|
||||
|
||||
## Building variants using Docker
|
||||
|
||||
Vulkan:
|
||||
### Vulkan:
|
||||
|
||||
```shell
|
||||
docker build -f docker/Dockerfile.vulkan -t sd .
|
||||
```
|
||||
|
||||
### ROCm (AMD GPU):
|
||||
|
||||
Defining one or multiple targets with `--build-arg AMDGPU_TARGETS` is strongly recommended.
|
||||
Building the image will otherwise likely fail or build for a target that does not fit your
|
||||
hardware.
|
||||
|
||||
```shell
|
||||
# Build for one target
|
||||
docker build -f docker/Dockerfile.rocm --build-arg "AMDGPU_TARGETS=gfx1100" -t sd .
|
||||
# Build for multiple targets
|
||||
docker build -f docker/Dockerfile.rocm --build-arg "AMDGPU_TARGETS=gfx1030;gfx1100" -t sd .
|
||||
```
|
||||
|
||||
## Run locally built image's CLI
|
||||
|
||||
```shell
|
||||
@@ -37,3 +50,21 @@ docker run --rm -v /path/to/models:/models -v /path/to/output/:/output sd [args.
|
||||
# For example
|
||||
# docker run --rm -v ./models:/models -v ./build:/output sd -m /models/sd-v1-4.ckpt -p "a lovely cat" -v -o /output/output.png
|
||||
```
|
||||
|
||||
### ROCm (AMD GPU)
|
||||
|
||||
The user that executes this command needs to be in the `video` group and have read and write
|
||||
access to `/dev/dri` and `/dev/kfd`.
|
||||
|
||||
`--device` and `--group-add` allow the container access to the GPU.
|
||||
|
||||
```shell
|
||||
docker run --rm --device=/dev/dri --device=/dev/kfd --group-add video \
|
||||
-v /path/to/models:/models -v /path/to/output/:/output \
|
||||
sd [args...]
|
||||
# For example
|
||||
# docker run --rm --device=/dev/dri --device=/dev/kfd --group-add video \
|
||||
# -v ./models:/models -v ./build:/output \
|
||||
# sd -m /models/sd-v1-4.ckpt -v -o /output/output.png \
|
||||
# -p "a lovely cat"
|
||||
```
|
||||
+2
-2
@@ -21,7 +21,7 @@ You can run ERNIE-Image with stable-diffusion.cpp on GPUs with 4GB of VRAM — o
|
||||
### ERNIE-Image-Turbo
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\ernie-image-turbo.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\ministral-3-3b.safetensors -p "a lovely cat" --cfg-scale 1.0 --steps 8 -v --offload-to-cpu --diffusion-fa
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\ernie-image-turbo.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\ministral-3-3b.safetensors -p "a lovely cat" --cfg-scale 1.0 --steps 8 -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img width="256" alt="ERNIE-Image Turbo example" src="../assets/ernie_image/turbo_example.png" />
|
||||
@@ -29,7 +29,7 @@ You can run ERNIE-Image with stable-diffusion.cpp on GPUs with 4GB of VRAM — o
|
||||
### ERNIE-Image
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\ernie-image-UD-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\ministral-3-3b.safetensors -p "a lovely cat" --cfg-scale 5.0 -v --offload-to-cpu --diffusion-fa
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\ernie-image-UD-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\ministral-3-3b.safetensors -p "a lovely cat" --cfg-scale 5.0 -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img width="256" alt="ERNIE-Image example" src="../assets/ernie_image/example.png" />
|
||||
|
||||
+3
-1
@@ -2,7 +2,9 @@
|
||||
|
||||
You can use ESRGAN—such as the model [RealESRGAN_x4plus_anime_6B.pth](https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth)—to upscale the generated images and improve their overall resolution and clarity.
|
||||
|
||||
RGBA images, including Qwen Image 2.1 output, keep their alpha channel during model upscaling and hires fix. ESRGAN processes the RGB channels; the alpha channel is resized with bilinear interpolation and recombined with the upscaled image.
|
||||
MoSR models with the PixelShuffle upsampler, such as [4xNomos2_hq_mosr.pth](https://github.com/Phhofm/models/releases/download/4xNomos2_hq_mosr/4xNomos2_hq_mosr.pth), are also supported. The architecture is detected from the weights, so they are used the same way as ESRGAN models.
|
||||
|
||||
RGBA images, including Qwen Image 2.1 output, keep their alpha channel during model upscaling and hires fix. The upscaler processes the RGB channels; the alpha channel is resized with bilinear interpolation and recombined with the upscaled image.
|
||||
|
||||
- Specify the model path using the `--upscale-model PATH` parameter. example:
|
||||
|
||||
|
||||
+7
-7
@@ -16,7 +16,7 @@
|
||||
### Examples
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux2-dev-Q4_K_S.gguf --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\Mistral-Small-3.2-24B-Instruct-2506-Q4_K_M.gguf -r .\kontext_input.png -p "change 'flux.cpp' to 'flux2-dev.cpp'" --cfg-scale 1.0 --sampling-method euler -v --diffusion-fa --offload-to-cpu
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux2-dev-Q4_K_S.gguf --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\Mistral-Small-3.2-24B-Instruct-2506-Q4_K_M.gguf -r .\kontext_input.png -p "change 'flux.cpp' to 'flux2-dev.cpp'" --cfg-scale 1.0 --sampling-method euler -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img alt="flux2 example" src="../assets/flux2/example.png" />
|
||||
@@ -42,19 +42,19 @@
|
||||
### Examples
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-4b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "a lovely cat" --cfg-scale 1.0 --steps 4 -v --offload-to-cpu --diffusion-fa
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-4b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "a lovely cat" --cfg-scale 1.0 --steps 4 -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img alt="flux2-klein-4b" src="../assets/flux2/flux2-klein-4b.png" />
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-4b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -r .\kontext_input.png -p "change 'flux.cpp' to 'klein.cpp'" --cfg-scale 1.0 --sampling-method euler -v --diffusion-fa --offload-to-cpu --steps 4
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-4b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -r .\kontext_input.png -p "change 'flux.cpp' to 'klein.cpp'" --cfg-scale 1.0 --sampling-method euler -v --offload-to-cpu --steps 4
|
||||
```
|
||||
|
||||
<img alt="flux2-klein-4b-edit" src="../assets/flux2/flux2-klein-4b-edit.png" />
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-base-4b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "a lovely cat" --cfg-scale 4.0 --steps 20 -v --offload-to-cpu --diffusion-fa
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-base-4b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "a lovely cat" --cfg-scale 4.0 --steps 20 -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img alt="flux2-klein-base-4b" src="../assets/flux2/flux2-klein-base-4b.png" />
|
||||
@@ -78,19 +78,19 @@
|
||||
### Examples
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-9b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_8b.safetensors -p "a lovely cat" --cfg-scale 1.0 --steps 4 -v --offload-to-cpu --diffusion-fa
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-9b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_8b.safetensors -p "a lovely cat" --cfg-scale 1.0 --steps 4 -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img alt="flux2-klein-9b" src="../assets/flux2/flux2-klein-9b.png" />
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-9b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_8b.safetensors -r .\kontext_input.png -p "change 'flux.cpp' to 'klein.cpp'" --cfg-scale 1.0 --sampling-method euler -v --diffusion-fa --offload-to-cpu --steps 4
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-9b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_8b.safetensors -r .\kontext_input.png -p "change 'flux.cpp' to 'klein.cpp'" --cfg-scale 1.0 --sampling-method euler -v --offload-to-cpu --steps 4
|
||||
```
|
||||
|
||||
<img alt="flux2-klein-9b-edit" src="../assets/flux2/flux2-klein-9b-edit.png" />
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-base-9b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_8b.safetensors -p "a lovely cat" --cfg-scale 4.0 --steps 20 -v --offload-to-cpu --diffusion-fa
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-2-klein-base-9b.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\qwen_3_8b.safetensors -p "a lovely cat" --cfg-scale 4.0 --steps 20 -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img alt="flux2-klein-base-9b" src="../assets/flux2/flux2-klein-base-9b.png" />
|
||||
@@ -18,7 +18,7 @@ and ByT5 Small GlyphXL for glyph-aware text conditioning.
|
||||
## Text-to-video example
|
||||
|
||||
```shell
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\hunyuanvideo1.5_720p_t2v_fp16.safetensors --vae ..\models\vae\hunyuanvideo15_vae_fp16.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --t5xxl ..\models\text_encoders\byt5_small_glyphxl_fp16.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -W 1280 -H 720 --offload-to-cpu --diffusion-fa --video-frames 33 --vae-tiling
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\hunyuanvideo1.5_720p_t2v_fp16.safetensors --vae ..\models\vae\hunyuanvideo15_vae_fp16.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --t5xxl ..\models\text_encoders\byt5_small_glyphxl_fp16.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -W 1280 -H 720 --offload-to-cpu --video-frames 33 --vae-tiling
|
||||
```
|
||||
|
||||
<video src=../assets/hunyuan_video/hy1.5_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
+1
-1
@@ -49,7 +49,7 @@ If you want lower VRAM usage, you can change the quantization from q8_0 to a low
|
||||
## Examples
|
||||
|
||||
```sh
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ideogram4-Q8_0.gguf --uncond-diffusion-model ideogram4_uncond-Q8_0.gguf --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors -p '{"high_level_description":"A square 1024 x 1024 luxury fashion magazine cover featuring exactly one short chubby fluffy cat as the main model. The cat sits on a soft ivory studio floor, facing the viewer with a stylish calm expression, wearing tiny black sunglasses, a red silk scarf, and a small gold collar charm. In front of the cat on the floor is a wide horizontal luxury nameplate that clearly reads ideogram4.cpp. The whole design feels premium, fashionable, clean, and editorial.","style_description":{"aesthetics":"luxury fashion magazine cover, high-end pet couture campaign, minimalist editorial design, elegant studio photography, soft paper texture, refined typography, fashionable and polished","lighting":"Soft diffused studio lighting, gentle spotlight on the cat, subtle floor shadow, warm ivory highlights, clean separation between subject and background","photo":"high-resolution fashion editorial photography look, front-facing cat portrait, crisp fur details, glossy sunglasses, clear readable nameplate text, shallow depth of field","medium":"mixed media fashion photography and premium editorial graphic design","color_palette":["#F4EFE7","#111111","#D8B56D","#B73A3A","#FFFFFF","#8A7A6A"]},"compositional_deconstruction":{"canvas":"Square 1024 x 1024 canvas with a normal upright orientation. Do not rotate the poster or any text. Use a clean fashion magazine cover layout.","background":"Warm ivory studio backdrop with subtle paper grain, a soft spotlight gradient, faint floor shadow, and a few minimal gold editorial lines. The background is spacious, premium, and uncluttered.","layout":"Top center has a small elegant headline. Center area features one cat as the main fashion model. Lower foreground has a wide horizontal luxury nameplate placed on the floor in front of the cat. Bottom center has a small footer. All text is horizontal, upright, and readable left to right.","elements":[{"type":"text","desc":"Top center headline reading LOOK WHAT I FOUND in a refined high-fashion serif font. The headline is horizontal, centered, elegant, and secondary to the nameplate text."},{"type":"obj","desc":"Exactly one short chubby fluffy cat sitting in the center like a luxury fashion model. The cat has a large round head, compact body, short legs, soft detailed fur, expressive eyes, and a calm confident pose. The cat is cute and rounded, not tall, not stretched, not duplicated."},{"type":"obj","desc":"Tiny glossy black sunglasses worn naturally by the cat, slightly oversized but still showing the cat face clearly. The sunglasses add a chic fashion-editorial attitude."},{"type":"obj","desc":"A red silk scarf tied neatly around the cat neck, with soft folds and a couture feeling. The scarf must not cover the cat face or the nameplate."},{"type":"obj","desc":"A small gold collar charm or fashion accessory under the scarf, subtle and premium, adding a luxury campaign detail."},{"type":"obj","desc":"In the lower foreground, place a wide horizontal luxury nameplate on the floor in front of the cat. The nameplate is low, flat, landscape-oriented, much wider than tall, like a fashion show seat card or premium display plaque. It is centered, front-facing, level, and fully visible. It must not become vertical, tall, standing, rotated, or side-facing."},{"type":"text","desc":"Print the exact text ideogram4.cpp only on the wide horizontal nameplate. Use clean bold black lettering, perfectly spelled, lowercase, with the number 4 and .cpp extension. The text must fit completely inside the nameplate, stay horizontal, and be readable from left to right."},{"type":"obj","desc":"Add sparse premium editorial accents around the edges: thin gold lines, small code brackets, tiny cursor marks, subtle dots, and minimal geometric details. No extra cats, no stickers, no animal faces, no busy decorations."},{"type":"text","desc":"Bottom center footer reading tiny paws, big compile energy in a small refined monospace or editorial font. The footer is horizontal, centered, understated, and much smaller than the nameplate text."}]}}' --diffusion-fa -v --offload-to-cpu -H 1024 -W 1024
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ideogram4-Q8_0.gguf --uncond-diffusion-model ideogram4_uncond-Q8_0.gguf --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors -p '{"high_level_description":"A square 1024 x 1024 luxury fashion magazine cover featuring exactly one short chubby fluffy cat as the main model. The cat sits on a soft ivory studio floor, facing the viewer with a stylish calm expression, wearing tiny black sunglasses, a red silk scarf, and a small gold collar charm. In front of the cat on the floor is a wide horizontal luxury nameplate that clearly reads ideogram4.cpp. The whole design feels premium, fashionable, clean, and editorial.","style_description":{"aesthetics":"luxury fashion magazine cover, high-end pet couture campaign, minimalist editorial design, elegant studio photography, soft paper texture, refined typography, fashionable and polished","lighting":"Soft diffused studio lighting, gentle spotlight on the cat, subtle floor shadow, warm ivory highlights, clean separation between subject and background","photo":"high-resolution fashion editorial photography look, front-facing cat portrait, crisp fur details, glossy sunglasses, clear readable nameplate text, shallow depth of field","medium":"mixed media fashion photography and premium editorial graphic design","color_palette":["#F4EFE7","#111111","#D8B56D","#B73A3A","#FFFFFF","#8A7A6A"]},"compositional_deconstruction":{"canvas":"Square 1024 x 1024 canvas with a normal upright orientation. Do not rotate the poster or any text. Use a clean fashion magazine cover layout.","background":"Warm ivory studio backdrop with subtle paper grain, a soft spotlight gradient, faint floor shadow, and a few minimal gold editorial lines. The background is spacious, premium, and uncluttered.","layout":"Top center has a small elegant headline. Center area features one cat as the main fashion model. Lower foreground has a wide horizontal luxury nameplate placed on the floor in front of the cat. Bottom center has a small footer. All text is horizontal, upright, and readable left to right.","elements":[{"type":"text","desc":"Top center headline reading LOOK WHAT I FOUND in a refined high-fashion serif font. The headline is horizontal, centered, elegant, and secondary to the nameplate text."},{"type":"obj","desc":"Exactly one short chubby fluffy cat sitting in the center like a luxury fashion model. The cat has a large round head, compact body, short legs, soft detailed fur, expressive eyes, and a calm confident pose. The cat is cute and rounded, not tall, not stretched, not duplicated."},{"type":"obj","desc":"Tiny glossy black sunglasses worn naturally by the cat, slightly oversized but still showing the cat face clearly. The sunglasses add a chic fashion-editorial attitude."},{"type":"obj","desc":"A red silk scarf tied neatly around the cat neck, with soft folds and a couture feeling. The scarf must not cover the cat face or the nameplate."},{"type":"obj","desc":"A small gold collar charm or fashion accessory under the scarf, subtle and premium, adding a luxury campaign detail."},{"type":"obj","desc":"In the lower foreground, place a wide horizontal luxury nameplate on the floor in front of the cat. The nameplate is low, flat, landscape-oriented, much wider than tall, like a fashion show seat card or premium display plaque. It is centered, front-facing, level, and fully visible. It must not become vertical, tall, standing, rotated, or side-facing."},{"type":"text","desc":"Print the exact text ideogram4.cpp only on the wide horizontal nameplate. Use clean bold black lettering, perfectly spelled, lowercase, with the number 4 and .cpp extension. The text must fit completely inside the nameplate, stay horizontal, and be readable from left to right."},{"type":"obj","desc":"Add sparse premium editorial accents around the edges: thin gold lines, small code brackets, tiny cursor marks, subtle dots, and minimal geometric details. No extra cats, no stickers, no animal faces, no busy decorations."},{"type":"text","desc":"Bottom center footer reading tiny paws, big compile energy in a small refined monospace or editorial font. The footer is horizontal, centered, understated, and much smaller than the nameplate text."}]}}' -v --offload-to-cpu -H 1024 -W 1024
|
||||
```
|
||||
|
||||
<img alt="ideogram4 image example" src="../assets/ideogram4/example.png" />
|
||||
|
||||
@@ -84,22 +84,23 @@ The floating-point output is reconstructed as
|
||||
Y[r, o] ~= A[r, o] * s_x[r] * s_w[o] + b[o]
|
||||
```
|
||||
|
||||
The packed runtime activation tensor contains the I8 activation rows and their floating-point row scales. Linear layers that share the same input and convrot group size reuse this packed tensor, avoiding repeated rotation and activation quantization within the graph.
|
||||
The packed runtime activation tensor contains the I8 activation rows and their floating-point row scales. Linear layers with an input scale of `1` that share the same input and convrot group size reuse this packed tensor, avoiding repeated rotation and activation quantization within the graph. For other input scales, activations are scaled before explicit convrot quantization, and the linear output is unscaled before adding bias.
|
||||
|
||||
## Backend support
|
||||
|
||||
- CPU provides the portable regular Hadamard, activation quantization, INT8 matrix multiplication, and scale restoration implementations.
|
||||
- NVIDIA CUDA devices with compute capability 7.5 or newer use the native accelerated path. For H256, CUDA fuses the rotation, row-wise maximum reduction, and activation quantization. It uses cuBLAS for I8 x I8 to I32 GEMM and a CUDA kernel for scale restoration and bias addition.
|
||||
- Vulkan and other GPU backends do not currently have dedicated INT8 convrot kernels. They use the backend scheduler to fall back to CPU, which is expected to be substantially slower than the CUDA path.
|
||||
- AMD HIP devices in the CDNA, RDNA3 (including RDNA3.5), and RDNA4 families use the same INT8/H256 kernels with hipBLAS for I8 x I8 to I32 GEMM. Other AMD architectures fall back to CPU for INT8 matrix multiplication.
|
||||
- Vulkan provides native H256 activation quantization and INT8 matrix multiplication when the build and device support accelerated packed INT8 dot products. Unsupported configurations and GPU backends without these kernels use the backend scheduler to fall back to CPU.
|
||||
|
||||
LoRA adapters are applied at runtime without modifying the INT8 weights. The INT8 convrot path computes the base linear output, while LoRA, LoHa, LoKr, and raw weight-difference adapters compute their output corrections from the original, unrotated activation and add them to the base output. `--lora-apply-mode auto` selects this path for models containing INT8 tensorwise weights. If `immediately` is requested, sd.cpp falls back to runtime application because merging an adapter would require dequantizing and rotating its weight update, then recalculating the per-row scales and requantizing the result.
|
||||
|
||||
The dedicated CUDA convrot activation path currently requires a group size of `256`; other supported group sizes use CPU execution.
|
||||
The dedicated CUDA and HIP convrot activation paths currently require a group size of `256`; other supported group sizes use CPU execution.
|
||||
|
||||
## Example
|
||||
|
||||
ComfyUI INT8 convrot safetensors can be passed to `--diffusion-model` without conversion:
|
||||
|
||||
```powershell
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\krea2_turbo_int8_convrot.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\wan_2.1_vae.safetensors -p "a lovely cat holding a sign says 'krea2.cpp'" --steps 8 --cfg-scale 1 --diffusion-fa -v --offload-to-cpu
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\krea2_turbo_int8_convrot.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\wan_2.1_vae.safetensors -p "a lovely cat holding a sign says 'krea2.cpp'" --steps 8 --cfg-scale 1 -v --offload-to-cpu
|
||||
```
|
||||
|
||||
+2
-2
@@ -48,7 +48,7 @@ sd-cli -m ..\models\sd_v1.5.safetensors --clip_vision ..\models\clip_vision_h.sa
|
||||
## Example (SDXL)
|
||||
|
||||
```
|
||||
sd-cli -m ..\models\sdxl.safetensors --clip_vision ..\models\clip_vision_h.safetensors --ip-adapter ..\models\ip-adapter_sdxl_vit-h.safetensors --ip-adapter-image ..\assets\reference.png --ip-adapter-strength 0.8 -p "a woman, best quality" -n "lowres, bad anatomy" --cfg-scale 6 --steps 25 --sampling-method dpm++2m --scheduler karras -W 1024 -H 1024 --diffusion-fa --vae-tiling
|
||||
sd-cli -m ..\models\sdxl.safetensors --clip_vision ..\models\clip_vision_h.safetensors --ip-adapter ..\models\ip-adapter_sdxl_vit-h.safetensors --ip-adapter-image ..\assets\reference.png --ip-adapter-strength 0.8 -p "a woman, best quality" -n "lowres, bad anatomy" --cfg-scale 6 --steps 25 --sampling-method dpm++2m --scheduler karras -W 1024 -H 1024 --vae-tiling
|
||||
```
|
||||
|
||||
The SDXL VAE decode at 1024x1024 is memory heavy; add `--vae-tiling` (and
|
||||
@@ -83,5 +83,5 @@ Add the usual Control Net options to keep the reference appearance while
|
||||
controlling the pose:
|
||||
|
||||
```
|
||||
sd-cli -m ..\models\sdxl.safetensors --clip_vision ..\models\clip_vision_h.safetensors --ip-adapter ..\models\ip-adapter_sdxl_vit-h.safetensors --ip-adapter-image ..\assets\character.png --ip-adapter-strength 0.9 --control-net ..\models\OpenPoseXL2.safetensors --control-image ..\assets\pose.png --control-strength 0.8 -p "a character, side view" --cfg-scale 6 --steps 25 -W 1024 -H 1024 --diffusion-fa --vae-tiling
|
||||
sd-cli -m ..\models\sdxl.safetensors --clip_vision ..\models\clip_vision_h.safetensors --ip-adapter ..\models\ip-adapter_sdxl_vit-h.safetensors --ip-adapter-image ..\assets\character.png --ip-adapter-strength 0.9 --control-net ..\models\OpenPoseXL2.safetensors --control-image ..\assets\pose.png --control-strength 0.8 -p "a character, side view" --cfg-scale 6 --steps 25 -W 1024 -H 1024 --vae-tiling
|
||||
```
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# How to Use
|
||||
|
||||
Iris-3B generates images directly in pixel space and uses Qwen3-VL-4B-Instruct as the text encoder. No VAE is required.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download Iris-3B
|
||||
- safetensors: https://huggingface.co/speridlabs/iris-3b/tree/main (`model.safetensors` in the root directory)
|
||||
- Download Qwen3-VL-4B-Instruct
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Krea-2/tree/main/text_encoders
|
||||
- gguf: https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct-GGUF/tree/main
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\iris-3b.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf -p "a lovely cat" --cfg-scale 3 -H 1024 -W 1024
|
||||
```
|
||||
|
||||
<img width="256" alt="iris-3b example" src="../assets/iris/example.png" />
|
||||
|
||||
## Notes
|
||||
|
||||
- Width and height must be multiples of 16. Do not pass `--vae`.
|
||||
- Captions are limited to 300 tokens including the assistant-turn suffix. Positive and negative prompts use the same template.
|
||||
+1
-1
@@ -21,7 +21,7 @@ Krea2 uses a Krea2 diffusion transformer, the Wan2.1 VAE, and Qwen3-VL 4B as the
|
||||
### Krea2
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Krea-2-Raw-Q8_0.gguf --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\wan_2.1_vae.safetensors -p "a lovely cat holding a sign says 'krea2.cpp'" --diffusion-fa -v --offload-to-cpu
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Krea-2-Raw-Q8_0.gguf --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\wan_2.1_vae.safetensors -p "a lovely cat holding a sign says 'krea2.cpp'" -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img width="256" alt="Krea2 Raw example" src="../assets/krea2/example.png" />
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ Lens and Lens Turbo require an external GPT-OSS `tokenizer.json` matching the te
|
||||
### Lens
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\lens_bf16.safetensors --llm "..\models\text_encoders\gpt-oss-20b-UD-Q8_K_XL.gguf" --tokenizer ..\models\tokenizers\tokenizer_gpt_oss.json --vae ..\models\vae\flux2_ae.safetensors --cfg-scale 5.0 -p "A crystal dragon soaring through an aurora borealis sky, its entire body made of transparent faceted crystal refracting the green and purple aurora light into rainbow spectra, ice particles trailing from its wings, high fantasy digital art" --diffusion-fa -v
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\lens_bf16.safetensors --llm "..\models\text_encoders\gpt-oss-20b-UD-Q8_K_XL.gguf" --tokenizer ..\models\tokenizers\tokenizer_gpt_oss.json --vae ..\models\vae\flux2_ae.safetensors --cfg-scale 5.0 -p "A crystal dragon soaring through an aurora borealis sky, its entire body made of transparent faceted crystal refracting the green and purple aurora light into rainbow spectra, ice particles trailing from its wings, high fantasy digital art" -v
|
||||
```
|
||||
|
||||
<img width="256" alt="Lens example" src="../assets/lens/example.png" />
|
||||
@@ -30,7 +30,7 @@ Lens and Lens Turbo require an external GPT-OSS `tokenizer.json` matching the te
|
||||
### Lens Turbo
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\lens_turbo_bf16.safetensors --llm "..\models\text_encoders\gpt-oss-20b-UD-Q8_K_XL.gguf" --tokenizer ..\models\tokenizers\tokenizer_gpt_oss.json --vae ..\models\vae\flux2_ae.safetensors --cfg-scale 1.0 -p "A crystal dragon soaring through an aurora borealis sky, its entire body made of transparent faceted crystal refracting the green and purple aurora light into rainbow spectra, ice particles trailing from its wings, high fantasy digital art" --diffusion-fa -v --steps 4
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\lens_turbo_bf16.safetensors --llm "..\models\text_encoders\gpt-oss-20b-UD-Q8_K_XL.gguf" --tokenizer ..\models\tokenizers\tokenizer_gpt_oss.json --vae ..\models\vae\flux2_ae.safetensors --cfg-scale 1.0 -p "A crystal dragon soaring through an aurora borealis sky, its entire body made of transparent faceted crystal refracting the green and purple aurora light into rainbow spectra, ice particles trailing from its wings, high fantasy digital art" -v --steps 4
|
||||
```
|
||||
|
||||
<img width="256" alt="Lens Turbo example" src="../assets/lens/turbo_example.png" />
|
||||
|
||||
@@ -28,5 +28,5 @@ Lingbot Video uses a Lingbot diffusion transformer, the Wan2.1 VAE, and Qwen3-VL
|
||||
### LingBot-Video T2V
|
||||
|
||||
```sh
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\lingbot-video-dens-1.3b.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\wan_2.1_vae.safetensors -p '{"caption":{"comprehensive_description":"A lovely cat sits comfortably on a soft cushion near a sunlit window, looking calm, gentle, and adorable. The cat has soft fluffy fur, bright expressive eyes, small rounded ears, delicate whiskers, and a relaxed posture. Warm daylight falls across the cat from one side, creating soft highlights on the fur and a cozy glow around the scene. The background is softly blurred, showing hints of a peaceful indoor home environment with warm tones and gentle natural light. The overall atmosphere is cute, tender, serene, and photorealistic, emphasizing the cat''s charming appearance, soft texture, and affectionate presence.","camera_info":{"color":"Warm","frame_size":"Close Up","shot_type_angle":"Eye level","lens_size":"Medium Lens","composition":"Centered balanced","lighting":"Soft light","lighting_type":"Daylight"},"world_knowledge":[],"prominent_elements":[{"name":"lovely cat","description":"A cute and gentle domestic cat with soft fluffy fur, expressive eyes, and a calm relaxed presence.","location":"center of the frame","relative_size":"large","shape_and_color":"Small animal body with rounded face, triangular ears, bright eyes, and soft fur in warm natural tones","texture":"soft, fluffy, silky","appearance_details":"The cat has clean well-groomed fur, delicate whiskers, small ears, a cute nose, and bright attentive eyes. Its expression appears calm, affectionate, and slightly curious.","relationship":"Acts as the main subject and emotional focal point of the scene.","orientation":"facing the camera","pose":"sitting comfortably with a relaxed posture","expression":"gentle, adorable, calm, slightly curious","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"cat eyes","description":"Bright expressive eyes that give the cat a sweet and affectionate appearance.","location":"upper center of the cat face","relative_size":"small","shape_and_color":"Round almond-like eyes with glossy reflections","texture":"clear, glossy, reflective","appearance_details":"The eyes catch the soft daylight, creating small natural highlights that make the cat look vivid and alive.","relationship":"Enhance the emotional charm and cuteness of the cat.","orientation":"looking toward the camera","pose":"","expression":"soft and attentive","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"soft cushion","description":"A comfortable cushion or blanket where the cat is resting.","location":"bottom portion of the frame","relative_size":"medium","shape_and_color":"Soft rounded fabric surface in light warm neutral tones","texture":"plush, fabric, cozy","appearance_details":"The cushion gently supports the cat and adds a comfortable home-like feeling to the scene.","relationship":"Provides a cozy resting place for the cat.","orientation":"horizontal","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"sunlit indoor background","description":"A softly blurred indoor background with warm daylight and peaceful home atmosphere.","location":"behind the cat, filling the upper and side areas of the frame","relative_size":"large","shape_and_color":"Soft abstract shapes in warm beige, cream, and pale golden tones","texture":"soft, blurry, bokeh-like","appearance_details":"The background is intentionally out of focus, keeping attention on the cat while creating a cozy and serene mood.","relationship":"Provides a warm and gentle environment that supports the cute domestic scene.","orientation":"upright","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":"","is_cluster":true,"number_of_objects":"numerous"}]}}' -n '{"universal_negative":{"visual_quality":["low quality","worst quality","blurry","pixelated","jpeg artifacts","low resolution","unstable color","color flicker","underexposed","overexposed","invisible subject","subject hidden in darkness"],"artistic_style":["painting","illustration","drawing","cartoon","3d render","cgi","sketch","digital art"],"composition_and_content":["text","watermark","signature","logo","subtitles","pillarboxed","side bars","portrait image in landscape frame"],"temporal_and_motion_stability":["flickering","jittery","motion blur","temporal inconsistency","warping","morphing","incoherent motion","unnatural movement","static object with sudden jump","frame-to-frame inconsistency"],"material_and_structure":["plastic-like glass","unrealistic texture","deformed bottle","liquid freezing improperly","distorted reflections"]}}' --diffusion-fa --offload-to-cpu --cfg-scale 3 --video-frames 33 -v
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\lingbot-video-dens-1.3b.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\wan_2.1_vae.safetensors -p '{"caption":{"comprehensive_description":"A lovely cat sits comfortably on a soft cushion near a sunlit window, looking calm, gentle, and adorable. The cat has soft fluffy fur, bright expressive eyes, small rounded ears, delicate whiskers, and a relaxed posture. Warm daylight falls across the cat from one side, creating soft highlights on the fur and a cozy glow around the scene. The background is softly blurred, showing hints of a peaceful indoor home environment with warm tones and gentle natural light. The overall atmosphere is cute, tender, serene, and photorealistic, emphasizing the cat''s charming appearance, soft texture, and affectionate presence.","camera_info":{"color":"Warm","frame_size":"Close Up","shot_type_angle":"Eye level","lens_size":"Medium Lens","composition":"Centered balanced","lighting":"Soft light","lighting_type":"Daylight"},"world_knowledge":[],"prominent_elements":[{"name":"lovely cat","description":"A cute and gentle domestic cat with soft fluffy fur, expressive eyes, and a calm relaxed presence.","location":"center of the frame","relative_size":"large","shape_and_color":"Small animal body with rounded face, triangular ears, bright eyes, and soft fur in warm natural tones","texture":"soft, fluffy, silky","appearance_details":"The cat has clean well-groomed fur, delicate whiskers, small ears, a cute nose, and bright attentive eyes. Its expression appears calm, affectionate, and slightly curious.","relationship":"Acts as the main subject and emotional focal point of the scene.","orientation":"facing the camera","pose":"sitting comfortably with a relaxed posture","expression":"gentle, adorable, calm, slightly curious","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"cat eyes","description":"Bright expressive eyes that give the cat a sweet and affectionate appearance.","location":"upper center of the cat face","relative_size":"small","shape_and_color":"Round almond-like eyes with glossy reflections","texture":"clear, glossy, reflective","appearance_details":"The eyes catch the soft daylight, creating small natural highlights that make the cat look vivid and alive.","relationship":"Enhance the emotional charm and cuteness of the cat.","orientation":"looking toward the camera","pose":"","expression":"soft and attentive","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"soft cushion","description":"A comfortable cushion or blanket where the cat is resting.","location":"bottom portion of the frame","relative_size":"medium","shape_and_color":"Soft rounded fabric surface in light warm neutral tones","texture":"plush, fabric, cozy","appearance_details":"The cushion gently supports the cat and adds a comfortable home-like feeling to the scene.","relationship":"Provides a cozy resting place for the cat.","orientation":"horizontal","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"sunlit indoor background","description":"A softly blurred indoor background with warm daylight and peaceful home atmosphere.","location":"behind the cat, filling the upper and side areas of the frame","relative_size":"large","shape_and_color":"Soft abstract shapes in warm beige, cream, and pale golden tones","texture":"soft, blurry, bokeh-like","appearance_details":"The background is intentionally out of focus, keeping attention on the cat while creating a cozy and serene mood.","relationship":"Provides a warm and gentle environment that supports the cute domestic scene.","orientation":"upright","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":"","is_cluster":true,"number_of_objects":"numerous"}]}}' -n '{"universal_negative":{"visual_quality":["low quality","worst quality","blurry","pixelated","jpeg artifacts","low resolution","unstable color","color flicker","underexposed","overexposed","invisible subject","subject hidden in darkness"],"artistic_style":["painting","illustration","drawing","cartoon","3d render","cgi","sketch","digital art"],"composition_and_content":["text","watermark","signature","logo","subtitles","pillarboxed","side bars","portrait image in landscape frame"],"temporal_and_motion_stability":["flickering","jittery","motion blur","temporal inconsistency","warping","morphing","incoherent motion","unnatural movement","static object with sudden jump","frame-to-frame inconsistency"],"material_and_structure":["plastic-like glass","unrealistic texture","deformed bottle","liquid freezing improperly","distorted reflections"]}}' --offload-to-cpu --cfg-scale 3 --video-frames 33 -v
|
||||
```
|
||||
|
||||
+1
-2
@@ -122,7 +122,6 @@ save_file(merged, "llada_connectors.safetensors")
|
||||
--height 1024 \
|
||||
--steps 4 \
|
||||
--cfg-scale 1.0 \
|
||||
--diffusion-fa \
|
||||
--output output.png
|
||||
```
|
||||
|
||||
@@ -145,7 +144,7 @@ only in the positive branch. Editing requires connectors that include the SigVQ
|
||||
unchanged instead of applying the instruction; LLaDA-Image-Turbo edits correctly at both.
|
||||
- Editing runs the reference and the target in one sequence, so it needs roughly twice the
|
||||
tokens of text to image at the same size. On 12 GB, editing at 1024x1024 needs
|
||||
`--diffusion-fa`; without it the diffusion graph does not fit.
|
||||
flash attention; without it the diffusion graph does not fit.
|
||||
- The weights total about 16 GB, but segmented execution streams them, so a much smaller
|
||||
budget works. At 512x512, `--max-vram 6` costs almost nothing over unconstrained execution,
|
||||
and `--max-vram 3` still produces byte-identical output at roughly 2.5x the time.
|
||||
|
||||
@@ -24,7 +24,7 @@ LongCat uses quoted text for character-level text rendering. Put target text ins
|
||||
### LongCat Image
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\LongCat-Image-Q4_K_M.gguf --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\Qwen2.5-VL-7B-Instruct-Q8_0.gguf -p "a lovely cat holding a sign says 'longcat.cpp'" --cfg-scale 5.0 --sampling-method euler --flow-shift 3 -v --offload-to-cpu --diffusion-fa
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\LongCat-Image-Q4_K_M.gguf --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\Qwen2.5-VL-7B-Instruct-Q8_0.gguf -p "a lovely cat holding a sign says 'longcat.cpp'" --cfg-scale 5.0 --sampling-method euler --flow-shift 3 -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img alt="longcat example" src="../assets/longcat/example.png" />
|
||||
|
||||
+7
-7
@@ -55,7 +55,7 @@ To run the text encoder quantized, convert it once with sd-cli:
|
||||
### LTX-2.3 dev T2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "worst quality, low quality, blurry, distorted, artifacts" -W 1280 -H 720 --diffusion-fa --offload-to-cpu --video-frames 33 --fps 24 -o t2v.webm
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "worst quality, low quality, blurry, distorted, artifacts" -W 1280 -H 720 --offload-to-cpu --video-frames 33 --fps 24 -o t2v.webm
|
||||
```
|
||||
|
||||
<video
|
||||
@@ -67,7 +67,7 @@ To run the text encoder quantized, convert it once with sd-cli:
|
||||
### LTX-2.3 dev I2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -W 1280 -H 720 --diffusion-fa --offload-to-cpu --video-frames 33 -i ..\assets\ernie_image\turbo_example.png -o i2v.webm
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -W 1280 -H 720 --offload-to-cpu --video-frames 33 -i ..\assets\ernie_image\turbo_example.png -o i2v.webm
|
||||
```
|
||||
|
||||
<video
|
||||
@@ -79,7 +79,7 @@ To run the text encoder quantized, convert it once with sd-cli:
|
||||
### LTX-2.3 dev FLF2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors -p "glass flower blossom" --cfg-scale 6.0 --sampling-method euler -v -W 1280 -H 720 --diffusion-fa --offload-to-cpu --video-frames 33 --init-img ..\..\ComfyUI\input\start_image.png --end-img ..\..\ComfyUI\input\end_image.png -o flf2v.webm
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors -p "glass flower blossom" --cfg-scale 6.0 --sampling-method euler -v -W 1280 -H 720 --offload-to-cpu --video-frames 33 --init-img ..\..\ComfyUI\input\start_image.png --end-img ..\..\ComfyUI\input\end_image.png -o flf2v.webm
|
||||
```
|
||||
|
||||
<video
|
||||
@@ -95,7 +95,7 @@ LTX spatial latent upscale runs a model-backed x2 latent upsampler between the l
|
||||
Put `ltx-2.3-spatial-upscaler-x2-1.1.safetensors` under the directory passed to `--hires-upscalers-dir`, then use the model name without path or extension in `--hires-upscaler`.
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors --hires-upscalers-dir ..\models\latent_upscale_models --hires-upscaler ltx-2.3-spatial-upscaler-x2-1.1 --hires --hires-steps 4 -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -W 640 -H 360 --diffusion-fa --offload-to-cpu --video-frames 33 -i ..\assets\ernie_image\turbo_example.png -o hires_i2v.webm
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.3-22b-dev-UD-Q4_K_M.gguf --vae ..\models\vae\ltx-2.3-22b-dev_video_vae.safetensors --audio-vae ..\models\vae\ltx-2.3-22b-dev_audio_vae.safetensors --llm ..\models\text_encoders\gemma-3-12b-it-qat-UD-Q4_K_XL.gguf --embeddings-connectors ..\models\text_encoders\ltx-2.3-22b-dev_embeddings_connectors.safetensors --hires-upscalers-dir ..\models\latent_upscale_models --hires-upscaler ltx-2.3-spatial-upscaler-x2-1.1 --hires --hires-steps 4 -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -W 640 -H 360 --offload-to-cpu --video-frames 33 -i ..\assets\ernie_image\turbo_example.png -o hires_i2v.webm
|
||||
```
|
||||
|
||||
By default, the hires refine pass uses the main sampler and scheduler, then trims the second-pass sigma schedule by `--hires-denoising-strength` (`0.7` by default). To reproduce a ComfyUI-style explicit refine schedule, pass custom hires sigmas:
|
||||
@@ -113,13 +113,13 @@ By default, the hires refine pass uses the main sampler and scheduler, then trim
|
||||
### LTX-2.5 dev T2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf -p "A wide aerial shot of a red vintage convertible driving along a coastal cliff road at sunset, waves crashing below" --cfg-scale 3.0 --sampling-method euler -v -n "worst quality, low quality, blurry, distorted, artifacts" -W 1280 -H 720 --diffusion-fa --offload-to-cpu --video-frames 121 --fps 24 -o t2v.webm
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf -p "A wide aerial shot of a red vintage convertible driving along a coastal cliff road at sunset, waves crashing below" --cfg-scale 3.0 --sampling-method euler -v -n "worst quality, low quality, blurry, distorted, artifacts" -W 1280 -H 720 --offload-to-cpu --video-frames 121 --fps 24 -o t2v.webm
|
||||
```
|
||||
|
||||
### LTX-2.5 dev I2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf -p "a lovely cat blinking slowly, gentle camera push in" --cfg-scale 3.0 --sampling-method euler -v -W 1280 -H 720 --diffusion-fa --offload-to-cpu --video-frames 121 -i ..\assets\ernie_image\turbo_example.png -o i2v.webm
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf -p "a lovely cat blinking slowly, gentle camera push in" --cfg-scale 3.0 --sampling-method euler -v -W 1280 -H 720 --offload-to-cpu --video-frames 121 -i ..\assets\ernie_image\turbo_example.png -o i2v.webm
|
||||
```
|
||||
|
||||
### LTX-2.5 spatial latent upscale
|
||||
@@ -129,7 +129,7 @@ Works exactly like the LTX-2.3 upscaler described below; put
|
||||
pass its name without path or extension to `--hires-upscaler`.
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf --hires-upscalers-dir ..\models\latent_upscale_models --hires-upscaler ltx-2.5-latent-spatial-upscaler-x2-bf16-1.0 --hires --hires-steps 6 -p "a lovely cat" --cfg-scale 3.0 --sampling-method euler -v -W 640 -H 360 --diffusion-fa --offload-to-cpu --video-frames 121 -o hires_t2v.webm
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\ltx-2.5-22b-dev-transformer-Q8_0.gguf --vae ..\models\vae\ltx-2.5-video-vae-conv-bf16.safetensors --audio-vae ..\models\vae\ltx-2.5-audio-vae-bf16.safetensors --llm ..\models\text_encoders\gemma4-12b-with-proj-ltx-2.5-Q8_0.gguf --hires-upscalers-dir ..\models\latent_upscale_models --hires-upscaler ltx-2.5-latent-spatial-upscaler-x2-bf16-1.0 --hires --hires-steps 6 -p "a lovely cat" --cfg-scale 3.0 --sampling-method euler -v -W 640 -H 360 --offload-to-cpu --video-frames 121 -o hires_t2v.webm
|
||||
```
|
||||
|
||||
## Not implemented
|
||||
|
||||
+4
-16
@@ -4,20 +4,8 @@
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download Mage-Flow
|
||||
- safetensors: https://huggingface.co/microsoft/Mage-Flow/tree/main/transformer
|
||||
- Download Mage-Flow-Base
|
||||
- safetensors: https://huggingface.co/microsoft/Mage-Flow-Base/tree/main/transformer
|
||||
- Download Mage-Flow-Turbo
|
||||
- safetensors: https://huggingface.co/microsoft/Mage-Flow-Turbo/tree/main/transformer
|
||||
- Download Mage-Flow-Edit
|
||||
- safetensors: https://huggingface.co/microsoft/Mage-Flow-Edit/tree/main/transformer
|
||||
- Download Mage-Flow-Edit-Turbo
|
||||
- safetensors: https://huggingface.co/microsoft/Mage-Flow-Edit-Turbo/tree/main/transformer
|
||||
- Download Mage-Flow-Edit-Base
|
||||
- safetensors: https://huggingface.co/microsoft/Mage-Flow-Edit-Base/tree/main/transformer
|
||||
- Download Mage-Flow vae
|
||||
- safetensors: https://huggingface.co/microsoft/Mage-Flow/tree/main/vae
|
||||
- Download Mage-Flow diffusion from https://huggingface.co/Comfy-Org/Mage-Flow/tree/main/diffusion_models
|
||||
- Download Mage-Flow vae from https://huggingface.co/Comfy-Org/Mage-Flow/tree/main/vae
|
||||
- Download Qwen3-VL 4B
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Krea-2/tree/main/text_encoders
|
||||
- gguf: https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct-GGUF/tree/main
|
||||
@@ -27,7 +15,7 @@
|
||||
Use 30 steps for Base models and 4 steps with `--cfg-scale 1` for Turbo models. Image dimensions must be multiples of 16; the official checkpoints are trained for native resolutions from 512 to 2048 pixels.
|
||||
|
||||
```bash
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Mage-Flow-Turbo.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\mage_vae.safetensors -p "a lovely cat holding a sign says 'mage.cpp'" --cfg-scale 1.0 --steps 4 --diffusion-fa -v --offload-to-cpu
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Mage-Flow-Turbo.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\mage_vae.safetensors -p "a lovely cat holding a sign says 'mage.cpp'" --cfg-scale 1.0 --steps 4 -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img width="256" alt="Mage-Flow example" src="../assets/mage_flow/example.png" />
|
||||
@@ -39,7 +27,7 @@ Mage-Flow-Edit accepts one or more reference images. The default `mage_flow` ref
|
||||
For the Turbo edit checkpoint, use 4 steps and `--cfg-scale 1`.
|
||||
|
||||
```bash
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Mage-Flow-Edit.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --llm_vision ..\models\text_encoders\Qwen3-VL-4B-Instruct-mmproj-BF16.gguf --vae ..\models\vae\mage_vae.safetensors -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'mage.cpp'" --cfg-scale 4.0 --sampling-method euler -v --diffusion-fa --offload-to-cpu
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Mage-Flow-Edit.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --llm_vision ..\models\text_encoders\Qwen3-VL-4B-Instruct-mmproj-BF16.gguf --vae ..\models\vae\mage_vae.safetensors -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'mage.cpp'" --cfg-scale 4.0 --sampling-method euler -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img width="256" alt="Mage-Flow-Edit example" src="../assets/mage_flow/edit_example.png" />
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Ming-Image
|
||||
|
||||
[Ming-Image](https://github.com/inclusionAI/Ming-Image) 0.1 Design uses a 6B diffusion transformer (DiT), Ling-mini-2.0 for text conditioning, and the Ming-Image VAE. Text-to-image generation with RGBA output is supported.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download Ming-Image 0.1 Design DiT
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Ming-Image/tree/main/diffusion_models
|
||||
- Download Ling-mini-2.0 BF16
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Ming-Image/tree/main/text_encoders
|
||||
- Download Ming-Image VAE
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Ming-Image/tree/main/vae
|
||||
- Download Ling tokenizer
|
||||
- tokenizer.json: https://huggingface.co/inclusionAI/Ming-Image-0.1-Design/blob/main/mllm/tokenizer.json
|
||||
|
||||
The example below uses `ming_image_0.1_design_bf16.safetensors` for the DiT. You can also use `ming_image_0.1_design_int8_convrot.safetensors` with [INT8 convrot support](int8_convrot.md). Use the BF16 text encoder.
|
||||
|
||||
## Text-to-image
|
||||
|
||||
Pass the Ling `tokenizer.json` with `--tokenizer` and use the matching Ming-Image VAE. Image dimensions must be multiples of 16. Save the output as PNG to preserve the alpha channel.
|
||||
|
||||
```bash
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\ming_image_0.1_design_bf16.safetensors --llm ..\models\text_encoders\ming_image_0.1_ling_mini_2.0_bf16.safetensors --vae ..\models\vae\ming_image_vae_bf16.safetensors --tokenizer ..\models\text_encoders\tokenizer.json -p "A cheerful orange cat sticker, transparent background" --width 1024 --height 1024 --steps 12 --cfg-scale 1 --sampling-method euler -v --offload-to-cpu -o ming_image.png
|
||||
```
|
||||
+3
-3
@@ -38,7 +38,7 @@ are detected from their weights.
|
||||
## Text-to-audio-video
|
||||
|
||||
```sh
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\minimax_h3_fl2va-Q4_K_M.gguf --vae ..\models\vae\minimax_h3_video_vae_fp16.safetensors --audio-vae ..\models\vae\minimax_h3_audio_vae_fp32.safetensors --llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf -p "A cute American Shorthair silver tabby kitten surfs on a tropical ocean wave, riding a white surfboard with the clear text 'sd.cpp' on it. Cinematic tracking shot, realistic water, bright sunlight, smooth motion, and consistent character appearance. Add upbeat tropical surf-rock background music with cheerful drums and guitar, synchronized with the kitten’s energetic surfing." --cfg-scale 1.0 -v -W 864 -H 480 --diffusion-fa --offload-to-cpu --rng cpu --fps 24 --video-frames 56
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\minimax_h3_fl2va-Q4_K_M.gguf --vae ..\models\vae\minimax_h3_video_vae_fp16.safetensors --audio-vae ..\models\vae\minimax_h3_audio_vae_fp32.safetensors --llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf -p "A cute American Shorthair silver tabby kitten surfs on a tropical ocean wave, riding a white surfboard with the clear text 'sd.cpp' on it. Cinematic tracking shot, realistic water, bright sunlight, smooth motion, and consistent character appearance. Add upbeat tropical surf-rock background music with cheerful drums and guitar, synchronized with the kitten’s energetic surfing." --cfg-scale 1.0 -v -W 864 -H 480 --offload-to-cpu --rng cpu --fps 24 --video-frames 56
|
||||
```
|
||||
|
||||
<video src=../assets/minimax-h3/t2av.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -51,7 +51,7 @@ decoded audio track.
|
||||
Add `--init-img` for I2VA, or both `--init-img` and `--end-img` for FL2VA:
|
||||
|
||||
```sh
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\minimax_h3_fl2va-Q4_K_M.gguf --vae ..\models\vae\minimax_h3_video_vae_fp16.safetensors --audio-vae ..\models\vae\minimax_h3_audio_vae_fp32.safetensors --llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf -p "a lovely cat" -i ..\assets\ernie_image\turbo_example.png --cfg-scale 1.0 -v -W 864 -H 480 --diffusion-fa --offload-to-cpu --rng cpu --fps 24 --video-frames 56
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\minimax_h3_fl2va-Q4_K_M.gguf --vae ..\models\vae\minimax_h3_video_vae_fp16.safetensors --audio-vae ..\models\vae\minimax_h3_audio_vae_fp32.safetensors --llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf -p "a lovely cat" -i ..\assets\ernie_image\turbo_example.png --cfg-scale 1.0 -v -W 864 -H 480 --offload-to-cpu --rng cpu --fps 24 --video-frames 56
|
||||
```
|
||||
|
||||
<video src=../assets/minimax-h3/i2av.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -62,7 +62,7 @@ Ref2VA accepts any combination of reference images, reference videos, paired
|
||||
video soundtracks, and standalone audio references:
|
||||
|
||||
```sh
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\minimax_h3_ref2va_pruned-Q4_K_M.gguf --vae ..\models\vae\minimax_h3_video_vae_fp16.safetensors --audio-vae ..\models\vae\minimax_h3_audio_vae_fp32.safetensors --llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf -p "Use the cat from <Picture 1> as the main character. Keep the cat’s appearance, fur color, facial features, and identity consistent with the reference image. Create a 2-second cinematic video: start with an extreme close-up shot of the cat’s face, focusing on its cute expression and detailed fur texture. The camera slowly rotates around the cat’s head, creating a dynamic reveal. Then smoothly pull back and zoom out to reveal the full scene: the cat is standing confidently on a surfboard, riding ocean waves. Water splashes around the board, sea breeze gently moves the cat’s fur, and the cat maintains a cute and fearless expression while surfing. Smooth camera movement, cinematic orbit shot, seamless zoom-out transition, low-angle wide shot, realistic ocean environment, golden sunlight, dynamic waves, high-quality realistic style, natural motion, no distortion, keep the cat’s identity unchanged." -r ..\assets\ernie_image\turbo_example.png --cfg-scale 1.0 -v -W 864 -H 480 --diffusion-fa --offload-to-cpu --rng cpu --fps 24 --video-frames 56
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\minimax_h3_ref2va_pruned-Q4_K_M.gguf --vae ..\models\vae\minimax_h3_video_vae_fp16.safetensors --audio-vae ..\models\vae\minimax_h3_audio_vae_fp32.safetensors --llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf -p "Use the cat from <Picture 1> as the main character. Keep the cat’s appearance, fur color, facial features, and identity consistent with the reference image. Create a 2-second cinematic video: start with an extreme close-up shot of the cat’s face, focusing on its cute expression and detailed fur texture. The camera slowly rotates around the cat’s head, creating a dynamic reveal. Then smoothly pull back and zoom out to reveal the full scene: the cat is standing confidently on a surfboard, riding ocean waves. Water splashes around the board, sea breeze gently moves the cat’s fur, and the cat maintains a cute and fearless expression while surfing. Smooth camera movement, cinematic orbit shot, seamless zoom-out transition, low-angle wide shot, realistic ocean environment, golden sunlight, dynamic waves, high-quality realistic style, natural motion, no distortion, keep the cat’s identity unchanged." -r ..\assets\ernie_image\turbo_example.png --cfg-scale 1.0 -v -W 864 -H 480 --offload-to-cpu --rng cpu --fps 24 --video-frames 56
|
||||
```
|
||||
|
||||
<video src=../assets/minimax-h3/r2av.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
|
||||
@@ -43,6 +43,5 @@ MiniT2I uses a MiniT2I diffusion transformer and `google/flan-t5-large` as the t
|
||||
--height 512 \
|
||||
--seed 42 \
|
||||
--sampling-method euler \
|
||||
--diffusion-fa \
|
||||
--output minit2i_cuda.png
|
||||
```
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
## Examples
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ovis_image-Q4_0.gguf --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\ovis_2.5.safetensors -p "a lovely cat" --cfg-scale 5.0 -v --offload-to-cpu --diffusion-fa
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ovis_image-Q4_0.gguf --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\ovis_2.5.safetensors -p "a lovely cat" --cfg-scale 5.0 -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img alt="ovis image example" src="../assets/ovis_image/example.png" />
|
||||
+11
-1
@@ -8,7 +8,7 @@ eg.:
|
||||
For most backends, it slows things down, but for cuda it generally speeds it up too.
|
||||
At the moment, it is only supported for some models and some backends (like cpu, cuda/rocm, metal).
|
||||
|
||||
Run by adding `--diffusion-fa` to the arguments and watch for:
|
||||
Watch for:
|
||||
```
|
||||
[INFO ] stable-diffusion.cpp:312 - Using flash attention in the diffusion model
|
||||
```
|
||||
@@ -21,6 +21,16 @@ CPU fallback. It excludes weights and cache buffers. Within a runner lifecycle,
|
||||
the summary is printed only on the first graph or when backend capacities or the
|
||||
segment count change.
|
||||
|
||||
## Run conditional and unconditional CFG in one batched UNet forward.
|
||||
|
||||
For UNet models, the conditional and unconditional guidance branches are
|
||||
concatenated into a single batch of two and run through one UNet forward per
|
||||
step instead of two separate forwards. This is enabled by default whenever the
|
||||
run qualifies for it.
|
||||
|
||||
Use `--batched-cfg off` to force separate conditional and unconditional
|
||||
forwards.
|
||||
|
||||
## Use VAE tiling to reduce encode and decode memory usage.
|
||||
|
||||
`--vae-tiling` enables spatial tiling for both VAE encoding and decoding. The
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ PiD and PiD 1.5 require an external Gemma 2 `tokenizer.json` matching the text e
|
||||
## Examples
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\pid_flux1_512_to_2048_4step_bf16.safetensors --llm "..\models\text_encoders\gemma_2_2b_it_elm_bf16.safetensors" --tokenizer ..\models\tokenizers\tokenizer_gemma2.json --vae ..\models\vae\ae.sft --vae-format flux --cfg-scale 1.0 -p "a lovely cat" -r ..\assets\ernie_image\turbo_example.png --diffusion-fa -v --steps 4 -H 2048 -W 2048 --rng cpu
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\pid_flux1_512_to_2048_4step_bf16.safetensors --llm "..\models\text_encoders\gemma_2_2b_it_elm_bf16.safetensors" --tokenizer ..\models\tokenizers\tokenizer_gemma2.json --vae ..\models\vae\ae.sft --vae-format flux --cfg-scale 1.0 -p "a lovely cat" -r ..\assets\ernie_image\turbo_example.png -v --steps 4 -H 2048 -W 2048 --rng cpu
|
||||
```
|
||||
|
||||
Before:
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
## Examples
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen-image-Q8_0.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\Qwen2.5-VL-7B-Instruct-Q8_0.gguf -p '一个穿着"QWEN"标志的T恤的中国美女正拿着黑色的马克笔面相镜头微笑。她身后的玻璃板上手写体写着 “一、Qwen-Image的技术路线: 探索视觉生成基础模型的极限,开创理解与生成一体化的未来。二、Qwen-Image的模型特色:1、复杂文字渲染。支持中英渲染、自动布局; 2、精准图像编辑。支持文字编辑、物体增减、风格变换。三、Qwen-Image的未来愿景:赋能专业内容创作、助力生成式AI发展。”' --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu -H 1024 -W 1024 --diffusion-fa --flow-shift 3
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen-image-Q8_0.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\Qwen2.5-VL-7B-Instruct-Q8_0.gguf -p '一个穿着"QWEN"标志的T恤的中国美女正拿着黑色的马克笔面相镜头微笑。她身后的玻璃板上手写体写着 “一、Qwen-Image的技术路线: 探索视觉生成基础模型的极限,开创理解与生成一体化的未来。二、Qwen-Image的模型特色:1、复杂文字渲染。支持中英渲染、自动布局; 2、精准图像编辑。支持文字编辑、物体增减、风格变换。三、Qwen-Image的未来愿景:赋能专业内容创作、助力生成式AI发展。”' --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu -H 1024 -W 1024 --flow-shift 3
|
||||
```
|
||||
|
||||
<img alt="qwen example" src="../assets/qwen/example.png" />
|
||||
|
||||
+14
-2
@@ -16,6 +16,8 @@ Qwen Image 2.1 supports text-to-image generation and image editing, using Qwen3-
|
||||
|
||||
Use `qwen_image_2.1_vae_bf16.safetensors` with this model. The earlier Qwen Image and Wan 2.2 VAE weights are not interchangeable with the Qwen Image 2.1 VAE weights.
|
||||
|
||||
For faster encoding, decoding, or previews, see [TAEQI2.1](taesd.md#qwen-image-21-taeqi21).
|
||||
|
||||
## Examples
|
||||
|
||||
Run the following commands from the build directory. Use image dimensions divisible by 32. The resolution-dependent flow schedule is selected automatically.
|
||||
@@ -23,19 +25,29 @@ Run the following commands from the build directory. Use image dimensions divisi
|
||||
### Text to image
|
||||
|
||||
```powershell
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen_image_2.1_int8_convrot.safetensors --vae ..\models\vae\qwen_image_2.1_vae_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf -p "a lovely cat holding a sign says 'qwen2.1.cpp'" --cfg-scale 6.0 --sampling-method euler -v --offload-to-cpu --fa -o qwen_image_2.1.png
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen_image_2.1_int8_convrot.safetensors --vae ..\models\vae\qwen_image_2.1_vae_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf -p "a lovely cat holding a sign says 'qwen2.1.cpp'" --cfg-scale 6.0 --sampling-method euler -v --offload-to-cpu -o qwen_image_2.1.png
|
||||
```
|
||||
|
||||
<img alt="Qwen Image 2.1 example" src="../assets/qwen/qwen_image_2.1.png" />
|
||||
|
||||
To use GGUF diffusion weights, set `--diffusion-model` to the path of a file such as `qwen_image_2.1-Q4_K.gguf`.
|
||||
|
||||
### Turbo
|
||||
|
||||
For [Qwen Image 2.1 Turbo](https://huggingface.co/Qwen/Qwen-Image-2.1-Turbo), use the Turbo diffusion weights and select its fixed 8-step sigma schedule with `--sigmas qwen-image-2.1-turbo`. For example, add these sampling options to the command above:
|
||||
|
||||
```text
|
||||
--sigmas qwen-image-2.1-turbo --steps 8 --cfg-scale 1.0 --sampling-method euler
|
||||
```
|
||||
|
||||
The preset is equivalent to `--sigmas 1.0,0.978453,0.95418,0.926626,0.89508,0.845148,0.704534,0.414568,0.0`. It only supplies the sigma values; CFG scale and sampling method remain independently configurable. As with an explicit sigma list, the sigma count determines the actual number of sampling steps. The same preset name is also accepted by `--hires-sigmas` for the highres fix second pass.
|
||||
|
||||
### Image editing
|
||||
|
||||
Pass the reference image with `-r` and describe the edit in `-p`. Vision weights are required; the example below loads them separately with `--llm_vision`.
|
||||
|
||||
```powershell
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen_image_2.1_int8_convrot.safetensors --vae ..\models\vae\qwen_image_2.1_vae_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --llm_vision ..\models\text_encoders\Qwen3VL-8B-Instruct-mmproj-BF16.gguf -r ..\assets\qwen\qwen_image_2.1.png -p "change 'qwen2.1.cpp' to 'sd.cpp'" --cfg-scale 6.0 --sampling-method euler -v --offload-to-cpu --fa -o qwen_image_2.1_edit.png
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen_image_2.1_int8_convrot.safetensors --vae ..\models\vae\qwen_image_2.1_vae_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --llm_vision ..\models\text_encoders\Qwen3VL-8B-Instruct-mmproj-BF16.gguf -r ..\assets\qwen\qwen_image_2.1.png -p "change 'qwen2.1.cpp' to 'sd.cpp'" --cfg-scale 6.0 --sampling-method euler -v --offload-to-cpu -o qwen_image_2.1_edit.png
|
||||
```
|
||||
|
||||
For multiple reference images, repeat `-r` in the desired order, for example `-r first.png -r second.png`.
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
### Qwen Image Edit
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Qwen_Image_Edit-Q8_0.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --diffusion-fa --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'edit.cpp'" --seed 1118877715456453
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Qwen_Image_Edit-Q8_0.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'edit.cpp'" --seed 1118877715456453
|
||||
```
|
||||
|
||||
<img alt="qwen_image_edit" src="../assets/qwen/qwen_image_edit.png" />
|
||||
@@ -32,7 +32,7 @@
|
||||
### Qwen Image Edit 2509
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Qwen-Image-Edit-2509-Q4_K_S.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\Qwen2.5-VL-7B-Instruct-Q8_0.gguf --llm_vision ..\models\text_encoders\Qwen2.5-VL-7B-Instruct.mmproj-Q8_0.gguf --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --diffusion-fa --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'Qwen Image Edit 2509'"
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\Qwen-Image-Edit-2509-Q4_K_S.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\Qwen2.5-VL-7B-Instruct-Q8_0.gguf --llm_vision ..\models\text_encoders\Qwen2.5-VL-7B-Instruct.mmproj-Q8_0.gguf --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'Qwen Image Edit 2509'"
|
||||
```
|
||||
|
||||
<img alt="qwen_image_edit_2509" src="../assets/qwen/qwen_image_edit_2509.png" />
|
||||
@@ -42,7 +42,7 @@
|
||||
To use the new Qwen Image Edit 2511 mode, `--model-args qwen_image_zero_cond_t=true` must be enabled; otherwise, image editing quality will degrade significantly.
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen-image-edit-2511-Q4_K_M.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --diffusion-fa --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'edit.cpp'" --model-args qwen_image_zero_cond_t=true
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen-image-edit-2511-Q4_K_M.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'edit.cpp'" --model-args qwen_image_zero_cond_t=true
|
||||
```
|
||||
|
||||
<img alt="qwen_image_edit_2509" src="../assets/qwen/qwen_image_edit_2511.png" />
|
||||
|
||||
@@ -37,12 +37,12 @@ rebuild with CUDA 12.8 or newer to use SageAttention2++.
|
||||
|
||||
## Use
|
||||
|
||||
Replace `--diffusion-fa` with `--sage-attn` in an existing command. For example,
|
||||
Add `--sage-attn` to enable SageAttention. For example,
|
||||
from the build directory:
|
||||
|
||||
```powershell
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 3.5 --sampling-method euler --steps 10 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 8 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,
|
||||
形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --offload-to-cpu --video-frames 33 --sage-attn
|
||||
形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --offload-to-cpu --video-frames 33 --sage-attn
|
||||
```
|
||||
|
||||
SageAttention currently handles unmasked attention with head dimensions from
|
||||
@@ -51,9 +51,7 @@ and multiple batches. Dimensions below 64 are zero-padded to 64; dimensions
|
||||
between 65 and 127 are zero-padded to 128. The original softmax scale is preserved,
|
||||
and the output is cropped back to the original dimension. Other attention
|
||||
operations fall back to FlashAttention when supported, then ordinary attention.
|
||||
SageAttention takes precedence in diffusion
|
||||
when combined with `--fa` or `--diffusion-fa`; `--fa` continues to control other
|
||||
modules. Existing attention scaling overrides remain effective.
|
||||
SageAttention takes precedence over FlashAttention in diffusion. Existing attention scaling overrides remain effective.
|
||||
|
||||
Attention quantization changes numerical results. Compare image quality and
|
||||
end-to-end generation time using the same seed, dimensions, and sampling
|
||||
|
||||
+3
-3
@@ -32,19 +32,19 @@ The dispatcher picks `alpha` from the filename (`turbo` substring => 1.0, otherw
|
||||
### 1B / 2B turbo
|
||||
|
||||
```
|
||||
./build/bin/sd-cli --diffusion-model /path/to/sefi_1b_turbo.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_2b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 1.0 --steps 4 -W 1024 -H 1024 -s 42 --diffusion-fa --offload-to-cpu -o out.png
|
||||
./build/bin/sd-cli --diffusion-model /path/to/sefi_1b_turbo.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_2b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 1.0 --steps 4 -W 1024 -H 1024 -s 42 --offload-to-cpu -o out.png
|
||||
```
|
||||
|
||||
### 1B / 2B base
|
||||
|
||||
```
|
||||
./build/bin/sd-cli --diffusion-model /path/to/sefi_1b_base.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_2b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 4.0 --steps 50 -W 1024 -H 1024 -s 42 --diffusion-fa --offload-to-cpu -o out.png
|
||||
./build/bin/sd-cli --diffusion-model /path/to/sefi_1b_base.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_2b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 4.0 --steps 50 -W 1024 -H 1024 -s 42 --offload-to-cpu -o out.png
|
||||
```
|
||||
|
||||
### 5B (needs streaming on 12 GiB VRAM)
|
||||
|
||||
```
|
||||
./build/bin/sd-cli --diffusion-model /path/to/sefi_5b_turbo.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_4b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 1.0 --steps 4 -W 1024 -H 1024 -s 42 --diffusion-fa --max-vram 8 --offload-to-cpu -o out.png
|
||||
./build/bin/sd-cli --diffusion-model /path/to/sefi_5b_turbo.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_4b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 1.0 --steps 4 -W 1024 -H 1024 -s 42 --max-vram 8 --offload-to-cpu -o out.png
|
||||
```
|
||||
|
||||
<img alt="SeFi-Image 5B turbo example" src="../assets/sefi_image/example.png" />
|
||||
|
||||
@@ -28,7 +28,6 @@ and the tokenizer files.
|
||||
--seed 42 \
|
||||
--sampling-method euler \
|
||||
--rng cuda \
|
||||
--fa \
|
||||
--output output.png
|
||||
```
|
||||
|
||||
|
||||
@@ -37,3 +37,15 @@ sd.cpp also supports [TAEHV](https://github.com/madebyollin/taehv) (#937), which
|
||||
```
|
||||
|
||||
Then simply replace the `--vae xxx.safetensors` with `--tae xxx.safetensors` in the commands. If it still out of VRAM, add `--vae-conv-direct` to your command though might be slower.
|
||||
|
||||
### Qwen Image 2.1 (TAEQI2.1)
|
||||
|
||||
For Qwen Image 2.1, use [taeqi2_1](https://github.com/madebyollin/taesd), which supports 64-channel latents, 16x spatial scaling, and RGBA images.
|
||||
|
||||
Download the official [safetensors weights](https://huggingface.co/madebyollin/taeqi2_1/blob/main/taeqi2_1.safetensors) directly; no conversion is needed:
|
||||
|
||||
```bash
|
||||
curl -L -o taeqi2_1.safetensors https://huggingface.co/madebyollin/taeqi2_1/resolve/main/taeqi2_1.safetensors
|
||||
```
|
||||
|
||||
Replace `--vae PATH` with `--taesd taeqi2_1.safetensors` in the [Qwen Image 2.1 examples](qwen_image_2.1.md) to use it for encoding and decoding. For previews only, keep `--vae PATH` and add `--taesd taeqi2_1.safetensors --taesd-preview-only --preview tae`.
|
||||
|
||||
@@ -40,8 +40,7 @@ the intended output scale:
|
||||
- `--linear-scale` scales Linear inputs before matrix multiplication and rescales
|
||||
the result.
|
||||
- `--attn-scale` scales attention keys and values (K/V). It takes effect only in
|
||||
the Flash Attention path, where `--fa` or `--diffusion-fa` is enabled and the
|
||||
backend supports it.
|
||||
the Flash Attention path, when selected and supported by the backend.
|
||||
|
||||
The two values can be set independently and apply across model components. The
|
||||
default `0` preserves each model's built-in settings; `1` explicitly disables the
|
||||
|
||||
+43
-18
@@ -40,6 +40,9 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/tree/main/split_files/diffusion_models
|
||||
- gguf: https://huggingface.co/QuantStack/Wan2.2-S2V-14B-GGUF/tree/main
|
||||
- int8_convrot safetensors: https://huggingface.co/noctrex/Wan2.2-S2V-14B-int8_convrot
|
||||
- Wan2.2 VACE-Fun A14B
|
||||
- safetensors: https://huggingface.co/alibaba-pai/Wan2.2-VACE-Fun-A14B
|
||||
- gguf: https://huggingface.co/QuantStack/Wan2.2-VACE-Fun-A14B-GGUF/tree/main
|
||||
- Download vae
|
||||
- wan_2.1_vae (for all the wan model except Wan2.2 TI2V 5B)
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/blob/main/split_files/vae/wan_2.1_vae.safetensors
|
||||
@@ -64,7 +67,7 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
|
||||
### Wan2.1 T2V 1.3B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1_t2v_1.3B_fp16.safetensors --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --video-frames 33 --flow-shift 3.0
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1_t2v_1.3B_fp16.safetensors --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --video-frames 33 --flow-shift 3.0
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.1_1.3B_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -72,7 +75,7 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
|
||||
### Wan2.1 T2V 14B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-t2v-14b-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --offload-to-cpu --video-frames 33 --flow-shift 3.0
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-t2v-14b-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --offload-to-cpu --video-frames 33 --flow-shift 3.0
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.1_14B_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -82,7 +85,7 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
|
||||
### Wan2.1 I2V 14B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-i2v-14b-480p-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --clip_vision ..\models\clip_vision\clip_vision_h.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa --video-frames 33 --offload-to-cpu -i ..\assets\cat_with_sd_cpp_42.png --flow-shift 3.0
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-i2v-14b-480p-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --clip_vision ..\models\clip_vision\clip_vision_h.safetensors -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --video-frames 33 --offload-to-cpu -i ..\assets\cat_with_sd_cpp_42.png --flow-shift 3.0
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.1_14B_i2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -90,7 +93,7 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
|
||||
### Wan2.2 T2V A14B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 3.5 --sampling-method euler --steps 10 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 8 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --offload-to-cpu --video-frames 33 --flow-shift 3.0
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 3.5 --sampling-method euler --steps 10 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 8 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --offload-to-cpu --video-frames 33 --flow-shift 3.0
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.2_14B_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -98,7 +101,7 @@ Wan models require `-M vid_gen`, including single-frame generation. `--video-fra
|
||||
### Wan2.2 I2V A14B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 3.5 --sampling-method euler --steps 10 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 8 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --offload-to-cpu --video-frames 33 --offload-to-cpu -i ..\assets\cat_with_sd_cpp_42.png --flow-shift 3.0
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 3.5 --sampling-method euler --steps 10 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 8 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --offload-to-cpu --video-frames 33 --offload-to-cpu -i ..\assets\cat_with_sd_cpp_42.png --flow-shift 3.0
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.2_14B_i2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -111,7 +114,7 @@ wav2vec2 audio encoder. Wan2.2 S2V requires the wan_2.1 vae (16 channel), not
|
||||
the wan2.2 vae.
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.2_s2v-14B-Q8_0.gguf --audio-encoder ..\models\audio_encoders\wav2vec2_large_english_fp16.safetensors --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a person is talking" --cfg-scale 6.0 --steps 20 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --offload-to-cpu --vae-tiling --video-frames 81 -i ..\assets\cat_with_sd_cpp_42.png --audio .\input\speech.wav --flow-shift 3.0
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.2_s2v-14B-Q8_0.gguf --audio-encoder ..\models\audio_encoders\wav2vec2_large_english_fp16.safetensors --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a person is talking" --cfg-scale 6.0 --steps 20 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --offload-to-cpu --vae-tiling --video-frames 81 -i ..\assets\cat_with_sd_cpp_42.png --audio .\input\speech.wav --flow-shift 3.0
|
||||
```
|
||||
|
||||
Notes:
|
||||
@@ -148,7 +151,7 @@ Notes:
|
||||
### Wan2.2 T2V A14B T2I
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 3.5 --sampling-method euler --steps 10 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 8 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --offload-to-cpu --flow-shift 3.0
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 3.5 --sampling-method euler --steps 10 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 8 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --offload-to-cpu --flow-shift 3.0
|
||||
```
|
||||
|
||||
<img width="832" height="480" alt="Wan2 2_14B_t2i" src="../assets/wan/Wan2.2_14B_t2i.png" />
|
||||
@@ -156,7 +159,7 @@ Notes:
|
||||
### Wan2.2 T2V 14B with Lora
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat<lora:wan2.2_t2v_lightx2v_4steps_lora_v1.1_low_noise:1><lora:|high_noise|wan2.2_t2v_lightx2v_4steps_lora_v1.1_high_noise:1>" --cfg-scale 3.5 --sampling-method euler --steps 4 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 4 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --offload-to-cpu --lora-model-dir ..\models\loras --video-frames 33 --flow-shift 3.0
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-T2V-A14B-HighNoise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat<lora:wan2.2_t2v_lightx2v_4steps_lora_v1.1_low_noise:1><lora:|high_noise|wan2.2_t2v_lightx2v_4steps_lora_v1.1_high_noise:1>" --cfg-scale 3.5 --sampling-method euler --steps 4 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 4 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --offload-to-cpu --lora-model-dir ..\models\loras --video-frames 33 --flow-shift 3.0
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.2_14B_t2v_lora.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -168,7 +171,7 @@ Notes:
|
||||
#### T2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.2_ti2v_5B_fp16.safetensors --vae ..\models\vae\wan2.2_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa --offload-to-cpu --video-frames 33 --flow-shift 3.0
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.2_ti2v_5B_fp16.safetensors --vae ..\models\vae\wan2.2_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --offload-to-cpu --video-frames 33 --flow-shift 3.0
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.2_5B_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -176,7 +179,7 @@ Notes:
|
||||
#### I2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.2_ti2v_5B_fp16.safetensors --vae ..\models\vae\wan2.2_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa --offload-to-cpu --video-frames 33 -i ..\assets\cat_with_sd_cpp_42.png --flow-shift 3.0
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.2_ti2v_5B_fp16.safetensors --vae ..\models\vae\wan2.2_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --offload-to-cpu --video-frames 33 -i ..\assets\cat_with_sd_cpp_42.png --flow-shift 3.0
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.2_5B_i2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -184,7 +187,7 @@ Notes:
|
||||
### Wan2.1 FLF2V 14B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-flf2v-14b-720p-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --clip_vision ..\models\clip_vision\clip_vision_h.safetensors -p "glass flower blossom" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa --video-frames 33 --offload-to-cpu --init-img ..\..\ComfyUI\input\start_image.png --end-img ..\..\ComfyUI\input\end_image.png --flow-shift 3.0
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-flf2v-14b-720p-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --clip_vision ..\models\clip_vision\clip_vision_h.safetensors -p "glass flower blossom" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --video-frames 33 --offload-to-cpu --init-img ..\..\ComfyUI\input\start_image.png --end-img ..\..\ComfyUI\input\end_image.png --flow-shift 3.0
|
||||
```
|
||||
|
||||
|
||||
@@ -193,7 +196,7 @@ Notes:
|
||||
### Wan2.2 FLF2V 14B
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --cfg-scale 3.5 --sampling-method euler --steps 10 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 8 -v -p "glass flower blossom" -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa --video-frames 33 --offload-to-cpu --init-img ..\..\ComfyUI\input\start_image.png --end-img ..\..\ComfyUI\input\end_image.png --flow-shift 3.0
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf --cfg-scale 3.5 --sampling-method euler --steps 10 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 8 -v -p "glass flower blossom" -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --video-frames 33 --offload-to-cpu --init-img ..\..\ComfyUI\input\start_image.png --end-img ..\..\ComfyUI\input\end_image.png --flow-shift 3.0
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.2_14B_flf2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -203,7 +206,7 @@ Notes:
|
||||
#### T2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-vace-1.3b-q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --video-frames 1 --offload-to-cpu
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-vace-1.3b-q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --video-frames 1 --offload-to-cpu
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.1_1.3B_vace_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -212,7 +215,7 @@ Notes:
|
||||
#### R2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-vace-1.3b-q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa -i ..\assets\cat_with_sd_cpp_42.png --video-frames 33 --offload-to-cpu
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-vace-1.3b-q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 -i ..\assets\cat_with_sd_cpp_42.png --video-frames 33 --offload-to-cpu
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.1_1.3B_vace_r2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -223,7 +226,7 @@ Notes:
|
||||
```
|
||||
mkdir post+depth
|
||||
ffmpeg -i ..\..\ComfyUI\input\post+depth.mp4 -qscale:v 1 -vf fps=8 post+depth\frame_%04d.jpg
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-vace-1.3b-q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "The girl is dancing in a sea of flowers, slowly moving her hands. There is a close - up shot of her upper body. The character is surrounded by other transparent glass flowers in the style of Nicoletta Ceccoli, creating a beautiful, surreal, and emotionally expressive movie scene with a white. transparent feel and a dreamyl atmosphere." --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa -i ..\..\ComfyUI\input\dance_girl.jpg --control-video ./post+depth --video-frames 33 --offload-to-cpu
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\wan2.1-vace-1.3b-q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "The girl is dancing in a sea of flowers, slowly moving her hands. There is a close - up shot of her upper body. The character is surrounded by other transparent glass flowers in the style of Nicoletta Ceccoli, creating a beautiful, surreal, and emotionally expressive movie scene with a white. transparent feel and a dreamyl atmosphere." --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 -i ..\..\ComfyUI\input\dance_girl.jpg --control-video ./post+depth --video-frames 33 --offload-to-cpu
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.1_1.3B_vace_v2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -233,7 +236,7 @@ ffmpeg -i ..\..\ComfyUI\input\post+depth.mp4 -qscale:v 1 -vf fps=8 post+depth\fr
|
||||
#### T2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.1_14B_VACE-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa --video-frames 33 --offload-to-cpu
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.1_14B_VACE-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --video-frames 33 --offload-to-cpu
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.1_14B_vace_t2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -242,7 +245,7 @@ ffmpeg -i ..\..\ComfyUI\input\post+depth.mp4 -qscale:v 1 -vf fps=8 post+depth\fr
|
||||
#### R2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.1_14B_VACE-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 --diffusion-fa -i ..\assets\cat_with_sd_cpp_42.png --video-frames 33 --offload-to-cpu
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.1_14B_VACE-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 -i ..\assets\cat_with_sd_cpp_42.png --video-frames 33 --offload-to-cpu
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.1_14B_vace_r2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
@@ -252,7 +255,29 @@ ffmpeg -i ..\..\ComfyUI\input\post+depth.mp4 -qscale:v 1 -vf fps=8 post+depth\fr
|
||||
#### V2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.1_14B_VACE-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "The girl is dancing in a sea of flowers, slowly moving her hands. There is a close - up shot of her upper body. The character is surrounded by other transparent glass flowers in the style of Nicoletta Ceccoli, creating a beautiful, surreal, and emotionally expressive movie scene with a white. transparent feel and a dreamyl atmosphere." --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 --diffusion-fa -i ..\..\ComfyUI\input\dance_girl.jpg --control-video ./post+depth --video-frames 33 --offload-to-cpu
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.1_14B_VACE-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "The girl is dancing in a sea of flowers, slowly moving her hands. There is a close - up shot of her upper body. The character is surrounded by other transparent glass flowers in the style of Nicoletta Ceccoli, creating a beautiful, surreal, and emotionally expressive movie scene with a white. transparent feel and a dreamyl atmosphere." --cfg-scale 6.0 --sampling-method euler -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 480 -H 832 -i ..\..\ComfyUI\input\dance_girl.jpg --control-video ./post+depth --video-frames 33 --offload-to-cpu
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.1_14B_vace_v2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
|
||||
### Wan2.2 VACE-Fun A14B
|
||||
|
||||
VACE-Fun runs as a MoE pair: `--diffusion-model` takes the low-noise expert and
|
||||
`--high-noise-diffusion-model` the high-noise one. Reference-to-video uses `-i`
|
||||
for the reference image, same as Wan2.1 VACE.
|
||||
|
||||
#### R2V
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe -M vid_gen --diffusion-model ..\models\diffusion_models\Wan2.2-VACE-Fun-A14B-low-noise-Q8_0.gguf --high-noise-diffusion-model ..\models\diffusion_models\Wan2.2-VACE-Fun-A14B-high-noise-Q8_0.gguf --vae ..\models\vae\wan_2.1_vae.safetensors --t5xxl ..\models\text_encoders\umt5-xxl-encoder-Q8_0.gguf -p "a lovely cat" --cfg-scale 3.5 --sampling-method euler --steps 10 --high-noise-cfg-scale 3.5 --high-noise-sampling-method euler --high-noise-steps 8 -v -n "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部, 畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" -W 832 -H 480 -i ..\assets\cat_with_sd_cpp_42.png --video-frames 33 --offload-to-cpu
|
||||
```
|
||||
|
||||
<video src=../assets/wan/Wan2.2_A14B_vace_r2v.mp4 controls="controls" muted="muted" type="video/mp4"></video>
|
||||
|
||||
#### T2V
|
||||
|
||||
Same command without `-i` (VACE context is synthesized from an empty control
|
||||
video, like Wan2.1 VACE t2v).
|
||||
|
||||
> On GPUs with ~12 GB VRAM, VACE also needs `--vae-tiling` — the control-video
|
||||
> encode can exceed the budget otherwise.
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ You can run Z-Image with stable-diffusion.cpp on GPUs with 4GB of VRAM — or ev
|
||||
### Z-Image-Turbo
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model z_image_turbo-Q3_K.gguf --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\Qwen3-4B-Instruct-2507-Q4_K_M.gguf -p "A cinematic, melancholic photograph of a solitary hooded figure walking through a sprawling, rain-slicked metropolis at night. The city lights are a chaotic blur of neon orange and cool blue, reflecting on the wet asphalt. The scene evokes a sense of being a single component in a vast machine. Superimposed over the image in a sleek, modern, slightly glitched font is the philosophical quote: 'THE CITY IS A CIRCUIT BOARD, AND I AM A BROKEN TRANSISTOR.' -- moody, atmospheric, profound, dark academic" --cfg-scale 1.0 -v --offload-to-cpu --diffusion-fa -H 1024 -W 512 --steps 8
|
||||
.\bin\Release\sd-cli.exe --diffusion-model z_image_turbo-Q3_K.gguf --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\Qwen3-4B-Instruct-2507-Q4_K_M.gguf -p "A cinematic, melancholic photograph of a solitary hooded figure walking through a sprawling, rain-slicked metropolis at night. The city lights are a chaotic blur of neon orange and cool blue, reflecting on the wet asphalt. The scene evokes a sense of being a single component in a vast machine. Superimposed over the image in a sleek, modern, slightly glitched font is the philosophical quote: 'THE CITY IS A CIRCUIT BOARD, AND I AM A BROKEN TRANSISTOR.' -- moody, atmospheric, profound, dark academic" --cfg-scale 1.0 -v --offload-to-cpu -H 1024 -W 512 --steps 8
|
||||
```
|
||||
|
||||
<img width="256" alt="z-image example" src="../assets/z_image/q3_K.png" />
|
||||
@@ -29,7 +29,7 @@ You can run Z-Image with stable-diffusion.cpp on GPUs with 4GB of VRAM — or ev
|
||||
### Z-Image-Base
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\z_image_bf16.safetensors --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "A cinematic, melancholic photograph of a solitary hooded figure walking through a sprawling, rain-slicked metropolis at night. The city lights are a chaotic blur of neon orange and cool blue, reflecting on the wet asphalt. The scene evokes a sense of being a single component in a vast machine. Superimposed over the image in a sleek, modern, slightly glitched font is the philosophical quote: 'THE CITY IS A CIRCUIT BOARD, AND I AM A BROKEN TRANSISTOR.' -- moody, atmospheric, profound, dark academic" --cfg-scale 5.0 -v --offload-to-cpu --diffusion-fa -H 1024 -W 512
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\z_image_bf16.safetensors --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "A cinematic, melancholic photograph of a solitary hooded figure walking through a sprawling, rain-slicked metropolis at night. The city lights are a chaotic blur of neon orange and cool blue, reflecting on the wet asphalt. The scene evokes a sense of being a single component in a vast machine. Superimposed over the image in a sleek, modern, slightly glitched font is the philosophical quote: 'THE CITY IS A CIRCUIT BOARD, AND I AM A BROKEN TRANSISTOR.' -- moody, atmospheric, profound, dark academic" --cfg-scale 5.0 -v --offload-to-cpu -H 1024 -W 512
|
||||
```
|
||||
|
||||
<img width="256" alt="z-image example" src="../assets/z_image/base_bf16.png" />
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Z-Image L2P
|
||||
|
||||
[L2P](https://nju-pcalab.github.io/projects/L2P/) (Latent-to-Pixel) transfers Z-Image-Turbo to pixel space. The VAE is replaced by 16x16 patch tokenization on RGB pixels and a small convolutional local decoder that turns the last DiT hidden states back into pixels, so no VAE is needed. The text encoder is the Qwen3-4B used by Z-Image-Turbo.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download L2P (1K)
|
||||
- safetensors: https://huggingface.co/zhen-nan/L2P/tree/main
|
||||
- Download Qwen3 4b
|
||||
- safetensors: https://huggingface.co/Comfy-Org/z_image_turbo/tree/main/split_files/text_encoders
|
||||
- gguf: https://huggingface.co/unsloth/Qwen3-4B-Instruct-2507-GGUF/tree/main
|
||||
|
||||
## Text-to-image
|
||||
|
||||
Do not pass `--vae`. Image dimensions must be multiples of 16. The reference pipeline uses 30 steps with a CFG scale of 2.0.
|
||||
|
||||
The released checkpoint keeps part of the transformer in F32. Pass `--type bf16` to load it in BF16, as the reference pipeline runs it; this reduces the diffusion model from about 18.6 GB to 11.8 GB.
|
||||
|
||||
```bash
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\model-1k-merge.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "an origami pig on fire in the middle of a dark room with a pentagram on the floor" --cfg-scale 2.0 --steps 30 --sampling-method euler -W 1024 -H 1024 --type bf16 -v -o z_image_l2p.png
|
||||
```
|
||||
@@ -11,6 +11,17 @@ Logging defaults to `info`. Use `--log-level <level>` to select `debug`, `verbos
|
||||
messages at that level and all less detailed levels. `-v` and `--verbose` are
|
||||
equivalent to `--log-level verbose`. If repeated, the last logging option wins.
|
||||
|
||||
`--flash-attn on|off|auto` defaults to `auto`, selecting flash attention for each
|
||||
compute backend during context creation. See [Backend selection](../../docs/backend.md#flash-attention---flash-attn-onoffauto)
|
||||
for probing behavior.
|
||||
Use module assignments such as `--flash-attn auto,te=off,vae=on` to override
|
||||
individual modules.
|
||||
|
||||
`--conv-direct on|off|auto` also defaults to `auto`, enabling direct convolution
|
||||
on backends with implicit GEMM implementations. Use `--conv-direct auto,vae=off`
|
||||
for module overrides; supported modules are `diffusion`, `vae`, and `controlnet`.
|
||||
See [Direct convolution](../../docs/backend.md#direct-convolution---conv-direct-onoffauto).
|
||||
|
||||
For direct image repair or automatic post-generation YOLOv8 detection followed by cropped inpainting, see
|
||||
[ADetailer](../../docs/adetailer.md).
|
||||
|
||||
|
||||
+12
-1
@@ -716,6 +716,15 @@ int main(int argc, const char* argv[]) {
|
||||
}
|
||||
|
||||
if (cli_params.mode == CONVERT) {
|
||||
std::vector<sd_lora_t> loras;
|
||||
loras.reserve(gen_params.lora_map.size() + gen_params.high_noise_lora_map.size());
|
||||
for (const auto& [path, multiplier] : gen_params.lora_map) {
|
||||
loras.push_back({false, multiplier, path.c_str()});
|
||||
}
|
||||
for (const auto& [path, multiplier] : gen_params.high_noise_lora_map) {
|
||||
loras.push_back({true, multiplier, path.c_str()});
|
||||
}
|
||||
|
||||
bool success = convert_with_components(ctx_params.model_path.c_str(),
|
||||
ctx_params.clip_l_path.c_str(),
|
||||
ctx_params.clip_g_path.c_str(),
|
||||
@@ -726,7 +735,9 @@ int main(int argc, const char* argv[]) {
|
||||
ctx_params.wtype,
|
||||
ctx_params.tensor_type_rules.c_str(),
|
||||
cli_params.convert_name,
|
||||
ctx_params.n_threads);
|
||||
ctx_params.n_threads,
|
||||
loras.empty() ? nullptr : loras.data(),
|
||||
(int)loras.size());
|
||||
if (!success) {
|
||||
LOG_ERROR("convert '%s'/'%s' to '%s' failed",
|
||||
ctx_params.model_path.c_str(),
|
||||
|
||||
+110
-48
@@ -378,6 +378,23 @@ static int parse_scale_override(int argc, const char** argv, int index, float& s
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int parse_on_off_arg(int argc, const char** argv, int index, const char* option, bool& value) {
|
||||
if (++index >= argc) {
|
||||
LOG_ERROR("%s requires 'on' or 'off'", option);
|
||||
return -1;
|
||||
}
|
||||
const std::string arg = argv[index];
|
||||
if (arg == "on") {
|
||||
value = true;
|
||||
} else if (arg == "off") {
|
||||
value = false;
|
||||
} else {
|
||||
LOG_ERROR("invalid %s value '%s'; expected 'on' or 'off'", option, argv[index]);
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
ArgOptions SDContextParams::get_options() {
|
||||
ArgOptions options;
|
||||
options.string_options = {
|
||||
@@ -615,43 +632,10 @@ ArgOptions SDContextParams::get_options() {
|
||||
"--vae-on-cpu",
|
||||
"deprecated; use --backend vae=cpu",
|
||||
true, &vae_on_cpu},
|
||||
{"",
|
||||
"--fa",
|
||||
"use flash attention",
|
||||
true, &flash_attn},
|
||||
{"",
|
||||
"--diffusion-fa",
|
||||
"use flash attention in the diffusion model only",
|
||||
true, &diffusion_flash_attn},
|
||||
{"",
|
||||
"--sage-attn",
|
||||
"use native CUDA SageAttention in the diffusion model, with flash/default attention fallback",
|
||||
true, &sage_attn},
|
||||
{"",
|
||||
"--diffusion-conv-direct",
|
||||
"use ggml_conv2d_direct in the diffusion model",
|
||||
true, &diffusion_conv_direct},
|
||||
{"",
|
||||
"--vae-conv-direct",
|
||||
"use direct 2D and 3D convolutions in the vae model",
|
||||
true, &vae_conv_direct},
|
||||
};
|
||||
|
||||
auto on_auto_fit_arg = [&](int argc, const char** argv, int index) {
|
||||
if (++index >= argc) {
|
||||
LOG_ERROR("--auto-fit requires 'on' or 'off'");
|
||||
return -1;
|
||||
}
|
||||
const std::string arg = argv[index];
|
||||
if (arg == "on") {
|
||||
auto_fit = true;
|
||||
} else if (arg == "off") {
|
||||
auto_fit = false;
|
||||
} else {
|
||||
LOG_ERROR("invalid --auto-fit value '%s'; expected 'on' or 'off'", argv[index]);
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
|
||||
auto on_type_arg = [&](int argc, const char** argv, int index) {
|
||||
@@ -733,16 +717,83 @@ ArgOptions SDContextParams::get_options() {
|
||||
}},
|
||||
{"",
|
||||
"--attn-scale",
|
||||
"flash-attention K/V scale override (float, default: 0 = model default, 1 = no scaling); requires --fa or --diffusion-fa",
|
||||
"flash-attention K/V scale override (float, default: 0 = model default, 1 = no scaling); applies when flash attention is selected",
|
||||
[this](int argc, const char** argv, int index) {
|
||||
return parse_scale_override(argc, argv, index, attn_scale);
|
||||
}},
|
||||
{"",
|
||||
"--diffusion-conv-direct",
|
||||
"deprecated; use --conv-direct auto,diffusion=on,controlnet=on",
|
||||
[this](int, const char**, int) {
|
||||
LOG_WARN("--diffusion-conv-direct is deprecated; use --conv-direct auto,diffusion=on,controlnet=on instead.");
|
||||
diffusion_conv_direct = true;
|
||||
return 0;
|
||||
}},
|
||||
{"",
|
||||
"--vae-conv-direct",
|
||||
"deprecated; use --conv-direct auto,vae=on",
|
||||
[this](int, const char**, int) {
|
||||
LOG_WARN("--vae-conv-direct is deprecated; use --conv-direct auto,vae=on instead.");
|
||||
vae_conv_direct = true;
|
||||
return 0;
|
||||
}},
|
||||
{"",
|
||||
"--conv-direct",
|
||||
"on|off|auto or diffusion/vae/controlnet assignments (default: auto), e.g. auto,vae=off. Overrides legacy convolution flags",
|
||||
[this](int argc, const char** argv, int index) {
|
||||
if (++index >= argc || argv[index][0] == '\0') {
|
||||
LOG_ERROR("--conv-direct requires on, off, auto, or module assignments");
|
||||
return -1;
|
||||
}
|
||||
conv_direct_mode = argv[index];
|
||||
return 1;
|
||||
}},
|
||||
{"",
|
||||
"--flash-attn",
|
||||
"on|off|auto or module assignments (default: auto), e.g. auto,te=off,vae=on. Last repeated option wins",
|
||||
[this](int argc, const char** argv, int index) {
|
||||
if (++index >= argc) {
|
||||
LOG_ERROR("--flash-attn requires on, off, or auto");
|
||||
return -1;
|
||||
}
|
||||
const std::string value = argv[index];
|
||||
if (value.empty()) {
|
||||
LOG_ERROR("--flash-attn requires a nonempty value");
|
||||
return -1;
|
||||
}
|
||||
flash_attn_mode = value;
|
||||
return 1;
|
||||
}},
|
||||
{"",
|
||||
"--fa",
|
||||
"deprecated; use --flash-attn on",
|
||||
[this](int, const char**, int) {
|
||||
LOG_WARN("--fa is deprecated; use --flash-attn on instead.");
|
||||
flash_attn_mode = "on";
|
||||
return 0;
|
||||
}},
|
||||
{"",
|
||||
"--diffusion-fa",
|
||||
"deprecated; use --flash-attn off,diffusion=on",
|
||||
[this](int, const char**, int) {
|
||||
LOG_WARN("--diffusion-fa is deprecated; use --flash-attn off,diffusion=on instead.");
|
||||
flash_attn_mode = "off,diffusion=on";
|
||||
return 0;
|
||||
}},
|
||||
{"",
|
||||
"--auto-fit",
|
||||
"on|off (default: on). Preserve --backend (otherwise select one GPU) and place weights on the compute GPU, "
|
||||
"RAM, another GPU, or disk in that order, according to available memory (--max-vram limits GPU budgets). "
|
||||
"Disabled by explicit --params-backend; uses automatic graph segmentation when needed",
|
||||
on_auto_fit_arg},
|
||||
[this](int argc, const char** argv, int index) {
|
||||
return parse_on_off_arg(argc, argv, index, "--auto-fit", auto_fit);
|
||||
}},
|
||||
{"",
|
||||
"--batched-cfg",
|
||||
"on|off (default: on). Run the conditional and unconditional CFG branches in one batched UNet forward when supported",
|
||||
[this](int argc, const char** argv, int index) {
|
||||
return parse_on_off_arg(argc, argv, index, "--batched-cfg", batched_cfg);
|
||||
}},
|
||||
{"",
|
||||
"--type",
|
||||
"weight type (examples: f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_K, q3_K, q4_K). "
|
||||
@@ -940,6 +991,7 @@ std::string SDContextParams::to_string() const {
|
||||
<< " max_vram: \"" << max_vram << "\",\n"
|
||||
<< " disable_prefetch: " << (disable_prefetch ? "true" : "false") << ",\n"
|
||||
<< " disable_segmented_compute: " << (disable_segmented_compute ? "true" : "false") << ",\n"
|
||||
<< " batched_cfg: " << (batched_cfg ? "true" : "false") << ",\n"
|
||||
<< " eager_load: " << (eager_load ? "true" : "false") << ",\n"
|
||||
<< " backend: \"" << backend << "\",\n"
|
||||
<< " params_backend: \"" << params_backend << "\",\n"
|
||||
@@ -950,8 +1002,8 @@ std::string SDContextParams::to_string() const {
|
||||
<< " control_net_cpu: " << (control_net_cpu ? "true" : "false") << ",\n"
|
||||
<< " clip_on_cpu: " << (clip_on_cpu ? "true" : "false") << ",\n"
|
||||
<< " vae_on_cpu: " << (vae_on_cpu ? "true" : "false") << ",\n"
|
||||
<< " flash_attn: " << (flash_attn ? "true" : "false") << ",\n"
|
||||
<< " diffusion_flash_attn: " << (diffusion_flash_attn ? "true" : "false") << ",\n"
|
||||
<< " flash_attn_mode: " << flash_attn_mode << ",\n"
|
||||
<< " conv_direct_mode: " << (conv_direct_mode.empty() ? "auto (legacy flags honored)" : conv_direct_mode) << ",\n"
|
||||
<< " sage_attn: " << (sage_attn ? "true" : "false") << ",\n"
|
||||
<< " linear_scale: " << linear_scale << ",\n"
|
||||
<< " attn_scale: " << attn_scale << ",\n"
|
||||
@@ -1009,8 +1061,8 @@ sd_ctx_params_t SDContextParams::to_sd_ctx_params_t(bool taesd_preview) {
|
||||
sd_ctx_params.prediction = prediction;
|
||||
sd_ctx_params.lora_apply_mode = lora_apply_mode;
|
||||
sd_ctx_params.enable_mmap = enable_mmap;
|
||||
sd_ctx_params.flash_attn = flash_attn;
|
||||
sd_ctx_params.diffusion_flash_attn = diffusion_flash_attn;
|
||||
sd_ctx_params.flash_attn_mode = flash_attn_mode.c_str();
|
||||
sd_ctx_params.conv_direct_mode = conv_direct_mode.c_str();
|
||||
sd_ctx_params.sage_attn = sage_attn;
|
||||
sd_ctx_params.linear_scale = linear_scale;
|
||||
sd_ctx_params.attn_scale = attn_scale;
|
||||
@@ -1022,6 +1074,7 @@ sd_ctx_params_t SDContextParams::to_sd_ctx_params_t(bool taesd_preview) {
|
||||
sd_ctx_params.max_vram = max_vram.c_str();
|
||||
sd_ctx_params.disable_prefetch = disable_prefetch;
|
||||
sd_ctx_params.disable_segmented_compute = disable_segmented_compute;
|
||||
sd_ctx_params.batched_cfg = batched_cfg;
|
||||
sd_ctx_params.eager_load = eager_load;
|
||||
sd_ctx_params.backend = effective_backend.c_str();
|
||||
sd_ctx_params.params_backend = effective_params_backend.c_str();
|
||||
@@ -1465,6 +1518,9 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
return -1;
|
||||
}
|
||||
std::string sigmas_str = value;
|
||||
if (sigmas_str == "qwen-image-2.1-turbo") {
|
||||
sigmas_str = "1.0,0.978453,0.95418,0.926626,0.89508,0.845148,0.704534,0.414568,0.0";
|
||||
}
|
||||
if (!sigmas_str.empty() && sigmas_str.front() == '[') {
|
||||
sigmas_str.erase(0, 1);
|
||||
}
|
||||
@@ -1711,11 +1767,11 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
on_scheduler_arg},
|
||||
{"",
|
||||
"--sigmas",
|
||||
"custom sigma values for the sampler, comma-separated (e.g., \"14.61,7.8,3.5,0.0\").",
|
||||
"custom sigma values for the sampler, comma-separated (e.g., \"14.61,7.8,3.5,0.0\") or preset: qwen-image-2.1-turbo.",
|
||||
on_sigmas_arg},
|
||||
{"",
|
||||
"--hires-sigmas",
|
||||
"custom sigma values for the highres fix second pass, comma-separated (e.g., \"0.85,0.725,0.421875,0.0\").",
|
||||
"custom sigma values for the highres fix second pass, comma-separated (e.g., \"0.85,0.725,0.421875,0.0\") or preset: qwen-image-2.1-turbo.",
|
||||
on_hires_sigmas_arg},
|
||||
{"",
|
||||
"--skip-layers",
|
||||
@@ -2271,14 +2327,15 @@ bool SDGenerationParams::from_json_str(
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_dir) {
|
||||
bool SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_dir) {
|
||||
if (lora_model_dir.empty()) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
static const std::regex re(R"(<lora:([^:>]+):([^>]+)>)");
|
||||
static const std::vector<std::string> valid_ext = {".gguf", ".safetensors", ".pt", ".ckpt"};
|
||||
std::smatch m;
|
||||
|
||||
bool missing = false;
|
||||
std::string tmp = prompt;
|
||||
|
||||
while (std::regex_search(tmp, m, re)) {
|
||||
@@ -2320,8 +2377,9 @@ void SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_d
|
||||
}
|
||||
if (!found) {
|
||||
LOG_WARN("can not found lora %s", final_path.lexically_normal().string().c_str());
|
||||
tmp = m.suffix().str();
|
||||
prompt = std::regex_replace(prompt, re, "", std::regex_constants::format_first_only);
|
||||
tmp = m.suffix().str();
|
||||
prompt = std::regex_replace(prompt, re, "", std::regex_constants::format_first_only);
|
||||
missing = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -2337,6 +2395,8 @@ void SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_d
|
||||
|
||||
tmp = m.suffix().str();
|
||||
}
|
||||
|
||||
return !missing;
|
||||
}
|
||||
|
||||
bool SDGenerationParams::width_and_height_are_set() const {
|
||||
@@ -2458,7 +2518,7 @@ bool SDGenerationParams::initialize_cache_params() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SDGenerationParams::resolve(const std::string& lora_model_dir, const std::string& hires_upscalers_dir, bool strict) {
|
||||
bool SDGenerationParams::resolve(const std::string& lora_model_dir, const std::string& hires_upscalers_dir, bool strict, bool validate_missing_loras) {
|
||||
vae_tiling_params.extra_tiling_args = extra_tiling_args.empty() ? nullptr : extra_tiling_args.c_str();
|
||||
|
||||
if (high_noise_sample_params.sample_steps <= 0) {
|
||||
@@ -2502,7 +2562,9 @@ bool SDGenerationParams::resolve(const std::string& lora_model_dir, const std::s
|
||||
|
||||
prompt_with_lora = prompt;
|
||||
if (!lora_model_dir.empty()) {
|
||||
extract_and_remove_lora(lora_model_dir);
|
||||
if (!extract_and_remove_lora(lora_model_dir) && validate_missing_loras) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -2630,7 +2692,7 @@ bool SDGenerationParams::resolve_and_validate(SDMode mode,
|
||||
const std::string& lora_model_dir,
|
||||
const std::string& hires_upscalers_dir,
|
||||
bool strict) {
|
||||
if (!resolve(lora_model_dir, hires_upscalers_dir, strict)) {
|
||||
if (!resolve(lora_model_dir, hires_upscalers_dir, strict, mode == CONVERT)) {
|
||||
return false;
|
||||
}
|
||||
if (!validate(mode)) {
|
||||
|
||||
@@ -157,6 +157,7 @@ struct SDContextParams {
|
||||
bool disable_prefetch = false;
|
||||
bool disable_segmented_compute = false;
|
||||
bool eager_load = false;
|
||||
bool batched_cfg = true;
|
||||
std::string backend;
|
||||
std::string params_backend;
|
||||
std::string split_mode;
|
||||
@@ -165,12 +166,12 @@ struct SDContextParams {
|
||||
std::string rpc_servers;
|
||||
std::string effective_backend;
|
||||
std::string effective_params_backend;
|
||||
bool enable_mmap = false;
|
||||
bool control_net_cpu = false;
|
||||
bool clip_on_cpu = false;
|
||||
bool vae_on_cpu = false;
|
||||
bool flash_attn = false;
|
||||
bool diffusion_flash_attn = false;
|
||||
bool enable_mmap = false;
|
||||
bool control_net_cpu = false;
|
||||
bool clip_on_cpu = false;
|
||||
bool vae_on_cpu = false;
|
||||
std::string flash_attn_mode = "auto";
|
||||
std::string conv_direct_mode; // Empty selects auto while honoring legacy convolution flags.
|
||||
bool sage_attn = false;
|
||||
bool diffusion_conv_direct = false;
|
||||
bool vae_conv_direct = false;
|
||||
@@ -313,12 +314,12 @@ struct SDGenerationParams {
|
||||
const std::function<std::string(const std::string&)>& lora_path_resolver = {});
|
||||
bool parse_image_preprocess_json(const std::string& json_str);
|
||||
bool initialize_cache_params();
|
||||
void extract_and_remove_lora(const std::string& lora_model_dir);
|
||||
bool extract_and_remove_lora(const std::string& lora_model_dir);
|
||||
bool width_and_height_are_set() const;
|
||||
void set_width_and_height_if_unset(int w, int h);
|
||||
int get_resolved_width() const;
|
||||
int get_resolved_height() const;
|
||||
bool resolve(const std::string& lora_model_dir, const std::string& hires_upscalers_dir, bool strict = false);
|
||||
bool resolve(const std::string& lora_model_dir, const std::string& hires_upscalers_dir, bool strict = false, bool validate_missing_loras = false);
|
||||
bool validate(SDMode mode);
|
||||
bool resolve_and_validate(SDMode mode,
|
||||
const std::string& lora_model_dir,
|
||||
|
||||
+18
-21
@@ -91,34 +91,34 @@ void log_print(enum sd_log_level_t level, const char* log, sd_log_level_t min_le
|
||||
switch (level) {
|
||||
case SD_LOG_DEBUG:
|
||||
tag_color = 37;
|
||||
level_str = "DEBUG";
|
||||
level_str = "D";
|
||||
break;
|
||||
case SD_LOG_VERBOSE:
|
||||
tag_color = 37;
|
||||
level_str = "VERBOSE";
|
||||
level_str = "V";
|
||||
break;
|
||||
case SD_LOG_INFO:
|
||||
tag_color = 34;
|
||||
level_str = "INFO";
|
||||
level_str = "I";
|
||||
break;
|
||||
case SD_LOG_WARN:
|
||||
tag_color = 35;
|
||||
level_str = "WARN";
|
||||
level_str = "W";
|
||||
break;
|
||||
case SD_LOG_ERROR:
|
||||
tag_color = 31;
|
||||
level_str = "ERROR";
|
||||
level_str = "E";
|
||||
break;
|
||||
default:
|
||||
tag_color = 33;
|
||||
level_str = "?????";
|
||||
level_str = "?";
|
||||
break;
|
||||
}
|
||||
|
||||
if (color) {
|
||||
fprintf(out_stream, "\033[%d;1m[%-7s]\033[0m ", tag_color, level_str);
|
||||
fprintf(out_stream, "\033[%d;1m[%s]\033[0m ", tag_color, level_str);
|
||||
} else {
|
||||
fprintf(out_stream, "[%-7s] ", level_str);
|
||||
fprintf(out_stream, "[%s] ", level_str);
|
||||
}
|
||||
fflush(out_stream);
|
||||
print_utf8(out_stream, log);
|
||||
@@ -131,18 +131,15 @@ void example_log_printf(sd_log_level_t level, const char* file, int line, const
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
static char log_buffer[LOG_BUFFER_SIZE + 1];
|
||||
int written = snprintf(log_buffer, LOG_BUFFER_SIZE, "%s:%-4d - ", sd_basename(file).c_str(), line);
|
||||
|
||||
if (written >= 0 && written < static_cast<int>(LOG_BUFFER_SIZE)) {
|
||||
vsnprintf(log_buffer + written, LOG_BUFFER_SIZE - written, format, args);
|
||||
}
|
||||
size_t len = strlen(log_buffer);
|
||||
if (len == 0 || log_buffer[len - 1] != '\n') {
|
||||
strncat(log_buffer, "\n", LOG_BUFFER_SIZE - len);
|
||||
}
|
||||
|
||||
log_print(level, log_buffer, log_level, log_color);
|
||||
|
||||
char log_buffer[LOG_BUFFER_SIZE + 1] = {};
|
||||
vsnprintf(log_buffer, sizeof(log_buffer), format, args);
|
||||
va_end(args);
|
||||
|
||||
std::string message = log_buffer;
|
||||
while (!message.empty() && (message.back() == '\n' || message.back() == '\r')) {
|
||||
message.pop_back();
|
||||
}
|
||||
message += " --- " + sd_basename(file) + ":" + std::to_string(line) + '\n';
|
||||
|
||||
log_print(level, message.c_str(), log_level, log_color);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
The following example starts `sd-server` with a standalone diffusion model, VAE, and LLM text encoder:
|
||||
|
||||
```
|
||||
.\bin\Release\sd-server.exe --diffusion-model ..\models\diffusion_models\z_image_turbo_bf16.safetensors --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\qwen_3_4b.safetensors --diffusion-fa --offload-to-cpu -v --cfg-scale 1.0
|
||||
.\bin\Release\sd-server.exe --diffusion-model ..\models\diffusion_models\z_image_turbo_bf16.safetensors --vae ..\models\vae\ae.sft --llm ..\models\text_encoders\qwen_3_4b.safetensors --offload-to-cpu -v --cfg-scale 1.0
|
||||
```
|
||||
|
||||
What this example does:
|
||||
@@ -11,7 +11,6 @@ What this example does:
|
||||
* `--diffusion-model` selects the standalone diffusion model
|
||||
* `--vae` selects the VAE decoder
|
||||
* `--llm` selects the text encoder / language model used by this pipeline
|
||||
* `--diffusion-fa` enables flash attention in the diffusion model
|
||||
* `--offload-to-cpu` reduces VRAM pressure by keeping weights in RAM when possible
|
||||
* `-v` enables verbose logging (equivalent to `--log-level verbose`)
|
||||
* `--cfg-scale 1.0` sets the default CFG scale for generation
|
||||
|
||||
+68
-1
@@ -37,6 +37,7 @@ Current generation-related endpoints include:
|
||||
|
||||
- `POST /sdapi/v1/txt2img`
|
||||
- `POST /sdapi/v1/img2img`
|
||||
- `GET /sdapi/v1/progress`
|
||||
- `GET /sdapi/v1/loras`
|
||||
- `GET /sdapi/v1/upscalers`
|
||||
- `GET /sdapi/v1/latent-upscale-modes`
|
||||
@@ -278,6 +279,26 @@ Response fields:
|
||||
| `parameters` | `object` | Echo of the parsed outer request body |
|
||||
| `info` | `string` | Currently empty string |
|
||||
|
||||
#### `GET /sdapi/v1/progress`
|
||||
|
||||
Poll this endpoint while a synchronous SDAPI generation request is running.
|
||||
An optional `id_task` query parameter selects the task with the same `id_task`
|
||||
provided in the generation request (default: `"sdapi"`). Set
|
||||
`skip_current_image=true` or `1` to omit the preview image.
|
||||
|
||||
`current_image` contains the latest base64 JPEG latent projection, or `null`
|
||||
when unavailable. `state.sampling_step` is the positive logical step and
|
||||
`state.sampling_steps` is the actual step count for the current sampling pass,
|
||||
including schedule and img2img strength adjustments. `state.job_no` is the
|
||||
zero-based pass index; `state.job_count` includes one pass per batch image and
|
||||
an additional pass per image when highres fix is enabled. `progress` weights
|
||||
these passes equally. Sampling progress can reach `1` before final decoding
|
||||
finishes; `eta_relative` is currently always `0`.
|
||||
|
||||
When no SDAPI task is active, or `id_task` does not match, the endpoint returns
|
||||
`progress=0`, `state.job=""`, zero job/step counts, and `current_image=null`.
|
||||
Completed and failed requests do not retain an active preview.
|
||||
|
||||
#### Discovery / Compatibility Endpoints
|
||||
|
||||
Currently exposed:
|
||||
@@ -410,6 +431,20 @@ Field types:
|
||||
| `queue_position` | `integer` |
|
||||
| `result` | `object \| null` |
|
||||
| `error` | `object \| null` |
|
||||
| `preview` | `object \| null` |
|
||||
|
||||
`preview` sub-fields:
|
||||
|
||||
| Field | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `preview.pass` | `integer` | Sampling pass number, starting at 1 within the job |
|
||||
| `preview.step` | `integer` | Positive logical sample step within this pass |
|
||||
| `preview.total_steps` | `integer` | Actual sample steps in this pass, after schedule and strength adjustments |
|
||||
| `preview.b64_json` | `string` | Base64-encoded PNG of the current preview frame |
|
||||
|
||||
The preview is updated in place as sampling progresses; poll `GET /sdcpp/v1/jobs/{id}` to retrieve the latest frame. Only the most recent preview is retained.
|
||||
|
||||
`step / total_steps` describes the current sampling pass, not overall job completion. A new batch item, high/low-noise stage, or highres pass increments `pass` and restarts `step`. Samplers with multiple denoiser evaluations per logical step may update a preview more than once at the same step. The frame and its pass/step metadata are published together.
|
||||
|
||||
### Endpoints
|
||||
|
||||
@@ -437,6 +472,7 @@ Top-level fields:
|
||||
| `loras` | `array<object>` | Available LoRA entries |
|
||||
| `upscalers` | `array<object>` | Available highres upscalers, built-in and model-backed |
|
||||
| `upscale` | `boolean` | Whether a compatible RGB ESRGAN model is available for `POST /sdcpp/v1/upscale` |
|
||||
| `preview_modes` | `array<string>` | Available preview modes, e.g. `["none", "proj", "tae", "vae"]` |
|
||||
| `limits` | `object` | Shared queue and size limits |
|
||||
|
||||
`model`
|
||||
@@ -598,6 +634,7 @@ Fields returned in `features_by_mode.img_gen`:
|
||||
- `cache`
|
||||
- `cancel_queued`
|
||||
- `cancel_generating`
|
||||
- `preview`
|
||||
|
||||
Fields returned in `features_by_mode.vid_gen`:
|
||||
|
||||
@@ -610,6 +647,7 @@ Fields returned in `features_by_mode.vid_gen`:
|
||||
- `cache`
|
||||
- `cancel_queued`
|
||||
- `cancel_generating`
|
||||
- `preview`
|
||||
|
||||
#### `POST /sdcpp/v1/img_gen`
|
||||
|
||||
@@ -782,6 +820,9 @@ Example:
|
||||
"scm_mask": "",
|
||||
"scm_policy_dynamic": true,
|
||||
|
||||
"preview": "none",
|
||||
"preview_interval": 1,
|
||||
|
||||
"output_format": "png",
|
||||
"output_compression": 100
|
||||
}
|
||||
@@ -847,6 +888,8 @@ Top-level scalar fields:
|
||||
| `control_strength` | `number` |
|
||||
| `ip_adapter_strength` | `number` |
|
||||
| `embed_image_metadata` | `boolean` |
|
||||
| `preview` | `string` |
|
||||
| `preview_interval` | `integer` |
|
||||
|
||||
Image fields:
|
||||
|
||||
@@ -934,6 +977,10 @@ When omitted, backend defaults apply to these fields:
|
||||
- `sample_params.flow_shift`
|
||||
- `sample_params.guidance.img_cfg`
|
||||
|
||||
### Preview Interval Semantics
|
||||
|
||||
`preview_interval` controls the period (in sample steps) at which preview frames are generated. The default is `1` (every step). Any non-positive values will be clamped to `1`. Note that if `preview` is set to `"none"`, no previews are produced regardless of the interval.
|
||||
|
||||
### Completion Result
|
||||
|
||||
Example completed job:
|
||||
@@ -1048,7 +1095,8 @@ Response fields:
|
||||
Compared with `img_gen`, the `vid_gen` request body:
|
||||
|
||||
- `vid_gen` is a single video sequence job, so `batch_count` is not part of the request schema
|
||||
- `ref_images`, `mask_image`, `control_image`, `control_strength`, `ip_adapter_image`, `ip_adapter_strength`, and `embed_image_metadata` are not part of the request schema
|
||||
- `mask_image`, `control_image`, `control_strength`, `ip_adapter_image`, `ip_adapter_strength`, and `embed_image_metadata` are not part of the request schema
|
||||
- `ref_images` is accepted for MiniMax-H3 Ref2VA conditioning; other video model families currently ignore it
|
||||
- `vid_gen` adds `end_image`, `control_frames`, `high_noise_sample_params`, `video_frames`, `fps`, `moe_boundary`, and `vace_strength`
|
||||
|
||||
Example:
|
||||
@@ -1069,6 +1117,7 @@ Example:
|
||||
|
||||
"init_image": null,
|
||||
"end_image": null,
|
||||
"ref_images": [],
|
||||
"control_frames": [],
|
||||
|
||||
"sample_params": {
|
||||
@@ -1130,11 +1179,25 @@ Example:
|
||||
"scm_mask": "",
|
||||
"scm_policy_dynamic": true,
|
||||
|
||||
"preview": "none",
|
||||
"preview_interval": 1,
|
||||
|
||||
"output_format": "webm",
|
||||
"output_compression": 100
|
||||
}
|
||||
```
|
||||
|
||||
### Reference Image Rules
|
||||
|
||||
- `ref_images` contains reference images for MiniMax-H3 Ref2VA conditioning.
|
||||
- Images retain request order and correspond to `<Picture 1>`, `<Picture 2>`, and so on in the prompt.
|
||||
- For MiniMax-H3, non-empty `ref_images` cannot be combined with `init_image` or `end_image`. A conflicting API request fails during generation; the WebUI checks this before submission.
|
||||
- MiniMax-H3 does not support `control_frames`; leave that array empty.
|
||||
- Other video model families currently ignore `ref_images`.
|
||||
- `features_by_mode.vid_gen` does not currently advertise `ref_images`; its absence is not an indication that MiniMax-H3 reference images are unsupported.
|
||||
|
||||
See [MiniMax-H3 reference conditioning](../../docs/minimax_h3.md#reference-to-audio-video-conditioning) for model requirements and prompt examples.
|
||||
|
||||
### LoRA Rules
|
||||
|
||||
- The server only accepts explicit LoRA entries from the `lora` field.
|
||||
@@ -1152,6 +1215,7 @@ Channel expectations:
|
||||
|
||||
- `init_image`: 3 channels
|
||||
- `end_image`: 3 channels
|
||||
- `ref_images[]`: decoded with native channels, then converted to RGB by MiniMax-H3
|
||||
- `control_frames[]`: 3 channels
|
||||
|
||||
Frame ordering rules:
|
||||
@@ -1181,6 +1245,8 @@ Top-level scalar fields:
|
||||
| `fps` | `integer` |
|
||||
| `moe_boundary` | `number` |
|
||||
| `vace_strength` | `number` |
|
||||
| `preview` | `string` |
|
||||
| `preview_interval` | `integer` |
|
||||
|
||||
Image and frame fields:
|
||||
|
||||
@@ -1188,6 +1254,7 @@ Image and frame fields:
|
||||
| --- | --- |
|
||||
| `init_image` | `string \| null` |
|
||||
| `end_image` | `string \| null` |
|
||||
| `ref_images` | `array<string>` |
|
||||
| `control_frames` | `array<string>` |
|
||||
|
||||
LoRA fields:
|
||||
|
||||
@@ -10,6 +10,55 @@
|
||||
#include "common/media_io.h"
|
||||
#include "common/resource_owners.hpp"
|
||||
|
||||
static void preview_callback(int step, int frame_count, sd_image_t* frames, bool is_noisy, void* data) {
|
||||
(void)is_noisy;
|
||||
if (frame_count <= 0 || frames == nullptr || frames[0].data == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncGenerationJob* job = static_cast<AsyncGenerationJob*>(data);
|
||||
const sd_image_t& frame = frames[0];
|
||||
const sd_preview_info_t info = sd_get_preview_info();
|
||||
|
||||
auto image_bytes = encode_image_to_vector(EncodedImageFormat::PNG,
|
||||
frame.data,
|
||||
frame.width,
|
||||
frame.height,
|
||||
frame.channel,
|
||||
"",
|
||||
80);
|
||||
if (image_bytes.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string b64 = base64_encode(image_bytes);
|
||||
std::lock_guard<std::mutex> lock(job->preview_mutex);
|
||||
job->preview_b64 = std::move(b64);
|
||||
job->preview_step = step < 0 ? -step : step;
|
||||
job->preview_pass = info.sample_pass;
|
||||
job->preview_total_steps = info.total_steps;
|
||||
}
|
||||
|
||||
static void clear_preview_callback() {
|
||||
sd_set_preview_callback(nullptr, PREVIEW_NONE, 1, false, false, nullptr);
|
||||
}
|
||||
|
||||
static void set_preview_callback(AsyncGenerationJob& job) {
|
||||
preview_t mode = str_to_preview(job.kind == AsyncJobKind::ImgGen
|
||||
? job.img_gen.preview_mode.c_str()
|
||||
: job.vid_gen.preview_mode.c_str());
|
||||
if (mode == PREVIEW_COUNT || mode == PREVIEW_NONE) {
|
||||
return;
|
||||
}
|
||||
int interval = job.kind == AsyncJobKind::ImgGen
|
||||
? job.img_gen.preview_interval
|
||||
: job.vid_gen.preview_interval;
|
||||
if (interval <= 0) {
|
||||
interval = 1;
|
||||
}
|
||||
sd_set_preview_callback(preview_callback, mode, interval, true, false, &job);
|
||||
}
|
||||
|
||||
const char* async_job_kind_name(AsyncJobKind kind) {
|
||||
switch (kind) {
|
||||
case AsyncJobKind::ImgGen:
|
||||
@@ -161,6 +210,18 @@ json make_async_job_json(const AsyncJobManager& manager, const AsyncGenerationJo
|
||||
result["error"] = nullptr;
|
||||
}
|
||||
|
||||
if (job.status == AsyncJobStatus::Generating) {
|
||||
std::lock_guard<std::mutex> lock(job.preview_mutex);
|
||||
if (!job.preview_b64.empty()) {
|
||||
result["preview"] = {
|
||||
{"pass", job.preview_pass},
|
||||
{"step", job.preview_step},
|
||||
{"total_steps", job.preview_total_steps},
|
||||
{"b64_json", job.preview_b64},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -174,12 +235,14 @@ bool execute_img_gen_job(ServerRuntime& runtime,
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
|
||||
set_preview_callback(job);
|
||||
sd_image_t* raw_results = nullptr;
|
||||
int num_results = 0;
|
||||
if (!generate_image(runtime.sd_ctx, ¶ms, &raw_results, &num_results)) {
|
||||
raw_results = nullptr;
|
||||
num_results = 0;
|
||||
}
|
||||
clear_preview_callback();
|
||||
results.adopt(raw_results, num_results);
|
||||
}
|
||||
|
||||
@@ -247,10 +310,12 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
|
||||
set_preview_callback(job);
|
||||
sd_image_t* raw_results = nullptr;
|
||||
if (!generate_video(runtime.sd_ctx, ¶ms, &raw_results, &num_results, &generated_audio, &output_fps)) {
|
||||
raw_results = nullptr;
|
||||
}
|
||||
clear_preview_callback();
|
||||
results.adopt(raw_results, num_results);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,11 @@ struct AsyncGenerationJob {
|
||||
int result_fps = 0;
|
||||
std::string error_code;
|
||||
std::string error_message;
|
||||
mutable std::mutex preview_mutex;
|
||||
std::string preview_b64;
|
||||
int preview_pass = 0;
|
||||
int preview_step = 0;
|
||||
int preview_total_steps = 0;
|
||||
};
|
||||
|
||||
struct AsyncJobManager {
|
||||
|
||||
+1
-1
Submodule examples/server/frontend updated: dd74a8e808...8e7c115059
@@ -75,6 +75,8 @@ int main(int argc, const char** argv) {
|
||||
}
|
||||
SDSvrParams svr_params;
|
||||
SDContextParams ctx_params;
|
||||
// Preserve the CLI default while disabling cwd scans for sd-server.
|
||||
ctx_params.lora_model_dir.clear();
|
||||
SDGenerationParams default_gen_params;
|
||||
|
||||
sd_set_log_callback(sd_log_cb, (void*)&svr_params);
|
||||
|
||||
@@ -301,6 +301,76 @@ static nlohmann::json prepare_info_field(const SDContextParams& ctx_params,
|
||||
return jsoninfo;
|
||||
}
|
||||
|
||||
struct SDAPIPreviewState {
|
||||
std::mutex mutex;
|
||||
std::string b64;
|
||||
std::string id_task;
|
||||
std::string job_type;
|
||||
int step = 0;
|
||||
int total = 0;
|
||||
bool active = false;
|
||||
int64_t job_count = 0;
|
||||
int job_no = 0;
|
||||
};
|
||||
|
||||
static SDAPIPreviewState g_preview;
|
||||
|
||||
static void preview_callback(int step, int frame_count, sd_image_t* frames, bool is_noisy, void* data) {
|
||||
(void)is_noisy;
|
||||
(void)data;
|
||||
if (frame_count <= 0 || frames == nullptr || frames[0].data == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sd_image_t& frame = frames[0];
|
||||
const sd_preview_info_t info = sd_get_preview_info();
|
||||
|
||||
auto image_bytes = encode_image_to_vector(EncodedImageFormat::JPEG,
|
||||
frame.data,
|
||||
frame.width,
|
||||
frame.height,
|
||||
frame.channel,
|
||||
"",
|
||||
80);
|
||||
if (image_bytes.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string b64 = base64_encode(image_bytes);
|
||||
std::lock_guard<std::mutex> lock(g_preview.mutex);
|
||||
g_preview.b64 = std::move(b64);
|
||||
g_preview.step = static_cast<int>(std::min<int64_t>(step < 0 ? -static_cast<int64_t>(step) : step,
|
||||
std::max(0, info.total_steps)));
|
||||
g_preview.total = std::max(0, info.total_steps);
|
||||
g_preview.job_no = std::max(0, info.sample_pass - 1);
|
||||
}
|
||||
|
||||
struct SDAPIPreviewGuard {
|
||||
SDAPIPreviewGuard(const std::string& id_task, const sd_img_gen_params_t& params, const std::string& job_type) {
|
||||
std::lock_guard<std::mutex> lock(g_preview.mutex);
|
||||
g_preview.id_task = id_task;
|
||||
g_preview.total = 0;
|
||||
g_preview.job_type = job_type;
|
||||
g_preview.b64.clear();
|
||||
g_preview.step = 0;
|
||||
g_preview.active = true;
|
||||
// Each batch item has a base pass and, when enabled, a hires pass.
|
||||
g_preview.job_count = static_cast<int64_t>(std::max(1, params.batch_count)) * (params.hires.enabled ? 2 : 1);
|
||||
g_preview.job_no = 0;
|
||||
sd_set_preview_callback(preview_callback, PREVIEW_PROJ, 1, true, false, nullptr);
|
||||
}
|
||||
|
||||
~SDAPIPreviewGuard() {
|
||||
sd_set_preview_callback(nullptr, PREVIEW_NONE, 1, false, false, nullptr);
|
||||
std::lock_guard<std::mutex> lock(g_preview.mutex);
|
||||
g_preview.active = false;
|
||||
g_preview.b64.clear();
|
||||
}
|
||||
|
||||
SDAPIPreviewGuard(const SDAPIPreviewGuard&) = delete;
|
||||
SDAPIPreviewGuard& operator=(const SDAPIPreviewGuard&) = delete;
|
||||
};
|
||||
|
||||
void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
ServerRuntime* runtime = &rt;
|
||||
|
||||
@@ -317,7 +387,9 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
return;
|
||||
}
|
||||
|
||||
json j = json::parse(req.body);
|
||||
std::string id_task = "sdapi";
|
||||
json j = json::parse(req.body);
|
||||
id_task = j.value("id_task", id_task);
|
||||
ImgGenJobRequest request;
|
||||
std::string error_message;
|
||||
if (!build_sdapi_img_gen_request(j, *runtime, img2img, request, error_message)) {
|
||||
@@ -334,6 +406,7 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime->sd_ctx_mutex);
|
||||
SDAPIPreviewGuard preview_guard(id_task, img_gen_params, img2img ? "img2img" : "txt2img");
|
||||
sd_image_t* raw_results = nullptr;
|
||||
if (!generate_image(runtime->sd_ctx, &img_gen_params, &raw_results, &num_results)) {
|
||||
raw_results = nullptr;
|
||||
@@ -528,4 +601,62 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
r["sd_model_checkpoint"] = model_path.stem();
|
||||
res.set_content(r.dump(), "application/json");
|
||||
});
|
||||
|
||||
svr.Get("/sdapi/v1/progress", [](const httplib::Request& req, httplib::Response& res) {
|
||||
bool skip_current_image = false;
|
||||
bool has_id_param = req.has_param("id_task");
|
||||
std::string req_id_task = has_id_param ? req.get_param_value("id_task") : "";
|
||||
|
||||
if (req.has_param("skip_current_image")) {
|
||||
std::string skip_str = req.get_param_value("skip_current_image");
|
||||
skip_current_image = (skip_str == "true" || skip_str == "1");
|
||||
}
|
||||
|
||||
json r;
|
||||
r["eta_relative"] = 0.0f;
|
||||
json state;
|
||||
state["skipped"] = false;
|
||||
state["interrupted"] = false;
|
||||
state["job_timestamp"] = "0";
|
||||
r["textinfo"] = nullptr;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_preview.mutex);
|
||||
|
||||
bool is_matching_task = g_preview.active && (!has_id_param || req_id_task == g_preview.id_task);
|
||||
|
||||
int step = is_matching_task ? g_preview.step : 0;
|
||||
int steps = is_matching_task ? g_preview.total : 0;
|
||||
std::string b64 = is_matching_task ? g_preview.b64 : "";
|
||||
std::string job_type = is_matching_task ? g_preview.job_type : "";
|
||||
int64_t job_count = is_matching_task ? g_preview.job_count : 0;
|
||||
int job_no = is_matching_task ? g_preview.job_no : 0;
|
||||
|
||||
float batch_progress = (steps > 0) ? (static_cast<float>(step) / static_cast<float>(steps)) : 0.0f;
|
||||
if (batch_progress > 1.0f)
|
||||
batch_progress = 1.0f;
|
||||
|
||||
float overall_progress = job_count > 0
|
||||
? (static_cast<float>(job_no) + batch_progress) / static_cast<float>(job_count)
|
||||
: 0.0f;
|
||||
if (overall_progress > 1.0f)
|
||||
overall_progress = 1.0f;
|
||||
|
||||
r["progress"] = overall_progress;
|
||||
state["job"] = job_type;
|
||||
state["sampling_step"] = step;
|
||||
state["sampling_steps"] = steps;
|
||||
state["job_count"] = job_count;
|
||||
state["job_no"] = job_no;
|
||||
r["state"] = state;
|
||||
|
||||
if (!skip_current_image && !b64.empty()) {
|
||||
r["current_image"] = b64;
|
||||
} else {
|
||||
r["current_image"] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
res.set_content(r.dump(), "application/json");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@ static json make_img_gen_features_json() {
|
||||
{"cache", true},
|
||||
{"cancel_queued", true},
|
||||
{"cancel_generating", false},
|
||||
{"preview", true},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -219,9 +220,18 @@ static json make_vid_gen_features_json() {
|
||||
{"cache", true},
|
||||
{"cancel_queued", true},
|
||||
{"cancel_generating", false},
|
||||
{"preview", true},
|
||||
};
|
||||
}
|
||||
|
||||
static json make_preview_modes_json() {
|
||||
json modes = json::array();
|
||||
for (int i = 0; i < PREVIEW_COUNT; ++i) {
|
||||
modes.push_back(sd_preview_name((preview_t)i));
|
||||
}
|
||||
return modes;
|
||||
}
|
||||
|
||||
static json make_capabilities_json(ServerRuntime& runtime) {
|
||||
refresh_lora_cache(runtime);
|
||||
refresh_upscaler_cache(runtime);
|
||||
@@ -396,6 +406,7 @@ static json make_capabilities_json(ServerRuntime& runtime) {
|
||||
result["loras"] = available_loras;
|
||||
result["upscalers"] = available_upscalers;
|
||||
result["upscale"] = have_upscaler_models;
|
||||
result["preview_modes"] = make_preview_modes_json();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -418,6 +429,8 @@ static bool parse_img_gen_request(const json& body,
|
||||
if (!assign_output_options(request, output_format, output_compression, true, error_message)) {
|
||||
return false;
|
||||
}
|
||||
request.preview_mode = body.value("preview", std::string("none"));
|
||||
request.preview_interval = body.value("preview_interval", 1);
|
||||
// Intentionally disable prompt-embedded LoRA tag parsing for server APIs.
|
||||
if (!request.gen_params.resolve_and_validate(IMG_GEN, "", runtime.ctx_params->hires_upscalers_dir, true)) {
|
||||
error_message = "invalid generation parameters";
|
||||
@@ -445,6 +458,8 @@ static bool parse_vid_gen_request(const json& body,
|
||||
if (!assign_output_options(request, output_format, output_compression, error_message)) {
|
||||
return false;
|
||||
}
|
||||
request.preview_mode = body.value("preview", std::string("none"));
|
||||
request.preview_interval = body.value("preview_interval", 1);
|
||||
// Intentionally disable prompt-embedded LoRA tag parsing for server APIs.
|
||||
if (!request.gen_params.resolve_and_validate(VID_GEN, "", runtime.ctx_params->hires_upscalers_dir, true)) {
|
||||
error_message = "invalid generation parameters";
|
||||
|
||||
+103
-45
@@ -254,28 +254,55 @@ std::string SDSvrParams::to_string() const {
|
||||
void refresh_lora_cache(ServerRuntime& rt) {
|
||||
std::vector<LoraEntry> new_cache;
|
||||
|
||||
if (rt.ctx_params->lora_model_dir.empty()) {
|
||||
std::lock_guard<std::mutex> lock(*rt.lora_mutex);
|
||||
rt.lora_cache->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
fs::path lora_dir = rt.ctx_params->lora_model_dir;
|
||||
if (fs::exists(lora_dir) && fs::is_directory(lora_dir)) {
|
||||
for (auto& entry : fs::recursive_directory_iterator(lora_dir, fs::directory_options::skip_permission_denied)) {
|
||||
if (!entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
const fs::path& p = entry.path();
|
||||
if (!is_supported_model_ext(p)) {
|
||||
continue;
|
||||
}
|
||||
std::error_code ec;
|
||||
if (fs::exists(lora_dir, ec) && !ec && fs::is_directory(lora_dir, ec) && !ec) {
|
||||
try {
|
||||
auto it = fs::recursive_directory_iterator(
|
||||
lora_dir,
|
||||
fs::directory_options::skip_permission_denied,
|
||||
ec);
|
||||
auto end = fs::recursive_directory_iterator();
|
||||
while (!ec && it != end) {
|
||||
std::error_code entry_ec;
|
||||
bool is_reg = it->is_regular_file(entry_ec);
|
||||
if (entry_ec || !is_reg) {
|
||||
it.increment(ec);
|
||||
continue;
|
||||
}
|
||||
const fs::path& p = it->path();
|
||||
if (!is_supported_model_ext(p)) {
|
||||
it.increment(ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
LoraEntry lora_entry;
|
||||
lora_entry.name = p.stem().u8string();
|
||||
lora_entry.fullpath = p.u8string();
|
||||
std::string rel = p.lexically_relative(lora_dir).u8string();
|
||||
std::replace(rel.begin(), rel.end(), '\\', '/');
|
||||
lora_entry.path = rel;
|
||||
LoraEntry lora_entry;
|
||||
lora_entry.name = p.stem().u8string();
|
||||
lora_entry.fullpath = p.u8string();
|
||||
std::string rel = p.lexically_relative(lora_dir).u8string();
|
||||
std::replace(rel.begin(), rel.end(), '\\', '/');
|
||||
lora_entry.path = rel;
|
||||
|
||||
new_cache.push_back(std::move(lora_entry));
|
||||
new_cache.push_back(std::move(lora_entry));
|
||||
it.increment(ec);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_WARN("error while scanning lora directory '%s': %s", lora_dir.string().c_str(), e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
LOG_WARN("error while scanning lora directory '%s': %s", lora_dir.string().c_str(), ec.message().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(new_cache.begin(), new_cache.end(), [](const LoraEntry& a, const LoraEntry& b) {
|
||||
return a.path < b.path;
|
||||
});
|
||||
@@ -302,40 +329,71 @@ void refresh_upscaler_cache(ServerRuntime& rt) {
|
||||
}
|
||||
|
||||
fs::path upscaler_dir = rt.ctx_params->hires_upscalers_dir;
|
||||
if (fs::exists(upscaler_dir) && fs::is_directory(upscaler_dir)) {
|
||||
for (auto& entry : fs::directory_iterator(upscaler_dir)) {
|
||||
if (!entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
const fs::path& p = entry.path();
|
||||
if (!is_supported_model_ext(p)) {
|
||||
continue;
|
||||
}
|
||||
std::error_code ec;
|
||||
if (fs::exists(upscaler_dir, ec) && !ec && fs::is_directory(upscaler_dir, ec) && !ec) {
|
||||
try {
|
||||
auto it = fs::directory_iterator(
|
||||
upscaler_dir,
|
||||
fs::directory_options::skip_permission_denied,
|
||||
ec);
|
||||
auto end = fs::directory_iterator();
|
||||
while (!ec && it != end) {
|
||||
std::error_code entry_ec;
|
||||
bool is_reg = it->is_regular_file(entry_ec);
|
||||
if (entry_ec || !is_reg) {
|
||||
it.increment(ec);
|
||||
continue;
|
||||
}
|
||||
const fs::path& p = it->path();
|
||||
if (!is_supported_model_ext(p)) {
|
||||
it.increment(ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
UpscalerEntry upscaler_entry;
|
||||
upscaler_entry.name = p.stem().u8string();
|
||||
upscaler_entry.fullpath = fs::absolute(p).lexically_normal().u8string();
|
||||
upscaler_entry.model_name = "ESRGAN_4x";
|
||||
upscaler_entry.path = p.filename().u8string();
|
||||
upscaler_entry.file_size = entry.file_size();
|
||||
upscaler_entry.last_modified = entry.last_write_time();
|
||||
auto previous = std::find_if(previous_cache.begin(), previous_cache.end(), [&](const UpscalerEntry& cached) {
|
||||
return cached.fullpath == upscaler_entry.fullpath &&
|
||||
cached.file_size == upscaler_entry.file_size &&
|
||||
cached.last_modified == upscaler_entry.last_modified;
|
||||
});
|
||||
upscaler_entry.image_upscale_factor = previous != previous_cache.end()
|
||||
? previous->image_upscale_factor
|
||||
: get_upscaler_model_scale(upscaler_entry.fullpath.c_str());
|
||||
if (upscaler_entry.image_upscale_factor > 0) {
|
||||
upscaler_entry.scale = upscaler_entry.image_upscale_factor;
|
||||
upscaler_entry.model_name = "ESRGAN_" + std::to_string(upscaler_entry.scale) + "x";
|
||||
}
|
||||
UpscalerEntry upscaler_entry;
|
||||
upscaler_entry.name = p.stem().u8string();
|
||||
std::error_code abs_ec;
|
||||
fs::path abs_path = fs::absolute(p, abs_ec);
|
||||
upscaler_entry.fullpath = (abs_ec ? p : abs_path.lexically_normal()).u8string();
|
||||
upscaler_entry.model_name = "ESRGAN_4x";
|
||||
upscaler_entry.path = p.filename().u8string();
|
||||
|
||||
new_cache.push_back(std::move(upscaler_entry));
|
||||
std::error_code size_ec;
|
||||
upscaler_entry.file_size = it->file_size(size_ec);
|
||||
if (size_ec) {
|
||||
it.increment(ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::error_code time_ec;
|
||||
upscaler_entry.last_modified = it->last_write_time(time_ec);
|
||||
auto previous = std::find_if(previous_cache.begin(), previous_cache.end(), [&](const UpscalerEntry& cached) {
|
||||
return cached.fullpath == upscaler_entry.fullpath &&
|
||||
cached.file_size == upscaler_entry.file_size &&
|
||||
(!time_ec && cached.last_modified == upscaler_entry.last_modified);
|
||||
});
|
||||
upscaler_entry.image_upscale_factor = previous != previous_cache.end()
|
||||
? previous->image_upscale_factor
|
||||
: get_upscaler_model_scale(upscaler_entry.fullpath.c_str());
|
||||
if (upscaler_entry.image_upscale_factor > 0) {
|
||||
upscaler_entry.scale = upscaler_entry.image_upscale_factor;
|
||||
upscaler_entry.model_name = "ESRGAN_" + std::to_string(upscaler_entry.scale) + "x";
|
||||
}
|
||||
|
||||
new_cache.push_back(std::move(upscaler_entry));
|
||||
it.increment(ec);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_WARN("error while scanning upscalers directory '%s': %s", upscaler_dir.string().c_str(), e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
LOG_WARN("error while scanning upscalers directory '%s': %s", upscaler_dir.string().c_str(), ec.message().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(new_cache.begin(), new_cache.end(), [](const UpscalerEntry& a, const UpscalerEntry& b) {
|
||||
return a.name < b.name;
|
||||
});
|
||||
|
||||
@@ -66,6 +66,8 @@ struct ImgGenJobRequest {
|
||||
SDGenerationParams gen_params;
|
||||
std::string output_format = "png";
|
||||
int output_compression = 100;
|
||||
std::string preview_mode = "none";
|
||||
int preview_interval = 1;
|
||||
|
||||
sd_img_gen_params_t to_sd_img_gen_params_t() {
|
||||
return gen_params.to_sd_img_gen_params_t();
|
||||
@@ -76,6 +78,8 @@ struct VidGenJobRequest {
|
||||
SDGenerationParams gen_params;
|
||||
std::string output_format = "webm";
|
||||
int output_compression = 100;
|
||||
std::string preview_mode = "none";
|
||||
int preview_interval = 1;
|
||||
|
||||
sd_vid_gen_params_t to_sd_vid_gen_params_t() {
|
||||
return gen_params.to_sd_vid_gen_params_t();
|
||||
|
||||
+1
-1
Submodule ggml updated: 4bf5f60006...d25b121ce1
@@ -245,11 +245,14 @@ typedef struct {
|
||||
const char* rpc_servers;
|
||||
const char* model_args;
|
||||
bool disable_segmented_compute; // Force monolithic graph execution even when automatic graph cutting would fit memory better
|
||||
bool batched_cfg; // Run the conditional and unconditional CFG branches in one batched UNet forward when supported
|
||||
float linear_scale; // Override linear input scaling; 0 keeps the model default
|
||||
float attn_scale; // Override flash-attention K/V scaling; 0 keeps the model default
|
||||
const char* tokenizer; // tokenizer.json path or main=FILE,clip-l=FILE,clip-g=FILE assignments; required for PiD and Lens
|
||||
bool sage_attn;
|
||||
int conditioning_cache_size; // Maximum cached conditioning entries per context; 0 disables caching (default: 4)
|
||||
const char* flash_attn_mode; // on/off/auto or module assignments (e.g. "auto,te=off"); NULL/empty honors legacy flags.
|
||||
const char* conv_direct_mode; // on/off/auto or diffusion/vae/controlnet assignments; NULL/empty honors legacy flags.
|
||||
} sd_ctx_params_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -461,6 +464,11 @@ struct ggml_tensor;
|
||||
typedef void (*sd_log_cb_t)(enum sd_log_level_t level, const char* text, void* data);
|
||||
typedef void (*sd_progress_cb_t)(int step, int steps, float time, void* data);
|
||||
typedef void (*sd_preview_cb_t)(int step, int frame_count, sd_image_t* frames, bool is_noisy, void* data);
|
||||
|
||||
typedef struct {
|
||||
int sample_pass;
|
||||
int total_steps;
|
||||
} sd_preview_info_t;
|
||||
typedef bool (*sd_graph_eval_callback_t)(struct ggml_tensor* t, bool ask, void* user_data);
|
||||
|
||||
SD_API void sd_set_log_callback(sd_log_cb_t sd_log_cb, void* data);
|
||||
@@ -469,6 +477,10 @@ SD_API void sd_set_progress_callback(sd_progress_cb_t cb, void* data);
|
||||
// negative interval previews only completed logical step -interval. Zero previews the final
|
||||
// completed step of the first sampling pass (base-resolution or high-noise).
|
||||
SD_API void sd_set_preview_callback(sd_preview_cb_t cb, enum preview_t mode, int interval, bool denoised, bool noisy, void* data);
|
||||
// Query from a preview callback. Passes are numbered from 1 since the last
|
||||
// sd_set_preview_callback call; total_steps is the actual count in that pass.
|
||||
// Both fields are zero before sampling starts.
|
||||
SD_API sd_preview_info_t sd_get_preview_info();
|
||||
SD_API void sd_set_backend_eval_callback(sd_graph_eval_callback_t cb, void* data);
|
||||
SD_API int32_t sd_get_num_physical_cores();
|
||||
SD_API const char* sd_get_system_info();
|
||||
@@ -601,7 +613,9 @@ SD_API bool convert_with_components(const char* model_path,
|
||||
enum sd_type_t output_type,
|
||||
const char* tensor_type_rules,
|
||||
bool convert_name,
|
||||
int n_threads);
|
||||
int n_threads,
|
||||
const sd_lora_t* loras,
|
||||
int lora_count);
|
||||
|
||||
SD_API bool preprocess_canny(sd_image_t image,
|
||||
float high_threshold,
|
||||
|
||||
@@ -8,6 +8,8 @@ $patterns = @(
|
||||
"src/core/*.cpp"
|
||||
"src/core/*.h"
|
||||
"src/core/*.hpp"
|
||||
"src/core/probe/*.cpp"
|
||||
"src/core/probe/*.h"
|
||||
"src/extensions/*.cpp"
|
||||
"src/extensions/*.h"
|
||||
"src/extensions/*.hpp"
|
||||
|
||||
@@ -8,6 +8,7 @@ cd "$repo_root"
|
||||
for f in src/*.cpp src/*.h src/*.hpp \
|
||||
src/conditioning/*.cpp src/conditioning/*.h src/conditioning/*.hpp \
|
||||
src/core/*.cpp src/core/*.h src/core/*.hpp \
|
||||
src/core/probe/*.cpp src/core/probe/*.h \
|
||||
src/extensions/*.cpp src/extensions/*.h src/extensions/*.hpp \
|
||||
src/pipeline/*.cpp src/pipeline/*.h \
|
||||
src/runtime/*.cpp src/runtime/*.h src/runtime/*.hpp \
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "model/te/clip.hpp"
|
||||
#include "model/te/llada_image_te.hpp"
|
||||
#include "model/te/llm.hpp"
|
||||
#include "model/te/ming_image_te.hpp"
|
||||
#include "model/te/t5.hpp"
|
||||
#include "model_loader.h"
|
||||
#include "tokenizers/sensenova_u1_tokenizer.h"
|
||||
@@ -398,7 +399,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
@@ -816,7 +817,7 @@ struct SD3CLIPEmbedder : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
@@ -908,6 +909,31 @@ struct SD3CLIPEmbedder : public Conditioner {
|
||||
|
||||
size_t chunk_count = std::max(std::max(clip_l_tokens.size(), clip_g_tokens.size()), t5_tokens.size()) / chunk_len;
|
||||
|
||||
// Each active encoder must cover every chunk of the longest token sequence.
|
||||
Tokenizer* tokenizers[] = {
|
||||
clip_l ? clip_l_tokenizer.get() : nullptr,
|
||||
clip_g ? clip_g_tokenizer.get() : nullptr,
|
||||
t5 ? &t5_tokenizer : nullptr,
|
||||
};
|
||||
const size_t target_length = chunk_count * chunk_len;
|
||||
|
||||
for (size_t encoder_idx = 0; encoder_idx < 3; encoder_idx++) {
|
||||
auto& tokens = token_and_weights[encoder_idx].first;
|
||||
auto& weights = token_and_weights[encoder_idx].second;
|
||||
if (!tokenizers[encoder_idx] || tokens.size() >= target_length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<int> empty_tokens;
|
||||
std::vector<float> empty_weights;
|
||||
tokenizers[encoder_idx]->pad_tokens(empty_tokens, &empty_weights, nullptr, chunk_len, chunk_len, false);
|
||||
|
||||
while (tokens.size() < target_length) {
|
||||
tokens.insert(tokens.end(), empty_tokens.begin(), empty_tokens.end());
|
||||
weights.insert(weights.end(), empty_weights.begin(), empty_weights.end());
|
||||
}
|
||||
}
|
||||
|
||||
for (int chunk_idx = 0; chunk_idx < chunk_count; chunk_idx++) {
|
||||
// clip_l
|
||||
sd::Tensor<float> chunk_hidden_states_l;
|
||||
@@ -1206,7 +1232,7 @@ struct FluxCLIPEmbedder : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
@@ -1483,7 +1509,7 @@ struct T5CLIPEmbedder : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
@@ -1872,7 +1898,7 @@ struct AnimaConditioner : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
std::vector<int> qwen_tokens;
|
||||
@@ -1986,9 +2012,10 @@ struct LLMEmbedder : public Conditioner {
|
||||
sd_version_is_sefi_image(version) ||
|
||||
sd_version_is_krea2(version) ||
|
||||
sd_version_is_minimax_h3(version) ||
|
||||
sd_version_is_mage_flow(version)) {
|
||||
sd_version_is_mage_flow(version) ||
|
||||
version == VERSION_IRIS) {
|
||||
arch = LLM::LLMArch::QWEN3_VL;
|
||||
} else if (sd_version_is_z_image(version) || version == VERSION_OVIS_IMAGE || version == VERSION_FLUX2_KLEIN) {
|
||||
} else if (sd_version_is_z_image(version) || sd_version_is_z_image_l2p(version) || version == VERSION_OVIS_IMAGE || version == VERSION_FLUX2_KLEIN) {
|
||||
arch = LLM::LLMArch::QWEN3;
|
||||
}
|
||||
llm = std::make_shared<LLM::LLMRunner>(arch,
|
||||
@@ -2112,9 +2139,10 @@ struct LLMEmbedder : public Conditioner {
|
||||
|
||||
std::tuple<std::vector<int>, std::vector<float>, std::vector<float>> tokenize(std::string text,
|
||||
const std::pair<int, int>& attn_range,
|
||||
size_t min_length = 0,
|
||||
size_t max_length = 100000000,
|
||||
bool spell_quotes = false) {
|
||||
size_t min_length = 0,
|
||||
size_t max_length = 100000000,
|
||||
bool spell_quotes = false,
|
||||
const std::string& suffix = "") {
|
||||
std::vector<std::pair<std::string, float>> parsed_attention;
|
||||
if (attn_range.first >= 0 && attn_range.second > 0) {
|
||||
if (attn_range.first > 0) {
|
||||
@@ -2143,7 +2171,7 @@ struct LLMEmbedder : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
std::vector<int> tokens;
|
||||
@@ -2159,6 +2187,20 @@ struct LLMEmbedder : public Conditioner {
|
||||
weights.insert(weights.end(), curr_tokens.size(), curr_weight);
|
||||
}
|
||||
|
||||
if (!suffix.empty()) {
|
||||
std::vector<int> suffix_tokens;
|
||||
if (!tokenizer->encode(suffix, suffix_tokens) || suffix_tokens.size() >= max_length) {
|
||||
return {};
|
||||
}
|
||||
// Reserve the assistant-turn marker before truncating the caption.
|
||||
if (tokens.size() > max_length - suffix_tokens.size()) {
|
||||
tokens.resize(max_length - suffix_tokens.size());
|
||||
weights.resize(tokens.size());
|
||||
}
|
||||
tokens.insert(tokens.end(), suffix_tokens.begin(), suffix_tokens.end());
|
||||
weights.insert(weights.end(), suffix_tokens.size(), 1.f);
|
||||
}
|
||||
|
||||
std::vector<float> mask;
|
||||
tokenizer->pad_tokens(tokens, &weights, &mask, min_length, max_length);
|
||||
|
||||
@@ -2181,8 +2223,10 @@ struct LLMEmbedder : public Conditioner {
|
||||
bool spell_quotes = false,
|
||||
int max_length = 100000000,
|
||||
const LLM::DeepStackImageEmbeds& deepstack_image_embeds = {},
|
||||
const std::vector<LLM::ImageGrid>& image_grids = {}) {
|
||||
auto tokens_weights_mask = tokenize(prompt, prompt_attn_range, min_length, max_length, spell_quotes);
|
||||
const std::vector<LLM::ImageGrid>& image_grids = {},
|
||||
const std::string& prompt_suffix = "",
|
||||
sd::Tensor<float>* output_mask = nullptr) {
|
||||
auto tokens_weights_mask = tokenize(prompt, prompt_attn_range, min_length, max_length, spell_quotes, prompt_suffix);
|
||||
auto& tokens = std::get<0>(tokens_weights_mask);
|
||||
auto& weights = std::get<1>(tokens_weights_mask);
|
||||
auto& mask = std::get<2>(tokens_weights_mask);
|
||||
@@ -2245,6 +2289,12 @@ struct LLMEmbedder : public Conditioner {
|
||||
1);
|
||||
}
|
||||
|
||||
if (output_mask != nullptr) {
|
||||
*output_mask = sd::Tensor<float>::zeros({new_hidden_states.shape()[1]});
|
||||
for (size_t i = prompt_template_encode_start_idx; i < mask.size(); ++i) {
|
||||
output_mask->data()[i - prompt_template_encode_start_idx] = mask[i];
|
||||
}
|
||||
}
|
||||
return new_hidden_states;
|
||||
}
|
||||
|
||||
@@ -2311,6 +2361,8 @@ struct LLMEmbedder : public Conditioner {
|
||||
int hidden_states_min_length = 0; // zero pad hidden_states
|
||||
bool spell_quotes = false;
|
||||
std::set<int> out_layers;
|
||||
std::string prompt_suffix;
|
||||
sd::Tensor<float> text_mask;
|
||||
|
||||
int64_t t0 = ggml_time_ms();
|
||||
RefImageResizeMode resize_mode = conditioner_params.ref_image_params.vlm_resize_mode;
|
||||
@@ -2952,7 +3004,7 @@ struct LLMEmbedder : public Conditioner {
|
||||
prompt_attn_range.second = static_cast<int>(prompt.size());
|
||||
|
||||
prompt += "<|end|><|start|>assistant<|channel|>analysis<|message|>Need to generate one image according to the description.<|end|><|start|>assistant<|channel|>final<|message|>";
|
||||
} else if (sd_version_is_z_image(version)) {
|
||||
} else if (sd_version_is_z_image(version) || sd_version_is_z_image_l2p(version)) {
|
||||
prompt_template_encode_start_idx = 0;
|
||||
out_layers = {35}; // -2
|
||||
|
||||
@@ -3060,6 +3112,23 @@ struct LLMEmbedder : public Conditioner {
|
||||
SDCondition result;
|
||||
result.c_crossattn = std::move(hidden_states);
|
||||
return result;
|
||||
} else if (version == VERSION_IRIS) {
|
||||
prompt =
|
||||
"<|im_start|>system\n"
|
||||
"Describe the image by detailing the color, shape, size, texture, quantity, text, spatial "
|
||||
"relationships of the objects and background:<|im_end|>\n<|im_start|>user\n";
|
||||
auto prefix_tokens = std::get<0>(tokenize(prompt, {0, 0}));
|
||||
if (prefix_tokens.empty()) {
|
||||
return {};
|
||||
}
|
||||
prompt_template_encode_start_idx = static_cast<int>(prefix_tokens.size());
|
||||
hidden_states_min_length = 300;
|
||||
max_length = prompt_template_encode_start_idx + hidden_states_min_length;
|
||||
out_layers = {2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35};
|
||||
prompt_attn_range.first = static_cast<int>(prompt.size());
|
||||
prompt += conditioner_params.text;
|
||||
prompt_attn_range.second = static_cast<int>(prompt.size());
|
||||
prompt_suffix = "<|im_end|>\n<|im_start|>assistant\n";
|
||||
} else {
|
||||
GGML_ABORT("unknown version %d", version);
|
||||
}
|
||||
@@ -3075,7 +3144,9 @@ struct LLMEmbedder : public Conditioner {
|
||||
spell_quotes,
|
||||
max_length,
|
||||
deepstack_image_embeds,
|
||||
image_grids);
|
||||
image_grids,
|
||||
prompt_suffix,
|
||||
version == VERSION_IRIS ? &text_mask : nullptr);
|
||||
if (hidden_states.empty()) {
|
||||
return {};
|
||||
}
|
||||
@@ -3140,6 +3211,7 @@ struct LLMEmbedder : public Conditioner {
|
||||
SDCondition result;
|
||||
result.c_crossattn = std::move(hidden_states);
|
||||
result.extra_c_crossattns = std::move(extra_hidden_states_vec);
|
||||
result.c_vector = std::move(text_mask);
|
||||
if (version == VERSION_QWEN_IMAGE_2_1) {
|
||||
auto slots = sd::Tensor<int32_t>::zeros({result.c_crossattn.shape()[1]});
|
||||
for (size_t i = 0; i < image_embeds.size(); ++i) {
|
||||
@@ -3171,6 +3243,65 @@ struct LLMEmbedder : public Conditioner {
|
||||
}
|
||||
};
|
||||
|
||||
struct MingImageEmbedder : public Conditioner {
|
||||
std::shared_ptr<Tokenizer> tokenizer;
|
||||
std::shared_ptr<MingImageTE::MingImageTextRunner> text_model;
|
||||
const std::string prefix = "text_encoders.llm";
|
||||
|
||||
MingImageEmbedder(ggml_backend_t backend, const String2TensorStorage& tensors, std::shared_ptr<RunnerWeightManager> weight_manager, const TokenizerConfig& tokenizers) {
|
||||
if (!tokenizers.has(TokenizerConfig::MAIN)) {
|
||||
throw std::runtime_error("Ming-Image requires the Ling tokenizer.json; pass --tokenizer FILE");
|
||||
}
|
||||
text_model = std::make_shared<MingImageTE::MingImageTextRunner>(backend, tensors, prefix, weight_manager);
|
||||
tokenizer = tokenizers.create(TokenizerConfig::MAIN, text_model->config.backbone.vocab_size, 156895);
|
||||
}
|
||||
|
||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
|
||||
text_model->get_param_tensors(tensors, prefix);
|
||||
}
|
||||
void get_param_tensor_ops(std::map<ggml_tensor*, enum ggml_op>& ops) override {
|
||||
text_model->get_param_tensor_ops(ops);
|
||||
}
|
||||
void get_layer_split_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
|
||||
text_model->get_param_tensors(tensors, prefix);
|
||||
}
|
||||
void set_flash_attention_enabled(bool enabled) override { text_model->set_flash_attention_enabled(enabled); }
|
||||
void set_max_graph_vram_bytes(size_t bytes) override { text_model->set_max_graph_vram_bytes(bytes); }
|
||||
void set_runtime_backends(const std::vector<ggml_backend_t>& backends) override { text_model->set_runtime_backends(backends); }
|
||||
void set_graph_cut_layer_split_enabled(bool enabled) override { text_model->set_graph_cut_layer_split_enabled(enabled); }
|
||||
void set_graph_cut_layer_split_backend_vram_limits(const std::vector<size_t>& limits) override { text_model->set_graph_cut_layer_split_backend_vram_limits(limits); }
|
||||
void set_scale_overrides(float linear, float attention) override { text_model->set_scale_overrides(linear, attention); }
|
||||
void set_weight_adapter(const std::shared_ptr<WeightAdapter>& adapter) override { text_model->set_weight_adapter(adapter); }
|
||||
void runner_end() override { text_model->runner_end(); }
|
||||
|
||||
SDCondition get_learned_condition(int n_threads, const ConditionerParams& input) override {
|
||||
if (input.ref_images != nullptr && !input.ref_images->empty()) {
|
||||
LOG_ERROR("Ming-Image currently supports text-to-image only");
|
||||
return {};
|
||||
}
|
||||
std::string prompt =
|
||||
"<role>SYSTEM</role>你是一个友好的AI助手。\n\ndetailed thinking off<|role_end|>"
|
||||
"<role>HUMAN</role>" +
|
||||
input.text + "<|role_end|><role>ASSISTANT</role>";
|
||||
std::vector<int> tokens;
|
||||
if (!tokenizer->encode(prompt, tokens, nullptr)) {
|
||||
return {};
|
||||
}
|
||||
if (tokens.size() + 258 > 32768) {
|
||||
LOG_ERROR("Ming-Image prompt exceeds the text encoder context length");
|
||||
return {};
|
||||
}
|
||||
auto output = text_model->compute(n_threads, tokens);
|
||||
if (output.empty()) {
|
||||
return {};
|
||||
}
|
||||
SDCondition result;
|
||||
result.c_crossattn = sd::ops::slice(sd::ops::slice(output, 1, 0, 256), 0, 0, text_model->config.caption_dim);
|
||||
result.extra_c_crossattns.push_back(sd::ops::slice(output, 1, 256, output.shape()[1]));
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct LTXAVTextProjection : public GGMLBlock {
|
||||
static constexpr int64_t kHiddenSize = 3840;
|
||||
static constexpr int64_t kNumStates = 49;
|
||||
|
||||
+240
-11
@@ -4,6 +4,7 @@
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
@@ -11,11 +12,15 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "core/ggml_extend_backend.h"
|
||||
#include "core/util.h"
|
||||
#include "model_io/gguf_io.h"
|
||||
#include "model_io/safetensors_io.h"
|
||||
#include "model_io/streaming_writer.h"
|
||||
#include "model_loader.h"
|
||||
#include "model_manager.h"
|
||||
#include "name_conversion.h"
|
||||
#include "runtime/imatrix.h"
|
||||
|
||||
struct TensorExportInfo {
|
||||
TensorStorage storage;
|
||||
@@ -75,9 +80,9 @@ static size_t export_tensor_nbytes(const TensorExportInfo& info) {
|
||||
return static_cast<size_t>(output_storage.nbytes());
|
||||
}
|
||||
|
||||
static TensorWritePlan tensor_write_plan_from_export_info(const TensorExportInfo& info) {
|
||||
static TensorWritePlan tensor_write_plan_from_export_info(const TensorExportInfo& info, const std::string& name) {
|
||||
TensorWritePlan plan;
|
||||
plan.name = info.storage.name;
|
||||
plan.name = name;
|
||||
plan.type = info.type;
|
||||
plan.n_dims = info.storage.n_dims;
|
||||
for (int i = 0; i < SD_MAX_DIMS; i++) {
|
||||
@@ -86,11 +91,14 @@ static TensorWritePlan tensor_write_plan_from_export_info(const TensorExportInfo
|
||||
return plan;
|
||||
}
|
||||
|
||||
static std::vector<TensorWritePlan> tensor_write_plans_from_export_infos(const std::vector<TensorExportInfo>& tensors) {
|
||||
static std::vector<TensorWritePlan> tensor_write_plans_from_export_infos(const std::vector<TensorExportInfo>& tensors,
|
||||
bool convert_name,
|
||||
SDVersion version) {
|
||||
std::vector<TensorWritePlan> plans;
|
||||
plans.reserve(tensors.size());
|
||||
for (const TensorExportInfo& info : tensors) {
|
||||
plans.push_back(tensor_write_plan_from_export_info(info));
|
||||
const std::string name = convert_name ? convert_tensor_name(info.storage.name, version) : info.storage.name;
|
||||
plans.push_back(tensor_write_plan_from_export_info(info, name));
|
||||
}
|
||||
return plans;
|
||||
}
|
||||
@@ -158,16 +166,42 @@ static bool load_tensor_for_export(ModelLoader& model_loader, TensorExportJob& j
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool export_tensor_from_memory(const TensorExportInfo& info,
|
||||
const std::map<std::string, ggml_tensor*>& mem,
|
||||
SDVersion version,
|
||||
std::vector<uint8_t>& out) {
|
||||
auto found = mem.find(info.storage.name);
|
||||
if (found == mem.end() || found->second == nullptr || found->second->data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const TensorStorage& storage = info.storage;
|
||||
out.resize(export_tensor_nbytes(info));
|
||||
if (out.size() > 0) {
|
||||
std::vector<float> imatrix = get_imatrix_collector().get_values(convert_tensor_name(info.storage.name, version));
|
||||
convert_tensor(found->second->data,
|
||||
found->second->type,
|
||||
out.data(),
|
||||
info.type,
|
||||
(int)(storage.nelements() / storage.ne[0]),
|
||||
(int)storage.ne[0],
|
||||
std::move(imatrix));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool stream_tensor_data(ModelLoader& model_loader,
|
||||
const std::string& output_path,
|
||||
const std::vector<TensorExportInfo>& tensors,
|
||||
const StreamingModelWriter& writer,
|
||||
int n_threads,
|
||||
const std::map<std::string, ggml_tensor*>* mem,
|
||||
std::string* error) {
|
||||
n_threads = n_threads > 0 ? n_threads : sd_get_num_physical_cores();
|
||||
n_threads = std::max(1, n_threads);
|
||||
LOG_INFO("streaming convert with %d threads", n_threads);
|
||||
|
||||
const SDVersion version = model_loader.get_sd_version();
|
||||
|
||||
int64_t start_time = ggml_time_ms();
|
||||
uint64_t bytes_written = 0;
|
||||
size_t tensors_written = 0;
|
||||
@@ -241,7 +275,10 @@ static bool stream_tensor_data(ModelLoader& model_loader,
|
||||
TensorExportJob job;
|
||||
job.info = tensors[tensor_index];
|
||||
try {
|
||||
job.success = load_tensor_for_export(model_loader, job);
|
||||
job.success = (mem != nullptr) && export_tensor_from_memory(job.info, *mem, version, job.data);
|
||||
if (!job.success) {
|
||||
job.success = load_tensor_for_export(model_loader, job);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
job.error = e.what();
|
||||
job.success = false;
|
||||
@@ -298,8 +335,10 @@ static bool write_model_file_streaming(ModelLoader& model_loader,
|
||||
const std::vector<TensorExportInfo>& tensors,
|
||||
StreamingModelWriter& writer,
|
||||
int n_threads,
|
||||
bool convert_name,
|
||||
const std::map<std::string, ggml_tensor*>* mem,
|
||||
std::string* error) {
|
||||
std::vector<TensorWritePlan> plans = tensor_write_plans_from_export_infos(tensors);
|
||||
std::vector<TensorWritePlan> plans = tensor_write_plans_from_export_infos(tensors, convert_name, model_loader.get_sd_version());
|
||||
if (!writer.write_metadata(output_path, plans, error)) {
|
||||
return false;
|
||||
}
|
||||
@@ -307,7 +346,7 @@ static bool write_model_file_streaming(ModelLoader& model_loader,
|
||||
return false;
|
||||
}
|
||||
model_loader.process_model_files(false, false);
|
||||
return stream_tensor_data(model_loader, output_path, tensors, writer, n_threads, error);
|
||||
return stream_tensor_data(model_loader, output_path, tensors, writer, n_threads, mem, error);
|
||||
}
|
||||
|
||||
static bool init_convert_path(ModelLoader& model_loader, const char* path, const char* prefix, bool& loaded_any) {
|
||||
@@ -326,7 +365,9 @@ static bool export_loaded_model(ModelLoader& model_loader,
|
||||
const char* output_path,
|
||||
sd_type_t output_type,
|
||||
const char* tensor_type_rules,
|
||||
int n_threads) {
|
||||
int n_threads,
|
||||
bool convert_name,
|
||||
const std::map<std::string, ggml_tensor*>* mem = nullptr) {
|
||||
ggml_type type = sd_type_to_ggml_type(output_type);
|
||||
bool output_is_safetensors = ends_with(output_path, ".safetensors");
|
||||
TensorTypeRules type_rules = parse_tensor_type_rules(tensor_type_rules);
|
||||
@@ -341,7 +382,7 @@ static bool export_loaded_model(ModelLoader& model_loader,
|
||||
} else {
|
||||
writer = std::make_unique<GGUFStreamingWriter>();
|
||||
}
|
||||
success = write_model_file_streaming(model_loader, output_path, tensors, *writer, n_threads, &error);
|
||||
success = write_model_file_streaming(model_loader, output_path, tensors, *writer, n_threads, convert_name, mem, &error);
|
||||
}
|
||||
|
||||
if (!success && !error.empty()) {
|
||||
@@ -351,6 +392,174 @@ static bool export_loaded_model(ModelLoader& model_loader,
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool has_active_loras(const sd_lora_t* loras, int lora_count) {
|
||||
if (loras == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < lora_count; i++) {
|
||||
if (loras[i].multiplier != 0.0f) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::vector<ModelManager::LoraSpec> build_lora_specs(const sd_lora_t* loras, int lora_count) {
|
||||
std::vector<ModelManager::LoraSpec> specs;
|
||||
specs.reserve(lora_count);
|
||||
for (int i = 0; i < lora_count; i++) {
|
||||
ModelManager::LoraSpec spec;
|
||||
spec.path = loras[i].path != nullptr ? loras[i].path : "";
|
||||
spec.multiplier = loras[i].multiplier;
|
||||
spec.is_high_noise = loras[i].is_high_noise;
|
||||
spec.required = true;
|
||||
specs.push_back(std::move(spec));
|
||||
}
|
||||
return specs;
|
||||
}
|
||||
|
||||
static ModelComponent component_for_tensor_name(const std::string& name) {
|
||||
if (starts_with(name, "text_encoders.") || starts_with(name, "cond_stage_model.")) {
|
||||
return ModelComponent::Conditioner;
|
||||
}
|
||||
if (starts_with(name, "model.high_noise_diffusion_model.")) {
|
||||
return ModelComponent::HighNoiseDiffusion;
|
||||
}
|
||||
if (starts_with(name, "model.diffusion_model.")) {
|
||||
return ModelComponent::Diffusion;
|
||||
}
|
||||
if (starts_with(name, "vae.") || starts_with(name, "first_stage_model.")) {
|
||||
return ModelComponent::VAE;
|
||||
}
|
||||
return ModelComponent::Diffusion;
|
||||
}
|
||||
|
||||
static bool load_model_into_memory(ModelLoader& model_loader,
|
||||
const sd_lora_t* loras,
|
||||
int lora_count,
|
||||
int n_threads,
|
||||
ModelManager& manager,
|
||||
ggml_backend_t cpu,
|
||||
ggml_context* ctx,
|
||||
std::map<std::string, ggml_tensor*>& mem,
|
||||
std::vector<ggml_tensor*>& all_tensors,
|
||||
ggml_type output_type,
|
||||
const TensorTypeRules& type_rules) {
|
||||
const String2TensorStorage& storage_map = model_loader.get_tensor_storage_map();
|
||||
if (storage_map.empty()) {
|
||||
LOG_ERROR("no tensors to load into memory for convert");
|
||||
return false;
|
||||
}
|
||||
|
||||
const SDVersion version = model_loader.get_sd_version();
|
||||
ModelLoader merge_loader = model_loader;
|
||||
merge_loader.convert_tensors_name();
|
||||
|
||||
std::map<ModelComponent, std::map<std::string, ggml_tensor*>> groups;
|
||||
size_t total_bytes = 0;
|
||||
for (const auto& [name, storage] : storage_map) {
|
||||
ggml_type load_type = storage.type;
|
||||
const ggml_type export_type = get_export_tensor_type(model_loader, storage, output_type, type_rules);
|
||||
const bool quant_src = ggml_is_quantized(storage.type);
|
||||
const bool quant_dst = ggml_is_quantized(export_type);
|
||||
if (quant_src && quant_dst) {
|
||||
load_type = GGML_TYPE_F16;
|
||||
} else if (quant_src || quant_dst) {
|
||||
load_type = quant_src ? export_type : storage.type;
|
||||
} else if (storage.type == GGML_TYPE_F32 || export_type == GGML_TYPE_F32) {
|
||||
load_type = GGML_TYPE_F32;
|
||||
}
|
||||
ggml_tensor* tensor = ggml_new_tensor(ctx, load_type, storage.n_dims, storage.ne);
|
||||
if (tensor == nullptr) {
|
||||
LOG_ERROR("failed to create tensor '%s' for in-memory convert", name.c_str());
|
||||
return false;
|
||||
}
|
||||
const std::string merge_name = convert_tensor_name(name, version);
|
||||
auto& group = groups[component_for_tensor_name(merge_name)];
|
||||
if (!group.emplace(merge_name, tensor).second) {
|
||||
LOG_ERROR("duplicate tensor name '%s' after name conversion", merge_name.c_str());
|
||||
return false;
|
||||
}
|
||||
mem[name] = tensor;
|
||||
all_tensors.push_back(tensor);
|
||||
total_bytes += ggml_nbytes(tensor);
|
||||
}
|
||||
LOG_INFO("loading %zu tensors (%.2f GB) into memory for convert",
|
||||
all_tensors.size(), total_bytes / (1024.0 * 1024.0 * 1024.0));
|
||||
|
||||
manager.set_n_threads(n_threads);
|
||||
manager.set_enable_mmap(false);
|
||||
if (!manager.set_loader(std::move(merge_loader))) {
|
||||
LOG_ERROR("failed to set model loader for in-memory convert");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& [component, tensors] : groups) {
|
||||
if (!manager.register_param_tensors(component, tensors, ModelManager::ResidencyMode::ParamBackend, cpu, cpu)) {
|
||||
LOG_ERROR("failed to register %s tensors for in-memory convert", model_component_name(component));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ModelManager::LoraSpec> lora_specs = build_lora_specs(loras, lora_count);
|
||||
if (!manager.prepare_lora_sources(lora_specs)) {
|
||||
LOG_ERROR("failed to prepare LoRA sources for convert");
|
||||
return false;
|
||||
}
|
||||
if (!manager.set_loras(lora_specs, version)) {
|
||||
LOG_ERROR("failed to set LoRAs for convert");
|
||||
return false;
|
||||
}
|
||||
if (!manager.prepare_params(all_tensors)) {
|
||||
LOG_ERROR("failed to load model into memory for convert");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool convert_model_in_memory(ModelLoader& model_loader,
|
||||
const char* output_path,
|
||||
sd_type_t output_type,
|
||||
const char* tensor_type_rules,
|
||||
bool convert_name,
|
||||
int n_threads,
|
||||
const sd_lora_t* loras,
|
||||
int lora_count) {
|
||||
auto cpu = std::unique_ptr<ggml_backend, decltype(&ggml_backend_free)>(sd_backend_cpu_init(), ggml_backend_free);
|
||||
if (cpu == nullptr) {
|
||||
LOG_ERROR("failed to init CPU backend for in-memory convert");
|
||||
return false;
|
||||
}
|
||||
|
||||
ggml_init_params ctx_params;
|
||||
ctx_params.mem_size = model_loader.get_tensor_storage_map().size() * ggml_tensor_overhead();
|
||||
ctx_params.mem_buffer = nullptr;
|
||||
ctx_params.no_alloc = true;
|
||||
ggml_context* ctx = ggml_init(ctx_params);
|
||||
if (ctx == nullptr) {
|
||||
LOG_ERROR("ggml_init failed for in-memory convert");
|
||||
return false;
|
||||
}
|
||||
|
||||
ggml_type type = sd_type_to_ggml_type(output_type);
|
||||
TensorTypeRules type_rules = parse_tensor_type_rules(tensor_type_rules);
|
||||
|
||||
std::vector<ggml_tensor*> all_tensors;
|
||||
std::map<std::string, ggml_tensor*> mem;
|
||||
bool success = false;
|
||||
{
|
||||
// The manager owns the buffers backing `mem` and must outlive the export; it is
|
||||
// destroyed (freeing those buffers) before the tensor context is freed below.
|
||||
ModelManager manager;
|
||||
if (load_model_into_memory(model_loader, loras, lora_count, n_threads, manager, cpu.get(), ctx, mem, all_tensors, type, type_rules)) {
|
||||
success = export_loaded_model(model_loader, output_path, output_type, tensor_type_rules, n_threads, convert_name, &mem);
|
||||
}
|
||||
}
|
||||
ggml_free(ctx);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool convert_with_components(const char* model_path,
|
||||
const char* clip_l_path,
|
||||
const char* clip_g_path,
|
||||
@@ -361,10 +570,22 @@ bool convert_with_components(const char* model_path,
|
||||
sd_type_t output_type,
|
||||
const char* tensor_type_rules,
|
||||
bool convert_name,
|
||||
int n_threads) {
|
||||
int n_threads,
|
||||
const sd_lora_t* loras,
|
||||
int lora_count) {
|
||||
if (!validate_tensor_types(output_type, tensor_type_rules)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (loras != nullptr) {
|
||||
for (int i = 0; i < lora_count; i++) {
|
||||
LOG_INFO("lora %d: '%s' (multiplier %.2f%s)",
|
||||
i + 1, loras[i].path != nullptr ? loras[i].path : "",
|
||||
loras[i].multiplier,
|
||||
loras[i].is_high_noise ? ", high noise" : "");
|
||||
}
|
||||
}
|
||||
|
||||
ModelLoader model_loader;
|
||||
bool loaded_any = false;
|
||||
|
||||
@@ -382,11 +603,17 @@ bool convert_with_components(const char* model_path,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (has_active_loras(loras, lora_count)) {
|
||||
LOG_INFO("loading the full model into memory to apply LoRAs");
|
||||
return convert_model_in_memory(model_loader, output_path, output_type, tensor_type_rules, convert_name, n_threads,
|
||||
loras, lora_count);
|
||||
}
|
||||
|
||||
if (convert_name) {
|
||||
model_loader.convert_tensors_name();
|
||||
}
|
||||
|
||||
return export_loaded_model(model_loader, output_path, output_type, tensor_type_rules, n_threads);
|
||||
return export_loaded_model(model_loader, output_path, output_type, tensor_type_rules, n_threads, false, nullptr);
|
||||
}
|
||||
|
||||
bool convert(const char* input_path,
|
||||
@@ -405,5 +632,7 @@ bool convert(const char* input_path,
|
||||
output_type,
|
||||
tensor_type_rules,
|
||||
convert_name,
|
||||
0,
|
||||
nullptr,
|
||||
0);
|
||||
}
|
||||
|
||||
@@ -497,11 +497,7 @@ namespace sd::backend_fit {
|
||||
return false;
|
||||
}
|
||||
auto smaller_tile = [&](int size) {
|
||||
int next_size = size / 2;
|
||||
if (!tiling_params.enabled) {
|
||||
next_size = std::min(next_size, 256 / scale_factor);
|
||||
}
|
||||
return std::min(size, std::max(4, next_size));
|
||||
return std::min(size, std::max(4, size / 2));
|
||||
};
|
||||
const int tile_size_w = smaller_tile(latent_tile_size_w);
|
||||
const int tile_size_h = smaller_tile(latent_tile_size_h);
|
||||
|
||||
+20
-14
@@ -223,13 +223,13 @@ ggml_tensor* ggml_ext_linear(ggml_context* ctx,
|
||||
x = ggml_reshape_2d(ctx, x, x->ne[0], x->ne[1] * x->ne[2] * x->ne[3]);
|
||||
x = ggml_mul_mat(ctx, w, x);
|
||||
if (force_prec_f32) {
|
||||
ggml_mul_mat_set_prec(x, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(x, GGML_PREC_F32);
|
||||
}
|
||||
x = ggml_reshape_4d(ctx, x, x->ne[0], x->ne[1] / ne2 / ne3, ne2, ne3);
|
||||
} else {
|
||||
x = ggml_mul_mat(ctx, w, x);
|
||||
if (force_prec_f32) {
|
||||
ggml_mul_mat_set_prec(x, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(x, GGML_PREC_F32);
|
||||
}
|
||||
}
|
||||
if (scale != 1.f) {
|
||||
@@ -255,14 +255,20 @@ ggml_tensor* ggml_ext_linear_i8_tensorwise(ggml_context* ctx,
|
||||
}
|
||||
|
||||
ggml_tensor* fused_bias = scale == 1.f ? b : nullptr;
|
||||
auto mul_mat = [&](ggml_tensor* input) {
|
||||
if (input->type == GGML_TYPE_F32 && convrot_group_size > 0) {
|
||||
input = ggml_quantize_i8_convrot(ctx, input, convrot_group_size);
|
||||
}
|
||||
return ggml_mul_mat_i8_tensorwise(ctx, w, input, weight_scale, fused_bias, convrot_group_size);
|
||||
};
|
||||
if (x->ne[2] * x->ne[3] > 1024) {
|
||||
int64_t ne2 = x->ne[2];
|
||||
int64_t ne3 = x->ne[3];
|
||||
x = ggml_reshape_2d(ctx, x, x->ne[0], x->ne[1] * x->ne[2] * x->ne[3]);
|
||||
x = ggml_mul_mat_i8_tensorwise(ctx, w, x, weight_scale, fused_bias, convrot_group_size);
|
||||
x = mul_mat(x);
|
||||
x = ggml_reshape_4d(ctx, x, x->ne[0], x->ne[1] / ne2 / ne3, ne2, ne3);
|
||||
} else {
|
||||
x = ggml_mul_mat_i8_tensorwise(ctx, w, x, weight_scale, fused_bias, convrot_group_size);
|
||||
x = mul_mat(x);
|
||||
}
|
||||
|
||||
if (scale != 1.f) {
|
||||
@@ -482,7 +488,7 @@ ggml_tensor* ggml_ext_conv_3d(ggml_context* ctx,
|
||||
x = ggml_mul_mat(ctx,
|
||||
ggml_reshape_2d(ctx, im2col, im2col->ne[0], im2col->ne[3] * im2col->ne[2] * im2col->ne[1]),
|
||||
ggml_reshape_2d(ctx, w, w->ne[0] * w->ne[1] * w->ne[2] * IC, OC));
|
||||
ggml_mul_mat_set_prec(x, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(x, GGML_PREC_F32);
|
||||
|
||||
int64_t OD = im2col->ne[3] / N;
|
||||
x = ggml_reshape_4d(ctx, x, im2col->ne[1] * im2col->ne[2], OD, N, OC);
|
||||
@@ -677,8 +683,8 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
v_in->type == GGML_TYPE_F32 && sd_backend_supports_cuda_mma(backend);
|
||||
if (pad_head) {
|
||||
// CUDA FA MMA starts at 64 channels; keep the original head's attention scale.
|
||||
q_in = ggml_pad(ctx, q_in, 64 - d_head, 0, 0, 0);
|
||||
k_in = ggml_pad(ctx, k_in, 64 - d_head, 0, 0, 0);
|
||||
q_in = ggml_pad(ctx, q_in, static_cast<int>(64 - d_head), 0, 0, 0);
|
||||
k_in = ggml_pad(ctx, k_in, static_cast<int>(64 - d_head), 0, 0, 0);
|
||||
}
|
||||
if (kv_scale != 1.0f) {
|
||||
k_in = ggml_ext_scale(ctx, k_in, kv_scale);
|
||||
@@ -688,7 +694,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
v_in = ggml_ext_cont(ctx, ggml_permute(ctx, v_in, 0, 2, 1, 3));
|
||||
v_in = ggml_reshape_3d(ctx, v_in, d_head, L_k, n_kv_head * N);
|
||||
if (pad_head) {
|
||||
v_in = ggml_pad(ctx, v_in, 64 - d_head, 0, 0, 0);
|
||||
v_in = ggml_pad(ctx, v_in, static_cast<int>(64 - d_head), 0, 0, 0);
|
||||
}
|
||||
if (kv_scale != 1.0f) {
|
||||
v_in = ggml_ext_scale(ctx, v_in, kv_scale);
|
||||
@@ -715,7 +721,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
if (!ggml_backend_supports_op(backend, out)) {
|
||||
return nullptr;
|
||||
}
|
||||
ggml_flash_attn_ext_set_prec(out, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(out, GGML_PREC_F32);
|
||||
if (kv_scale != 1.0f) {
|
||||
out = ggml_ext_scale(ctx, out, 1.0f / kv_scale);
|
||||
}
|
||||
@@ -736,9 +742,9 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
}
|
||||
if (padded_head != d_head) {
|
||||
// Keep the original head's softmax scale when padding for the CUDA kernel.
|
||||
q_in = ggml_pad(ctx, q_in, padded_head - d_head, 0, 0, 0);
|
||||
k_in = ggml_pad(ctx, k_in, padded_head - d_head, 0, 0, 0);
|
||||
v_in = ggml_pad(ctx, v_in, padded_head - d_head, 0, 0, 0);
|
||||
q_in = ggml_pad(ctx, q_in, static_cast<int>(padded_head - d_head), 0, 0, 0);
|
||||
k_in = ggml_pad(ctx, k_in, static_cast<int>(padded_head - d_head), 0, 0, 0);
|
||||
v_in = ggml_pad(ctx, v_in, static_cast<int>(padded_head - d_head), 0, 0, 0);
|
||||
}
|
||||
if (kv_scale != 1.0f) {
|
||||
k_in = ggml_ext_scale(ctx, k_in, kv_scale);
|
||||
@@ -755,7 +761,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
}
|
||||
#endif
|
||||
|
||||
if (kqv == nullptr && (flash_attn || sage_attn)) {
|
||||
if (kqv == nullptr && flash_attn) {
|
||||
// LOG_VERBOSE("attention_ext L_q:%d L_k:%d n_head:%d C:%d d_head:%d N:%d", L_q, L_k, n_head, C, d_head, N);
|
||||
bool can_use_flash_attn = true;
|
||||
if (mask != nullptr) {
|
||||
@@ -791,7 +797,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
v = ggml_reshape_3d(ctx, v, L_k, d_head, n_kv_head * N); // [N * n_kv_head, d_head, L_k]
|
||||
|
||||
auto kq = ggml_mul_mat(ctx, k, q); // [N * n_head, L_q, L_k]
|
||||
ggml_mul_mat_set_prec(kq, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(kq, GGML_PREC_F32);
|
||||
kq = ggml_scale_inplace(ctx, kq, scale);
|
||||
if (mask) {
|
||||
kq = ggml_add_inplace(ctx, kq, mask);
|
||||
|
||||
@@ -6,12 +6,17 @@
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef SD_USE_CUDA
|
||||
#include <cuda.h>
|
||||
#endif
|
||||
|
||||
#ifdef SD_USE_HIP
|
||||
#include <hip/hip_runtime_api.h>
|
||||
#endif
|
||||
|
||||
#include "core/util.h"
|
||||
#include "ggml-backend-impl.h"
|
||||
#include "ggml-impl.h"
|
||||
@@ -452,23 +457,23 @@ ggml_backend_buffer_t sd_backend_dev_buffer_from_host_ptr(ggml_backend_dev_t dev
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool sd_backend_supports_cuda_mma(ggml_backend_t backend) {
|
||||
static int cuda_compiled_arch(ggml_backend_t backend) {
|
||||
#ifdef SD_USE_CUDA
|
||||
if (!sd_backend_is(backend, "CUDA")) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
auto dev = ggml_backend_get_device(backend);
|
||||
if (dev == nullptr) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
static std::mutex mutex;
|
||||
static std::unordered_map<ggml_backend_dev_t, bool> cache;
|
||||
static std::unordered_map<ggml_backend_dev_t, int> cache;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
auto it = cache.find(dev);
|
||||
if (it != cache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
const bool supported = [&]() {
|
||||
const int arch = [&]() {
|
||||
ggml_backend_dev_props props{};
|
||||
ggml_backend_dev_get_props(dev, &props);
|
||||
CUdevice device;
|
||||
@@ -477,13 +482,13 @@ bool sd_backend_supports_cuda_mma(ggml_backend_t backend) {
|
||||
cuDeviceGetByPCIBusId(&device, props.device_id) != CUDA_SUCCESS ||
|
||||
cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, device) != CUDA_SUCCESS ||
|
||||
cuDeviceGetAttribute(&minor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, device) != CUDA_SUCCESS) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
auto reg = ggml_backend_dev_backend_reg(dev);
|
||||
auto get_features = reinterpret_cast<ggml_backend_get_features_t>(
|
||||
ggml_backend_reg_get_proc_address(reg, "ggml_backend_get_features"));
|
||||
if (get_features == nullptr) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
// Match ggml's highest compiled architecture for this device, including PTX fallback.
|
||||
const int cc = 100 * major + 10 * minor;
|
||||
@@ -506,16 +511,78 @@ bool sd_backend_supports_cuda_mma(ggml_backend_t backend) {
|
||||
arch = end;
|
||||
}
|
||||
}
|
||||
return compiled_arch == 700 || compiled_arch >= 750;
|
||||
return compiled_arch;
|
||||
}();
|
||||
cache.emplace(dev, supported);
|
||||
return supported;
|
||||
cache.emplace(dev, arch);
|
||||
return arch;
|
||||
#else
|
||||
(void)backend;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool sd_backend_supports_cuda_mma(ggml_backend_t backend) {
|
||||
const int arch = cuda_compiled_arch(backend);
|
||||
return arch == 700 || arch >= 750;
|
||||
}
|
||||
|
||||
bool sd_backend_supports_hip_mma(ggml_backend_t backend) {
|
||||
#ifdef SD_USE_HIP
|
||||
if (!sd_backend_is(backend, "ROCm")) {
|
||||
return false;
|
||||
}
|
||||
auto dev = ggml_backend_get_device(backend);
|
||||
if (dev == nullptr) {
|
||||
return false;
|
||||
}
|
||||
ggml_backend_dev_props props{};
|
||||
ggml_backend_dev_get_props(dev, &props);
|
||||
int device = 0;
|
||||
hipDeviceProp_t hip_props{};
|
||||
if (props.device_id == nullptr ||
|
||||
hipDeviceGetByPCIBusId(&device, props.device_id) != hipSuccess ||
|
||||
hipGetDeviceProperties(&hip_props, device) != hipSuccess) {
|
||||
return false;
|
||||
}
|
||||
const char* arch = hip_props.gcnArchName;
|
||||
if (std::strncmp(arch, "gfx", 3) != 0) {
|
||||
return false;
|
||||
}
|
||||
char* end = nullptr;
|
||||
const long value = std::strtol(arch + 3, &end, 16);
|
||||
if (end == arch + 3 || (*end != '\0' && *end != ':')) {
|
||||
return false;
|
||||
}
|
||||
// Match the HIP FA kernels; keep unknown architecture families on the probe path.
|
||||
if (value >= 0x1100 && value < 0x1300) {
|
||||
return true;
|
||||
}
|
||||
#ifdef SD_HIP_MFMA
|
||||
return value == 0x908 || value == 0x90a ||
|
||||
value == 0x940 || value == 0x941 || value == 0x942 || value == 0x950;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
#else
|
||||
(void)backend;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool sd_backend_supports_conv_implicit_gemm(ggml_backend_t backend, bool conv3d) {
|
||||
if (backend == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (sd_backend_is(backend, "Vulkan") || sd_backend_is(backend, "MUSA")) {
|
||||
return true;
|
||||
}
|
||||
// CUDA/HIP Conv3d also implements SIMT implicit GEMM; Conv2d requires MMA.
|
||||
if (conv3d) {
|
||||
return sd_backend_is(backend, "CUDA") || sd_backend_is(backend, "ROCm");
|
||||
}
|
||||
return cuda_compiled_arch(backend) >= 750 || sd_backend_supports_hip_mma(backend);
|
||||
}
|
||||
|
||||
ggml_backend_t sd_backend_cpu_init() {
|
||||
ggml_backend_load_all_once();
|
||||
return ggml_backend_init_by_type(GGML_BACKEND_DEVICE_TYPE_CPU, nullptr);
|
||||
@@ -734,6 +801,72 @@ bool sd_parse_backend_assignment(const std::string& spec, SDBackendAssignment* a
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_operation_assignment(const std::string& spec, SDBackendAssignment* assignment, std::string* error, bool convolution) {
|
||||
const std::string operation = convolution ? "direct convolution" : "flash attention";
|
||||
if (assignment == nullptr) {
|
||||
return false;
|
||||
}
|
||||
SDBackendAssignment result;
|
||||
result.set_default("auto");
|
||||
std::istringstream stream(spec);
|
||||
std::string part;
|
||||
while (std::getline(stream, part, ',')) {
|
||||
SDBackendAssignment entry;
|
||||
if (!sd_parse_backend_assignment(part, &entry, error)) {
|
||||
return false;
|
||||
}
|
||||
auto valid_mode = [&](const std::string& mode) {
|
||||
if (mode == "on" || mode == "off" || mode == "auto") {
|
||||
return true;
|
||||
}
|
||||
if (error != nullptr) {
|
||||
*error = "invalid " + operation + " mode '" + mode + "': expected on, off, or auto";
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (!entry.default_name.empty()) {
|
||||
if (!valid_mode(entry.default_name)) {
|
||||
return false;
|
||||
}
|
||||
result.set_default(entry.default_name);
|
||||
}
|
||||
for (const auto& setting : entry.module_names) {
|
||||
switch (setting.first) {
|
||||
case SDBackendModule::DIFFUSION:
|
||||
case SDBackendModule::VAE:
|
||||
case SDBackendModule::CONTROL_NET:
|
||||
break;
|
||||
case SDBackendModule::TE:
|
||||
case SDBackendModule::CLIP_VISION:
|
||||
case SDBackendModule::AUDIO_ENCODER:
|
||||
if (!convolution) {
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
default:
|
||||
if (error != nullptr) {
|
||||
*error = operation + " is not configurable for module '" + sd_backend_module_name(setting.first) + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!valid_mode(setting.second)) {
|
||||
return false;
|
||||
}
|
||||
result.set_module(setting.first, setting.second);
|
||||
}
|
||||
}
|
||||
*assignment = std::move(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sd_parse_flash_attention_assignment(const std::string& spec, SDBackendAssignment* assignment, std::string* error) {
|
||||
return parse_operation_assignment(spec, assignment, error, false);
|
||||
}
|
||||
|
||||
bool sd_parse_conv_direct_assignment(const std::string& spec, SDBackendAssignment* assignment, std::string* error) {
|
||||
return parse_operation_assignment(spec, assignment, error, true);
|
||||
}
|
||||
|
||||
bool SDBackendAssignment::empty() const {
|
||||
return default_name.empty() && module_names.empty();
|
||||
}
|
||||
|
||||
@@ -88,6 +88,8 @@ private:
|
||||
bool sd_backend_is(ggml_backend_t backend, const std::string& name);
|
||||
bool sd_backend_is_cpu(ggml_backend_t backend);
|
||||
bool sd_backend_supports_cuda_mma(ggml_backend_t backend);
|
||||
bool sd_backend_supports_hip_mma(ggml_backend_t backend);
|
||||
bool sd_backend_supports_conv_implicit_gemm(ggml_backend_t backend, bool conv3d);
|
||||
ggml_backend_buffer_t sd_backend_dev_buffer_from_host_ptr(ggml_backend_dev_t device,
|
||||
void* ptr,
|
||||
size_t size,
|
||||
@@ -100,6 +102,8 @@ ggml_status sd_backend_graph_compute_with_eval_callback(ggml_backend_t backend,
|
||||
void* callback_eval_user_data);
|
||||
std::string sd_backend_resolve_name(const std::string& name);
|
||||
bool sd_parse_backend_assignment(const std::string& spec, SDBackendAssignment* assignment, std::string* error);
|
||||
bool sd_parse_flash_attention_assignment(const std::string& spec, SDBackendAssignment* assignment, std::string* error);
|
||||
bool sd_parse_conv_direct_assignment(const std::string& spec, SDBackendAssignment* assignment, std::string* error);
|
||||
const char* sd_backend_module_name(SDBackendModule module);
|
||||
void ggml_ext_im_set_f32_1d(const struct ggml_tensor* tensor, int i, float value);
|
||||
bool add_rpc_devices(const std::string& servers);
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
#include "core/probe/backend_probe.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <exception>
|
||||
|
||||
#include "core/ggml_extend_backend.h"
|
||||
#include "core/util.h"
|
||||
#include "ggml-alloc.h"
|
||||
|
||||
BackendProbeGraph::BackendProbeGraph(ggml_backend_t backend, size_t graph_size)
|
||||
: backend_(backend), graph_size_(graph_size) {
|
||||
ctx = ggml_init({graph_size * ggml_tensor_overhead() + ggml_graph_overhead_custom(graph_size, false), nullptr, true});
|
||||
}
|
||||
|
||||
BackendProbeGraph::~BackendProbeGraph() {
|
||||
ggml_backend_buffer_free(buffer_);
|
||||
ggml_free(ctx);
|
||||
}
|
||||
|
||||
bool BackendProbeGraph::supported() {
|
||||
if (backend_ == nullptr || ctx == nullptr || output == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (graph_ == nullptr) {
|
||||
graph_ = ggml_new_graph_custom(ctx, graph_size_, false);
|
||||
ggml_build_forward_expand(graph_, output);
|
||||
}
|
||||
for (int i = 0; i < ggml_graph_n_nodes(graph_); ++i) {
|
||||
if (!ggml_backend_supports_op(backend_, ggml_graph_node(graph_, i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BackendProbeGraph::allocate() {
|
||||
if (ctx == nullptr || backend_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (buffer_ == nullptr) {
|
||||
buffer_ = ggml_backend_alloc_ctx_tensors(ctx, backend_);
|
||||
}
|
||||
return buffer_ != nullptr;
|
||||
}
|
||||
|
||||
bool BackendProbeGraph::initialize_f32_inputs() {
|
||||
uint32_t state = 42;
|
||||
for (auto* tensor : inputs) {
|
||||
if (tensor == nullptr || tensor->type != GGML_TYPE_F32 || !ggml_is_contiguous(tensor)) {
|
||||
return false;
|
||||
}
|
||||
std::vector<float> values(static_cast<size_t>(ggml_nelements(tensor)));
|
||||
for (float& value : values) {
|
||||
state = state * 1664525u + 1013904223u;
|
||||
value = static_cast<float>(state >> 8) / 16777216.f - 0.5f;
|
||||
}
|
||||
ggml_backend_tensor_set(tensor, values.data(), 0, ggml_nbytes(tensor));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
double BackendProbeGraph::run() {
|
||||
if (graph_ == nullptr || buffer_ == nullptr) {
|
||||
return -1.;
|
||||
}
|
||||
ggml_backend_synchronize(backend_);
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
const auto status = ggml_backend_graph_compute(backend_, graph_);
|
||||
ggml_backend_synchronize(backend_);
|
||||
if (status != GGML_STATUS_SUCCESS) {
|
||||
return -1.;
|
||||
}
|
||||
return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
||||
}
|
||||
|
||||
static bool backend_probe_outputs_match(const BackendProbeGraph& reference,
|
||||
const BackendProbeGraph& candidate,
|
||||
const BackendProbeOptions& options) {
|
||||
if (!ggml_are_same_shape(reference.output, candidate.output) ||
|
||||
reference.output->type != GGML_TYPE_F32 || candidate.output->type != GGML_TYPE_F32 ||
|
||||
!ggml_is_contiguous(reference.output) || !ggml_is_contiguous(candidate.output)) {
|
||||
return false;
|
||||
}
|
||||
const size_t count = static_cast<size_t>(ggml_nelements(reference.output));
|
||||
std::vector<float> expected(count), actual(count);
|
||||
ggml_backend_tensor_get(reference.output, expected.data(), 0, count * sizeof(float));
|
||||
ggml_backend_tensor_get(candidate.output, actual.data(), 0, count * sizeof(float));
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (!std::isfinite(expected[i]) || !std::isfinite(actual[i]) ||
|
||||
std::abs(expected[i] - actual[i]) > options.absolute_tolerance + options.relative_tolerance * std::abs(expected[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BackendProbeResult sd_run_backend_probe(ggml_backend_t backend,
|
||||
int n_threads,
|
||||
const std::vector<BackendProbeCase>& cases,
|
||||
const BackendProbeOptions& options) {
|
||||
BackendProbeResult result;
|
||||
if (backend == nullptr || cases.empty() || options.graph_size == 0 || options.warmup_runs < 1 || options.measurement_runs < 1 ||
|
||||
!std::isfinite(options.time_budget_ms) || options.time_budget_ms < 0. ||
|
||||
!std::isfinite(options.min_improvement) || options.min_improvement < 0. || options.min_improvement >= 1. ||
|
||||
!std::isfinite(options.absolute_tolerance) || options.absolute_tolerance < 0.f ||
|
||||
!std::isfinite(options.relative_tolerance) || options.relative_tolerance < 0.f) {
|
||||
result.error = "invalid probe configuration";
|
||||
return result;
|
||||
}
|
||||
try {
|
||||
if (sd_backend_is_cpu(backend)) {
|
||||
sd_backend_cpu_set_n_threads(backend, n_threads > 0 ? n_threads : sd_get_num_physical_cores());
|
||||
}
|
||||
auto dev = ggml_backend_get_device(backend);
|
||||
if (dev != nullptr && !sd_backend_is_cpu(backend)) {
|
||||
size_t free_bytes = 0, total_bytes = 0;
|
||||
ggml_backend_dev_memory(dev, &free_bytes, &total_bytes);
|
||||
if (free_bytes > total_bytes || free_bytes < options.min_free_device_bytes) {
|
||||
result.error = "insufficient probe memory";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
double timed_ms = 0.;
|
||||
for (const auto& test : cases) {
|
||||
BackendProbeGraph reference(backend, options.graph_size), candidate(backend, options.graph_size);
|
||||
if (!test.build || reference.ctx == nullptr || candidate.ctx == nullptr) {
|
||||
result.error = "probe graph unavailable";
|
||||
return result;
|
||||
}
|
||||
test.build(reference, false);
|
||||
test.build(candidate, true);
|
||||
if (!reference.supported() || !candidate.supported()) {
|
||||
result.error = "unsupported probe graph";
|
||||
return result;
|
||||
}
|
||||
if (!reference.allocate() || !candidate.allocate()) {
|
||||
result.error = "probe allocation failed";
|
||||
return result;
|
||||
}
|
||||
auto initialize = [&](BackendProbeGraph& graph) {
|
||||
return test.initialize ? test.initialize(graph) : graph.initialize_f32_inputs();
|
||||
};
|
||||
if (!initialize(reference) || !initialize(candidate)) {
|
||||
result.error = "probe input initialization failed";
|
||||
return result;
|
||||
}
|
||||
for (int i = 0; i < options.warmup_runs; ++i) {
|
||||
if (reference.run() < 0. || candidate.run() < 0.) {
|
||||
result.error = "probe warmup failed";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (!(test.compare ? test.compare(reference, candidate) : backend_probe_outputs_match(reference, candidate, options))) {
|
||||
result.error = "probe output check failed";
|
||||
return result;
|
||||
}
|
||||
std::vector<double> reference_times(options.measurement_runs), candidate_times(options.measurement_runs);
|
||||
for (int i = 0; i < options.measurement_runs; ++i) {
|
||||
if (timed_ms >= options.time_budget_ms) {
|
||||
result.error = "probe time budget exhausted";
|
||||
return result;
|
||||
}
|
||||
// Alternate order so clock ramp-up does not consistently favor one path.
|
||||
if (i % 2 == 0) {
|
||||
reference_times[i] = reference.run();
|
||||
candidate_times[i] = candidate.run();
|
||||
} else {
|
||||
candidate_times[i] = candidate.run();
|
||||
reference_times[i] = reference.run();
|
||||
}
|
||||
if (reference_times[i] < 0. || candidate_times[i] < 0.) {
|
||||
result.error = "probe execution failed";
|
||||
return result;
|
||||
}
|
||||
timed_ms += reference_times[i] + candidate_times[i];
|
||||
}
|
||||
auto median = [](std::vector<double>& values) {
|
||||
std::sort(values.begin(), values.end());
|
||||
const size_t mid = values.size() / 2;
|
||||
return values.size() % 2 ? values[mid] : (values[mid - 1] + values[mid]) * 0.5;
|
||||
};
|
||||
result.reference_ms += median(reference_times);
|
||||
result.candidate_ms += median(candidate_times);
|
||||
}
|
||||
result.completed = true;
|
||||
result.candidate_faster = result.candidate_ms < result.reference_ms * (1. - options.min_improvement);
|
||||
} catch (const std::exception& error) {
|
||||
result.error = error.what();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef __SD_CORE_PROBE_BACKEND_PROBE_H__
|
||||
#define __SD_CORE_PROBE_BACKEND_PROBE_H__
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ggml-backend.h"
|
||||
|
||||
struct BackendProbeOptions {
|
||||
size_t graph_size = 128;
|
||||
int warmup_runs = 1;
|
||||
int measurement_runs = 5;
|
||||
double time_budget_ms = 1000.;
|
||||
size_t min_free_device_bytes = 128ULL * 1024 * 1024;
|
||||
double min_improvement = 0.05;
|
||||
float absolute_tolerance = 0.001f;
|
||||
float relative_tolerance = 0.02f;
|
||||
};
|
||||
|
||||
class BackendProbeGraph {
|
||||
ggml_backend_t backend_;
|
||||
size_t graph_size_;
|
||||
ggml_backend_buffer_t buffer_ = nullptr;
|
||||
ggml_cgraph* graph_ = nullptr;
|
||||
|
||||
public:
|
||||
ggml_context* ctx = nullptr;
|
||||
std::vector<ggml_tensor*> inputs;
|
||||
ggml_tensor* output = nullptr;
|
||||
|
||||
explicit BackendProbeGraph(ggml_backend_t backend, size_t graph_size = 128);
|
||||
~BackendProbeGraph();
|
||||
BackendProbeGraph(const BackendProbeGraph&) = delete;
|
||||
BackendProbeGraph& operator=(const BackendProbeGraph&) = delete;
|
||||
|
||||
bool supported();
|
||||
bool allocate();
|
||||
bool initialize_f32_inputs();
|
||||
double run();
|
||||
};
|
||||
|
||||
struct BackendProbeCase {
|
||||
// Variants must be repeatable without mutating inputs; leave output null if unavailable.
|
||||
std::function<void(BackendProbeGraph&, bool candidate)> build;
|
||||
// Optional overrides for non-F32 inputs/outputs or operation-specific data.
|
||||
std::function<bool(BackendProbeGraph&)> initialize;
|
||||
std::function<bool(const BackendProbeGraph&, const BackendProbeGraph&)> compare;
|
||||
};
|
||||
|
||||
struct BackendProbeResult {
|
||||
bool completed = false;
|
||||
bool candidate_faster = false;
|
||||
double reference_ms = 0.;
|
||||
double candidate_ms = 0.;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
BackendProbeResult sd_run_backend_probe(ggml_backend_t backend,
|
||||
int n_threads,
|
||||
const std::vector<BackendProbeCase>& cases,
|
||||
const BackendProbeOptions& options = {});
|
||||
|
||||
#endif // __SD_CORE_PROBE_BACKEND_PROBE_H__
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "core/probe/conv_direct.h"
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include "core/ggml_extend_backend.h"
|
||||
#include "core/probe/backend_probe.h"
|
||||
#include "core/util.h"
|
||||
|
||||
static void build_conv_direct_probe(BackendProbeGraph& graph, bool conv3d) {
|
||||
constexpr int channels = 64;
|
||||
if (conv3d) {
|
||||
auto input = ggml_new_tensor_4d(graph.ctx, GGML_TYPE_F32, 16, 16, 4, channels);
|
||||
auto weight = ggml_new_tensor_4d(graph.ctx, GGML_TYPE_F16, 3, 3, 3, channels * channels);
|
||||
graph.inputs = {input, weight};
|
||||
graph.output = ggml_conv_3d_direct(graph.ctx, weight, input, 1, 1, 1, 1, 1, 1, 1, 1, 1, channels, 1, channels);
|
||||
} else {
|
||||
auto input = ggml_new_tensor_4d(graph.ctx, GGML_TYPE_F32, 32, 32, channels, 1);
|
||||
auto weight = ggml_new_tensor_4d(graph.ctx, GGML_TYPE_F16, 3, 3, channels, channels);
|
||||
graph.inputs = {input, weight};
|
||||
graph.output = ggml_conv_2d_direct(graph.ctx, weight, input, 1, 1, 1, 1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
bool sd_probe_conv_direct(ggml_backend_t backend, bool conv3d) {
|
||||
if (backend == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const char* name = ggml_backend_name(backend);
|
||||
const int dims = conv3d ? 3 : 2;
|
||||
try {
|
||||
BackendProbeGraph capability(backend);
|
||||
if (capability.ctx == nullptr) {
|
||||
LOG_INFO("conv%dd direct auto: %s -> off (probe graph unavailable)", dims, name);
|
||||
return false;
|
||||
}
|
||||
build_conv_direct_probe(capability, conv3d);
|
||||
if (!capability.supported()) {
|
||||
LOG_INFO("conv%dd direct auto: %s -> off (unsupported probe graph)", dims, name);
|
||||
return false;
|
||||
}
|
||||
if (sd_backend_supports_conv_implicit_gemm(backend, conv3d)) {
|
||||
LOG_INFO("conv%dd direct auto: %s -> on (implicit GEMM)", dims, name);
|
||||
return true;
|
||||
}
|
||||
LOG_INFO("conv%dd direct auto: %s -> off (no implicit GEMM path)", dims, name);
|
||||
return false;
|
||||
} catch (const std::exception& error) {
|
||||
LOG_WARN("conv%dd direct auto: %s -> off (probe failed: %s)", dims, name, error.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef __SD_CORE_PROBE_CONV_DIRECT_H__
|
||||
#define __SD_CORE_PROBE_CONV_DIRECT_H__
|
||||
|
||||
#include "ggml-backend.h"
|
||||
|
||||
bool sd_probe_conv_direct(ggml_backend_t backend, bool conv3d);
|
||||
|
||||
#endif // __SD_CORE_PROBE_CONV_DIRECT_H__
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "core/probe/flash_attn.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "core/ggml_extend.h"
|
||||
#include "core/ggml_extend_backend.h"
|
||||
#include "core/probe/backend_probe.h"
|
||||
#include "core/util.h"
|
||||
|
||||
static bool is_vulkan_nvidia_rtx(ggml_backend_t backend) {
|
||||
auto device = ggml_backend_get_device(backend);
|
||||
if (device == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto reg = ggml_backend_dev_backend_reg(device);
|
||||
if (reg == nullptr || std::strcmp(ggml_backend_reg_name(reg), "Vulkan") != 0) {
|
||||
return false;
|
||||
}
|
||||
const char* description = ggml_backend_dev_description(device);
|
||||
if (description == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::istringstream stream(description);
|
||||
std::string token;
|
||||
bool nvidia = false, rtx = false;
|
||||
while (stream >> token) {
|
||||
nvidia = nvidia || token == "NVIDIA";
|
||||
rtx = rtx || token == "RTX";
|
||||
}
|
||||
return nvidia && rtx;
|
||||
}
|
||||
|
||||
static void build_flash_attention_probe(BackendProbeGraph& graph, ggml_backend_t backend, int length, int head_size, bool flash) {
|
||||
constexpr int heads = 4;
|
||||
auto q = ggml_new_tensor_3d(graph.ctx, GGML_TYPE_F32, head_size * heads, length, 1);
|
||||
auto k = ggml_dup_tensor(graph.ctx, q);
|
||||
auto v = ggml_dup_tensor(graph.ctx, q);
|
||||
graph.inputs = {q, k, v};
|
||||
bool used_flash = false;
|
||||
auto output = ggml_ext_attention_ext(graph.ctx, backend, q, k, v, heads, nullptr, false, flash, 1.f, false, &used_flash);
|
||||
graph.output = flash && !used_flash ? nullptr : output;
|
||||
}
|
||||
|
||||
bool sd_probe_flash_attention(ggml_backend_t backend, int n_threads) {
|
||||
if (backend == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const char* name = ggml_backend_name(backend);
|
||||
try {
|
||||
BackendProbeGraph capability(backend);
|
||||
if (capability.ctx == nullptr) {
|
||||
LOG_INFO("flash attention auto: %s -> off (probe graph unavailable)", name);
|
||||
return false;
|
||||
}
|
||||
build_flash_attention_probe(capability, backend, 512, 64, true);
|
||||
if (!capability.supported()) {
|
||||
LOG_INFO("flash attention auto: %s -> off (unsupported probe graph)", name);
|
||||
return false;
|
||||
}
|
||||
if (sd_backend_is_cpu(backend)) {
|
||||
LOG_INFO("flash attention auto: %s -> on (CPU)", name);
|
||||
return true;
|
||||
}
|
||||
if (sd_backend_supports_cuda_mma(backend)) {
|
||||
LOG_INFO("flash attention auto: %s -> on (CUDA MMA)", name);
|
||||
return true;
|
||||
}
|
||||
if (sd_backend_supports_hip_mma(backend)) {
|
||||
LOG_INFO("flash attention auto: %s -> on (HIP MMA)", name);
|
||||
return true;
|
||||
}
|
||||
if (is_vulkan_nvidia_rtx(backend)) {
|
||||
LOG_INFO("flash attention auto: %s -> on (Vulkan NVIDIA RTX)", name);
|
||||
return true;
|
||||
}
|
||||
std::vector<BackendProbeCase> cases;
|
||||
for (const auto& shape : {std::array<int, 2>{512, 64}, std::array<int, 2>{1024, 128}}) {
|
||||
BackendProbeCase test;
|
||||
test.build = [backend, shape](BackendProbeGraph& graph, bool candidate) {
|
||||
build_flash_attention_probe(graph, backend, shape[0], shape[1], candidate);
|
||||
};
|
||||
cases.push_back(std::move(test));
|
||||
}
|
||||
const auto result = sd_run_backend_probe(backend, n_threads, cases);
|
||||
if (!result.completed) {
|
||||
LOG_WARN("flash attention auto: %s -> off (%s)", name, result.error.c_str());
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("flash attention auto: %s -> %s (probe: normal %.3f ms, flash %.3f ms)",
|
||||
name, result.candidate_faster ? "on" : "off", result.reference_ms, result.candidate_ms);
|
||||
return result.candidate_faster;
|
||||
} catch (const std::exception& error) {
|
||||
LOG_WARN("flash attention auto: %s -> off (probe failed: %s)", name, error.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef __SD_CORE_PROBE_FLASH_ATTN_H__
|
||||
#define __SD_CORE_PROBE_FLASH_ATTN_H__
|
||||
|
||||
#include "ggml-backend.h"
|
||||
|
||||
bool sd_probe_flash_attention(ggml_backend_t backend, int n_threads);
|
||||
|
||||
#endif // __SD_CORE_PROBE_FLASH_ATTN_H__
|
||||
+43
-10
@@ -62,6 +62,21 @@ void replace_all_chars(std::string& str, char target, char replacement) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string escape_newlines(const std::string& text) {
|
||||
std::string escaped;
|
||||
escaped.reserve(text.size());
|
||||
for (char ch : text) {
|
||||
if (ch == '\n') {
|
||||
escaped += "\\n";
|
||||
} else if (ch == '\r') {
|
||||
escaped += "\\r";
|
||||
} else {
|
||||
escaped += ch;
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
static std::string sd_vformat(const char* fmt, va_list ap) {
|
||||
char small[128];
|
||||
va_list ap2;
|
||||
@@ -360,12 +375,13 @@ int32_t sd_get_num_physical_cores() {
|
||||
static sd_progress_cb_t sd_progress_cb = nullptr;
|
||||
void* sd_progress_cb_data = nullptr;
|
||||
|
||||
static sd_preview_cb_t sd_preview_cb = nullptr;
|
||||
static void* sd_preview_cb_data = nullptr;
|
||||
preview_t sd_preview_mode = PREVIEW_NONE;
|
||||
int sd_preview_interval = 1;
|
||||
bool sd_preview_denoised = true;
|
||||
bool sd_preview_noisy = false;
|
||||
static sd_preview_info_t sd_preview_info = {};
|
||||
static sd_preview_cb_t sd_preview_cb = nullptr;
|
||||
static void* sd_preview_cb_data = nullptr;
|
||||
preview_t sd_preview_mode = PREVIEW_NONE;
|
||||
int sd_preview_interval = 1;
|
||||
bool sd_preview_denoised = true;
|
||||
bool sd_preview_noisy = false;
|
||||
|
||||
static sd_graph_eval_callback_t sd_backend_eval_cb = nullptr;
|
||||
static void* sd_backend_eval_cb_data = nullptr;
|
||||
@@ -644,9 +660,14 @@ void* sd_log_cb_data = nullptr;
|
||||
static void sd_log_dispatch(sd_log_level_t level, const std::string& origin, const std::string& text) {
|
||||
if (sd_log_cb == nullptr)
|
||||
return;
|
||||
std::string message = origin + " - " + text;
|
||||
if (message.back() != '\n') {
|
||||
message += '\n';
|
||||
std::string message = text;
|
||||
while (!message.empty() && (message.back() == '\n' || message.back() == '\r')) {
|
||||
message.pop_back();
|
||||
}
|
||||
if (origin == "ggml") {
|
||||
message = origin + ": " + message + '\n';
|
||||
} else {
|
||||
message += " --- " + origin + '\n';
|
||||
}
|
||||
sd_log_cb(level, message.c_str(), sd_log_cb_data);
|
||||
}
|
||||
@@ -656,7 +677,7 @@ void log_printf(sd_log_level_t level, const char* file, int line, const char* fo
|
||||
va_start(args, format);
|
||||
std::string message = sd_vformat(format, args);
|
||||
va_end(args);
|
||||
std::string origin = sd_format("%s:%-4d", sd_basename(file).c_str(), line);
|
||||
std::string origin = sd_format("%s:%d", sd_basename(file).c_str(), line);
|
||||
sd_log_dispatch(level, origin, message);
|
||||
}
|
||||
|
||||
@@ -691,6 +712,7 @@ void sd_set_progress_callback(sd_progress_cb_t cb, void* data) {
|
||||
sd_progress_cb_data = data;
|
||||
}
|
||||
void sd_set_preview_callback(sd_preview_cb_t cb, preview_t mode, int interval, bool denoised, bool noisy, void* data) {
|
||||
sd_preview_info = {};
|
||||
sd_preview_cb = cb;
|
||||
sd_preview_cb_data = data;
|
||||
sd_preview_mode = mode;
|
||||
@@ -704,6 +726,17 @@ void sd_set_backend_eval_callback(sd_graph_eval_callback_t cb, void* data) {
|
||||
sd_backend_eval_cb_data = data;
|
||||
}
|
||||
|
||||
void sd_begin_preview_pass(int total_steps) {
|
||||
if (sd_preview_cb != nullptr) {
|
||||
++sd_preview_info.sample_pass;
|
||||
sd_preview_info.total_steps = total_steps;
|
||||
}
|
||||
}
|
||||
|
||||
sd_preview_info_t sd_get_preview_info() {
|
||||
return sd_preview_info;
|
||||
}
|
||||
|
||||
sd_preview_cb_t sd_get_preview_callback() {
|
||||
return sd_preview_cb;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ bool contains(const std::string& str, const std::string& substr);
|
||||
std::string sd_format(const char* fmt, ...);
|
||||
|
||||
void replace_all_chars(std::string& str, char target, char replacement);
|
||||
std::string escape_newlines(const std::string& text);
|
||||
|
||||
int round_up_to(int value, int base);
|
||||
|
||||
@@ -101,6 +102,7 @@ std::vector<std::pair<std::string, float>> split_quotation_attention(
|
||||
sd_progress_cb_t sd_get_progress_callback();
|
||||
void* sd_get_progress_callback_data();
|
||||
|
||||
void sd_begin_preview_pass(int total_steps);
|
||||
sd_preview_cb_t sd_get_preview_callback();
|
||||
void* sd_get_preview_callback_data();
|
||||
preview_t sd_get_preview_mode();
|
||||
|
||||
+12
-2
@@ -63,6 +63,9 @@ enum SDVersion {
|
||||
VERSION_LLADA_IMAGE,
|
||||
VERSION_ESRGAN,
|
||||
VERSION_PIXART,
|
||||
VERSION_MING_IMAGE,
|
||||
VERSION_Z_IMAGE_L2P,
|
||||
VERSION_IRIS,
|
||||
VERSION_COUNT,
|
||||
};
|
||||
|
||||
@@ -175,6 +178,10 @@ static inline bool sd_version_is_z_image(SDVersion version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool sd_version_is_z_image_l2p(SDVersion version) {
|
||||
return version == VERSION_Z_IMAGE_L2P;
|
||||
}
|
||||
|
||||
static inline bool sd_version_is_llada_image(SDVersion version) {
|
||||
if (version == VERSION_LLADA_IMAGE) {
|
||||
return true;
|
||||
@@ -280,7 +287,7 @@ static inline bool sd_version_uses_flux2_vae(SDVersion version) {
|
||||
}
|
||||
|
||||
static inline bool sd_version_uses_wan_vae(SDVersion version) {
|
||||
if (sd_version_is_wan(version) || sd_version_is_lingbot_video(version) || sd_version_is_qwen_image(version) || sd_version_is_krea2(version) || sd_version_is_anima(version)) {
|
||||
if (sd_version_is_wan(version) || sd_version_is_lingbot_video(version) || sd_version_is_qwen_image(version) || sd_version_is_krea2(version) || sd_version_is_anima(version) || version == VERSION_MING_IMAGE) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -314,6 +321,8 @@ static inline bool sd_version_is_dit(SDVersion version) {
|
||||
version == VERSION_HIDREAM_O1 ||
|
||||
sd_version_is_anima(version) ||
|
||||
sd_version_is_z_image(version) ||
|
||||
sd_version_is_z_image_l2p(version) ||
|
||||
version == VERSION_MING_IMAGE ||
|
||||
sd_version_is_llada_image(version) ||
|
||||
sd_version_is_boogu_image(version) ||
|
||||
sd_version_is_ernie_image(version) ||
|
||||
@@ -326,7 +335,8 @@ static inline bool sd_version_is_dit(SDVersion version) {
|
||||
sd_version_is_krea2(version) ||
|
||||
sd_version_is_mage_flow(version) ||
|
||||
sd_version_is_sensenova_u1(version) ||
|
||||
sd_version_is_pixart(version)) {
|
||||
sd_version_is_pixart(version) ||
|
||||
version == VERSION_IRIS) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -11,7 +11,9 @@ ggml_tensor* ggml_ext_merge_lora(ggml_context* ctx,
|
||||
ggml_tensor* lora_mid) {
|
||||
ggml_tensor* updown;
|
||||
// flat lora tensors to multiply it
|
||||
int64_t lora_up_rows = lora_up->ne[ggml_n_dims(lora_up) - 1];
|
||||
// ggml_n_dims drops the output dimension when a linear layer has one output.
|
||||
int lora_up_n_dims = ggml_n_dims(lora_up);
|
||||
int64_t lora_up_rows = lora_up_n_dims == 1 ? 1 : lora_up->ne[lora_up_n_dims - 1];
|
||||
lora_up = ggml_reshape_2d(ctx, lora_up, ggml_nelements(lora_up) / lora_up_rows, lora_up_rows);
|
||||
auto lora_down_n_dims = ggml_n_dims(lora_down);
|
||||
// assume n_dims should always be a multiple of 2 (otherwise rank 1 doesn't work)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef __SD_MODEL_COMMON_GGML_BLOCK_HPP__
|
||||
#define __SD_MODEL_COMMON_GGML_BLOCK_HPP__
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -204,6 +205,11 @@ public:
|
||||
force_prec_f32 = force_prec_f32_;
|
||||
}
|
||||
|
||||
bool has_quantized_weight() const {
|
||||
auto it = params.find("weight");
|
||||
return it != params.end() && it->second != nullptr && ggml_is_quantized(it->second->type);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
ggml_tensor* w = params["weight"];
|
||||
const float scale = ctx->linear_scale > 0.f ? ctx->linear_scale : this->scale;
|
||||
@@ -214,7 +220,7 @@ public:
|
||||
if (ctx->backend != nullptr) {
|
||||
ggml_tensor* fp8_matmul = ggml_mul_mat(ctx->ggml_ctx, w, x);
|
||||
if (force_prec_f32) {
|
||||
ggml_mul_mat_set_prec(fp8_matmul, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(fp8_matmul, GGML_PREC_F32);
|
||||
}
|
||||
supports_fp8_matmul = ggml_backend_supports_op(ctx->backend, fp8_matmul);
|
||||
}
|
||||
@@ -335,15 +341,75 @@ class Embedding : public UnaryBlock {
|
||||
protected:
|
||||
int64_t embedding_dim;
|
||||
int64_t num_embeddings;
|
||||
bool is_int8_tensorwise = false;
|
||||
int int8_convrot_group_size = 0;
|
||||
|
||||
static void get_rows_i8(ggml_tensor* dst, int ith, int nth, void* userdata) {
|
||||
const auto* embedding = static_cast<const Embedding*>(userdata);
|
||||
const int group_size = embedding->int8_convrot_group_size;
|
||||
const auto* weight = dst->src[0];
|
||||
const auto* input_ids = static_cast<const int32_t*>(dst->src[1]->data);
|
||||
const auto* scales = static_cast<const float*>(dst->src[2]->data);
|
||||
const bool scalar_scale = ggml_nelements(dst->src[2]) == 1;
|
||||
const float normalization = group_size > 0 ? 1.f / std::sqrt(static_cast<float>(group_size)) : 1.f;
|
||||
for (int64_t row = ith; row < dst->ne[1]; row += nth) {
|
||||
const int32_t token = input_ids[row];
|
||||
GGML_ASSERT(token >= 0 && token < weight->ne[1]);
|
||||
const auto* src = reinterpret_cast<const int8_t*>(static_cast<const char*>(weight->data) + token * weight->nb[1]);
|
||||
auto* out = reinterpret_cast<float*>(static_cast<char*>(dst->data) + row * dst->nb[1]);
|
||||
const float scale = scales[scalar_scale ? 0 : token] * normalization;
|
||||
for (int64_t i = 0; i < weight->ne[0]; ++i) {
|
||||
out[i] = static_cast<float>(src[i]) * scale;
|
||||
}
|
||||
// The regular Hadamard rotation is symmetric and its own inverse.
|
||||
for (int stride = 1; stride < group_size; stride *= 4) {
|
||||
for (int64_t base = 0; base < weight->ne[0]; base += 4 * stride) {
|
||||
for (int j = 0; j < stride; ++j) {
|
||||
float* values = out + base + j;
|
||||
const float a = values[0];
|
||||
const float b = values[stride];
|
||||
const float c = values[2 * stride];
|
||||
const float d = values[3 * stride];
|
||||
values[0] = a + b + c - d;
|
||||
values[stride] = a + b - c + d;
|
||||
values[2 * stride] = a - b + c + d;
|
||||
values[3 * stride] = -a + b + c + d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map, const std::string prefix = "") override {
|
||||
enum ggml_type wtype = get_type(prefix + "weight", tensor_storage_map, GGML_TYPE_F32);
|
||||
if (!support_get_rows(wtype)) {
|
||||
auto weight_storage = tensor_storage_map.find(prefix + "weight");
|
||||
is_int8_tensorwise = weight_storage != tensor_storage_map.end() && weight_storage->second.is_int8_tensorwise;
|
||||
int8_convrot_group_size = 0;
|
||||
enum ggml_type wtype = get_type(prefix + "weight", tensor_storage_map, GGML_TYPE_F32);
|
||||
if (is_int8_tensorwise) {
|
||||
GGML_ASSERT(wtype == GGML_TYPE_I8);
|
||||
auto scale_storage = tensor_storage_map.find(prefix + "weight_scale");
|
||||
GGML_ASSERT(scale_storage != tensor_storage_map.end());
|
||||
const int64_t scale_nelements = scale_storage->second.nelements();
|
||||
GGML_ASSERT(scale_nelements == 1 || scale_nelements == num_embeddings);
|
||||
params["weight_scale"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, scale_nelements);
|
||||
if (weight_storage->second.int8_convrot) {
|
||||
int8_convrot_group_size = weight_storage->second.int8_convrot_group_size;
|
||||
int remainder = int8_convrot_group_size;
|
||||
while (remainder > 1 && remainder % 4 == 0) {
|
||||
remainder /= 4;
|
||||
}
|
||||
GGML_ASSERT(remainder == 1 && embedding_dim % int8_convrot_group_size == 0);
|
||||
}
|
||||
} else if (!support_get_rows(wtype)) {
|
||||
wtype = GGML_TYPE_F32;
|
||||
}
|
||||
params["weight"] = ggml_new_tensor_2d(ctx, wtype, embedding_dim, num_embeddings);
|
||||
}
|
||||
|
||||
enum ggml_op param_usage_op(const std::string& name) const override {
|
||||
if (is_int8_tensorwise) {
|
||||
return GGML_OP_CUSTOM;
|
||||
}
|
||||
return name == "weight" ? GGML_OP_GET_ROWS : GGML_OP_NONE;
|
||||
}
|
||||
|
||||
@@ -363,9 +429,17 @@ public:
|
||||
int64_t n = input_ids->ne[1];
|
||||
input_ids = ggml_reshape_1d(ctx->ggml_ctx, input_ids, input_ids->ne[0] * input_ids->ne[1]);
|
||||
|
||||
input_ids = ggml_reshape_3d(ctx->ggml_ctx, input_ids, input_ids->ne[0], 1, input_ids->ne[1]);
|
||||
auto embedding = ggml_get_rows(ctx->ggml_ctx, weight, input_ids);
|
||||
embedding = ggml_reshape_3d(ctx->ggml_ctx, embedding, embedding->ne[0], embedding->ne[1] / n, n);
|
||||
ggml_tensor* embedding;
|
||||
if (is_int8_tensorwise) {
|
||||
GGML_ASSERT(input_ids->type == GGML_TYPE_I32);
|
||||
ggml_tensor* args[] = {weight, input_ids, params["weight_scale"]};
|
||||
embedding = ggml_custom_4d(ctx->ggml_ctx, GGML_TYPE_F32, embedding_dim, input_ids->ne[0], 1, 1,
|
||||
args, 3, get_rows_i8, GGML_N_TASKS_MAX, this);
|
||||
} else {
|
||||
input_ids = ggml_reshape_3d(ctx->ggml_ctx, input_ids, input_ids->ne[0], 1, input_ids->ne[1]);
|
||||
embedding = ggml_get_rows(ctx->ggml_ctx, weight, input_ids);
|
||||
}
|
||||
embedding = ggml_reshape_3d(ctx->ggml_ctx, embedding, embedding->ne[0], embedding->ne[1] / n, n);
|
||||
|
||||
// [N, n_token, embedding_dim]
|
||||
return embedding;
|
||||
|
||||
@@ -1067,10 +1067,22 @@ namespace Rope {
|
||||
return result;
|
||||
}
|
||||
|
||||
__STATIC_INLINE__ ggml_tensor* apply_rope(ggml_context* ctx,
|
||||
__STATIC_INLINE__ ggml_tensor* apply_rope(GGMLRunnerContext* runner_ctx,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* pe,
|
||||
bool rope_interleaved = true) {
|
||||
auto ctx = runner_ctx->ggml_ctx;
|
||||
#ifndef SD_USE_UPSTREAM_GGML
|
||||
auto backend = runner_ctx->backend;
|
||||
if (backend != nullptr && x->type == GGML_TYPE_F32 && pe->type == GGML_TYPE_F32 &&
|
||||
x->ne[0] % 2 == 0 && pe->ne[0] == 2 && pe->ne[1] == 2 &&
|
||||
pe->ne[2] == x->ne[0] / 2 && pe->ne[3] == x->ne[2]) {
|
||||
auto out = ggml_rope_apply(ctx, x, pe, rope_interleaved);
|
||||
if (ggml_backend_supports_op(backend, out)) {
|
||||
return out;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// x: [N, L, n_head, d_head]
|
||||
// pe: [L, d_head/2, 2, 2], [[cos, -sin], [sin, cos]]
|
||||
int64_t d_head = x->ne[0];
|
||||
@@ -1082,8 +1094,16 @@ namespace Rope {
|
||||
x = ggml_reshape_4d(ctx, x, 2, d_head / 2, L, n_head * N); // [N * n_head, L, d_head/2, 2]
|
||||
x = ggml_cont(ctx, ggml_permute(ctx, x, 3, 0, 1, 2)); // [2, N * n_head, L, d_head/2]
|
||||
} else {
|
||||
x = ggml_reshape_4d(ctx, x, d_head / 2, 2, L, n_head * N); // [N * n_head, L, 2, d_head/2]
|
||||
x = ggml_cont(ctx, ggml_ext_torch_permute(ctx, x, 0, 2, 3, 1)); // [2, N * n_head, L, d_head/2]
|
||||
auto x_0 = ggml_view_3d(ctx, x, d_head / 2, L, n_head * N, x->nb[1], x->nb[2], 0);
|
||||
auto x_1 = ggml_view_3d(ctx, x, d_head / 2, L, n_head * N, x->nb[1], x->nb[2], (d_head / 2) * x->nb[0]);
|
||||
|
||||
auto pe_2 = ggml_cont(ctx, ggml_permute(ctx, pe, 2, 3, 0, 1));
|
||||
auto cos = ggml_view_2d(ctx, pe_2, d_head / 2, L, pe_2->nb[1], 0);
|
||||
auto sin = ggml_view_2d(ctx, pe_2, d_head / 2, L, pe_2->nb[1], pe_2->nb[3]);
|
||||
|
||||
auto part_0 = ggml_sub(ctx, ggml_mul(ctx, x_0, cos), ggml_mul(ctx, x_1, sin));
|
||||
auto part_1 = ggml_add(ctx, ggml_mul(ctx, x_0, sin), ggml_mul(ctx, x_1, cos));
|
||||
return ggml_concat(ctx, part_0, part_1, 0);
|
||||
}
|
||||
|
||||
int64_t offset = x->nb[2] * x->ne[2];
|
||||
@@ -1101,10 +1121,7 @@ namespace Rope {
|
||||
auto pe_1 = ggml_view_3d(ctx, pe, pe->ne[0], pe->ne[1], pe->ne[2], pe->nb[1], pe->nb[2], offset * 1); // [L, d_head/2, 2]
|
||||
|
||||
auto x_out = ggml_add_inplace(ctx, ggml_mul(ctx, x_0, pe_0), ggml_mul(ctx, x_1, pe_1)); // [N * n_head, L, d_head/2, 2]
|
||||
if (!rope_interleaved) {
|
||||
x_out = ggml_cont(ctx, ggml_permute(ctx, x_out, 1, 0, 2, 3)); // [N * n_head, L, x, d_head/2]
|
||||
}
|
||||
x_out = ggml_reshape_3d(ctx, x_out, d_head, L, n_head * N); // [N*n_head, L, d_head]
|
||||
x_out = ggml_reshape_3d(ctx, x_out, d_head, L, n_head * N); // [N*n_head, L, d_head]
|
||||
return x_out;
|
||||
}
|
||||
|
||||
@@ -1121,8 +1138,8 @@ namespace Rope {
|
||||
// return: [N, L, n_head*d_head]
|
||||
int64_t n_head = q->ne[1];
|
||||
|
||||
q = apply_rope(ctx->ggml_ctx, q, pe, rope_interleaved); // [N*n_head, L, d_head]
|
||||
k = apply_rope(ctx->ggml_ctx, k, pe, rope_interleaved); // [N*n_head, L, d_head]
|
||||
q = apply_rope(ctx, q, pe, rope_interleaved); // [N*n_head, L, d_head]
|
||||
k = apply_rope(ctx, k, pe, rope_interleaved); // [N*n_head, L, d_head]
|
||||
|
||||
auto x = ggml_ext_attention_ext(ctx, q, k, v, n_head, mask, true, ctx->flash_attn_enabled, kv_scale); // [N, L, n_head*d_head]
|
||||
return x;
|
||||
|
||||
@@ -235,8 +235,8 @@ namespace Anima {
|
||||
if (pe_k == nullptr) {
|
||||
pe_k = pe_q;
|
||||
}
|
||||
auto q_rope = Rope::apply_rope(ctx->ggml_ctx, q4, pe_q, false);
|
||||
auto k_rope = Rope::apply_rope(ctx->ggml_ctx, k4, pe_k, false);
|
||||
auto q_rope = Rope::apply_rope(ctx, q4, pe_q, false);
|
||||
auto k_rope = Rope::apply_rope(ctx, k4, pe_k, false);
|
||||
attn_out = ggml_ext_attention_ext(ctx,
|
||||
q_rope,
|
||||
k_rope,
|
||||
|
||||
@@ -0,0 +1,450 @@
|
||||
#ifndef __SD_MODEL_DIFFUSION_IRIS_HPP__
|
||||
#define __SD_MODEL_DIFFUSION_IRIS_HPP__
|
||||
|
||||
#include <array>
|
||||
#include <stdexcept>
|
||||
#include "model/diffusion/pid.hpp"
|
||||
|
||||
namespace Iris {
|
||||
struct IrisConfig {
|
||||
int64_t hidden_size = 2560;
|
||||
int64_t depth = 24;
|
||||
int64_t dual_depth = 8;
|
||||
int64_t head_dim = 128;
|
||||
int64_t num_heads = 20;
|
||||
int64_t kv_heads = 5;
|
||||
int64_t mlp_dim = 6826;
|
||||
int64_t patch_size = 16;
|
||||
int64_t text_dim = 2560;
|
||||
int64_t text_len = 300;
|
||||
int64_t text_layers = 12;
|
||||
int64_t layer_heads = 32;
|
||||
int64_t layer_mlp_dim = 3328;
|
||||
int64_t pixel_dim = 16;
|
||||
int64_t pixel_attn_dim = 1280;
|
||||
int64_t pixel_heads = 10;
|
||||
int64_t pixel_depth = 4;
|
||||
|
||||
static IrisConfig detect_from_weights(const String2TensorStorage& weights, const std::string& prefix) {
|
||||
IrisConfig config;
|
||||
auto find = [&](const std::string& name) -> const TensorStorage& {
|
||||
auto it = weights.find(prefix + "." + name);
|
||||
if (it == weights.end()) {
|
||||
throw std::runtime_error("Iris-3B: missing weight " + prefix + "." + name);
|
||||
}
|
||||
return it->second;
|
||||
};
|
||||
config.hidden_size = find("s_embedder.proj.weight").ne[1];
|
||||
config.patch_size = static_cast<int64_t>(std::sqrt(find("s_embedder.proj.weight").ne[0] / 3));
|
||||
config.head_dim = find("blocks.0.attn.q_norm_x.weight").ne[0];
|
||||
config.num_heads = config.hidden_size / config.head_dim;
|
||||
config.kv_heads = find("blocks.0.attn.k_proj_x.weight").ne[1] / config.head_dim;
|
||||
config.mlp_dim = find("blocks.0.mlp_x.w1.weight").ne[1];
|
||||
config.text_dim = find("y_embedder.refiner.proj.weight").ne[0];
|
||||
config.text_len = find("y_pos_embedding").ne[1];
|
||||
config.text_layers = find("y_embedder.layer_pool.weight").ne[0];
|
||||
config.layer_mlp_dim = find("y_embedder.layer_blocks.0.mlp.0.weight").ne[1];
|
||||
config.pixel_dim = find("pixel_embedder.proj.weight").ne[1];
|
||||
config.pixel_attn_dim = find("pixel_blocks.0.compress.weight").ne[1];
|
||||
config.pixel_heads = config.pixel_attn_dim / find("pixel_blocks.0.attn.q_norm.weight").ne[0];
|
||||
config.depth = config.dual_depth = config.pixel_depth = 0;
|
||||
for (const auto& [name, tensor] : weights) {
|
||||
if (!starts_with(name, prefix + ".")) {
|
||||
continue;
|
||||
}
|
||||
auto parts = split_string(name.substr(prefix.size() + 1), '.');
|
||||
if (parts.size() > 2 && parts[0] == "blocks") {
|
||||
int64_t index = std::stoi(parts[1]);
|
||||
config.depth = std::max(config.depth, index + 1);
|
||||
if (parts[2] == "adaln_img") {
|
||||
config.dual_depth = std::max(config.dual_depth, index + 1);
|
||||
}
|
||||
} else if (parts.size() > 2 && parts[0] == "pixel_blocks") {
|
||||
config.pixel_depth = std::max(config.pixel_depth, int64_t(std::stoi(parts[1]) + 1));
|
||||
}
|
||||
}
|
||||
if (config.text_layers != 12 || config.text_dim != 2560 || config.text_len != 300 ||
|
||||
find("modulation_cores.adaln_img.weight").ne[1] != 6 * config.hidden_size ||
|
||||
find("pixel_blocks.0.adaln.weight").ne[1] != 4 * config.pixel_dim * config.patch_size * config.patch_size) {
|
||||
throw std::runtime_error("Iris-3B: unsupported checkpoint configuration");
|
||||
}
|
||||
LOG_VERBOSE("iris: hidden_size = %" PRId64 ", depth = %" PRId64 ", dual_depth = %" PRId64 ", heads = %" PRId64 "/%" PRId64 ", pixel_depth = %" PRId64,
|
||||
config.hidden_size, config.depth, config.dual_depth, config.num_heads, config.kv_heads, config.pixel_depth);
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
struct SelfAttention : public GGMLBlock {
|
||||
int64_t dim;
|
||||
int64_t heads;
|
||||
bool qk_norm;
|
||||
|
||||
SelfAttention(int64_t dim, int64_t heads, bool qk_norm = true)
|
||||
: dim(dim), heads(heads), qk_norm(qk_norm) {
|
||||
blocks["qkv"] = std::make_shared<Linear>(dim, 3 * dim, false);
|
||||
blocks["proj"] = std::make_shared<Linear>(dim, dim, true);
|
||||
if (qk_norm) {
|
||||
blocks["q_norm"] = std::make_shared<RMSNorm>(dim / heads, 1e-6f);
|
||||
blocks["k_norm"] = std::make_shared<RMSNorm>(dim / heads, 1e-6f);
|
||||
}
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* pe = nullptr, ggml_tensor* mask = nullptr) {
|
||||
auto c = ctx->ggml_ctx;
|
||||
auto qkv = split_qkv(c, std::dynamic_pointer_cast<Linear>(blocks["qkv"])->forward(ctx, x));
|
||||
auto q = ggml_reshape_4d(c, qkv[0], dim / heads, heads, x->ne[1], x->ne[2]);
|
||||
auto k = ggml_reshape_4d(c, qkv[1], dim / heads, heads, x->ne[1], x->ne[2]);
|
||||
auto v = ggml_reshape_4d(c, qkv[2], dim / heads, heads, x->ne[1], x->ne[2]);
|
||||
if (qk_norm) {
|
||||
q = std::dynamic_pointer_cast<RMSNorm>(blocks["q_norm"])->forward(ctx, q);
|
||||
k = std::dynamic_pointer_cast<RMSNorm>(blocks["k_norm"])->forward(ctx, k);
|
||||
}
|
||||
if (pe) {
|
||||
x = Rope::attention(ctx, q, k, v, pe, mask);
|
||||
} else {
|
||||
q = ggml_reshape_3d(c, q, dim, x->ne[1], x->ne[2]);
|
||||
k = ggml_reshape_3d(c, k, dim, x->ne[1], x->ne[2]);
|
||||
v = ggml_reshape_3d(c, v, dim, x->ne[1], x->ne[2]);
|
||||
x = ggml_ext_attention_ext(ctx, q, k, v, heads, mask, false, ctx->flash_attn_enabled);
|
||||
}
|
||||
return std::dynamic_pointer_cast<Linear>(blocks["proj"])->forward(ctx, x);
|
||||
}
|
||||
};
|
||||
|
||||
struct TextBlock : public GGMLBlock {
|
||||
bool layerwise;
|
||||
|
||||
TextBlock(int64_t dim, int64_t heads, int64_t mlp_dim, bool layerwise)
|
||||
: layerwise(layerwise) {
|
||||
blocks["norm1"] = std::make_shared<RMSNorm>(dim, 1e-6f);
|
||||
blocks["norm2"] = std::make_shared<RMSNorm>(dim, 1e-6f);
|
||||
blocks["attn"] = std::make_shared<SelfAttention>(dim, heads, !layerwise);
|
||||
if (layerwise) {
|
||||
blocks["mlp.0"] = std::make_shared<Linear>(dim, mlp_dim, true);
|
||||
blocks["mlp.2"] = std::make_shared<Linear>(mlp_dim, dim, true);
|
||||
} else {
|
||||
blocks["mlp"] = std::make_shared<Pid::FeedForward>(dim, mlp_dim);
|
||||
}
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* mask = nullptr) {
|
||||
auto h = std::dynamic_pointer_cast<RMSNorm>(blocks["norm1"])->forward(ctx, x);
|
||||
h = std::dynamic_pointer_cast<SelfAttention>(blocks["attn"])->forward(ctx, h, nullptr, mask);
|
||||
x = ggml_add(ctx->ggml_ctx, x, h);
|
||||
h = std::dynamic_pointer_cast<RMSNorm>(blocks["norm2"])->forward(ctx, x);
|
||||
if (layerwise) {
|
||||
h = std::dynamic_pointer_cast<Linear>(blocks["mlp.0"])->forward(ctx, h);
|
||||
h = ggml_silu(ctx->ggml_ctx, h);
|
||||
h = std::dynamic_pointer_cast<Linear>(blocks["mlp.2"])->forward(ctx, h);
|
||||
} else {
|
||||
h = std::dynamic_pointer_cast<Pid::FeedForward>(blocks["mlp"])->forward(ctx, h);
|
||||
}
|
||||
return ggml_add(ctx->ggml_ctx, x, h);
|
||||
}
|
||||
};
|
||||
|
||||
struct TextEmbedder : public GGMLBlock {
|
||||
IrisConfig config;
|
||||
|
||||
TextEmbedder(const IrisConfig& config)
|
||||
: config(config) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
blocks["layer_blocks." + std::to_string(i)] = std::make_shared<TextBlock>(config.text_dim, config.layer_heads, config.layer_mlp_dim, true);
|
||||
blocks["refiner.blocks." + std::to_string(i)] = std::make_shared<TextBlock>(config.hidden_size, config.num_heads, config.mlp_dim, false);
|
||||
}
|
||||
blocks["layer_pool"] = std::make_shared<Linear>(config.text_layers, 1, true);
|
||||
blocks["refiner.proj"] = std::make_shared<Linear>(config.text_dim, config.hidden_size, true);
|
||||
blocks["refiner.norm"] = std::make_shared<RMSNorm>(config.hidden_size, 1e-6f);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* mask) {
|
||||
auto c = ctx->ggml_ctx;
|
||||
int64_t tokens = x->ne[1];
|
||||
int64_t batch = x->ne[2];
|
||||
// The encoder concatenates selected layers within each token's feature dimension.
|
||||
x = ggml_reshape_3d(c, x, config.text_dim, config.text_layers, tokens * batch);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
x = std::dynamic_pointer_cast<TextBlock>(blocks["layer_blocks." + std::to_string(i)])->forward(ctx, x);
|
||||
}
|
||||
x = ggml_cont(c, ggml_permute(c, x, 1, 0, 2, 3));
|
||||
x = std::dynamic_pointer_cast<Linear>(blocks["layer_pool"])->forward(ctx, x);
|
||||
x = ggml_reshape_3d(c, x, config.text_dim, tokens, batch);
|
||||
x = std::dynamic_pointer_cast<Linear>(blocks["refiner.proj"])->forward(ctx, x);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
x = std::dynamic_pointer_cast<TextBlock>(blocks["refiner.blocks." + std::to_string(i)])->forward(ctx, x, mask);
|
||||
}
|
||||
return std::dynamic_pointer_cast<RMSNorm>(blocks["refiner.norm"])->forward(ctx, x);
|
||||
}
|
||||
};
|
||||
|
||||
struct TrunkBlock : public GGMLBlock {
|
||||
IrisConfig config;
|
||||
bool dual;
|
||||
|
||||
TrunkBlock(const IrisConfig& config, bool dual)
|
||||
: config(config), dual(dual) {
|
||||
int64_t d = config.hidden_size;
|
||||
for (const std::string stream : dual ? std::vector<std::string>{"x", "y"} : std::vector<std::string>{""}) {
|
||||
std::string suffix = stream.empty() ? "" : "_" + stream;
|
||||
std::string attn = dual ? "attn." : "";
|
||||
blocks[attn + "q_proj" + suffix] = std::make_shared<Linear>(d, d, false);
|
||||
blocks[attn + "k_proj" + suffix] = std::make_shared<Linear>(d, config.kv_heads * config.head_dim, false);
|
||||
blocks[attn + "v_proj" + suffix] = std::make_shared<Linear>(d, config.kv_heads * config.head_dim, false);
|
||||
blocks[attn + "q_norm" + suffix] = std::make_shared<RMSNorm>(config.head_dim, 1e-6f);
|
||||
blocks[attn + "k_norm" + suffix] = std::make_shared<RMSNorm>(config.head_dim, 1e-6f);
|
||||
blocks[dual ? "attn.proj" + suffix : "attn_proj"] = std::make_shared<Linear>(d, d, true);
|
||||
blocks["attn_gate" + suffix] = std::make_shared<Linear>(d, d, false);
|
||||
blocks["norm" + suffix + "1"] = std::make_shared<RMSNorm>(d, 1e-6f);
|
||||
blocks["norm" + suffix + "2"] = std::make_shared<RMSNorm>(d, 1e-6f);
|
||||
blocks["attn_post_norm" + suffix] = std::make_shared<RMSNorm>(d, 1e-6f);
|
||||
blocks["mlp_post_norm" + suffix] = std::make_shared<RMSNorm>(d, 1e-6f);
|
||||
blocks["mlp" + suffix] = std::make_shared<Pid::FeedForward>(d, config.mlp_dim);
|
||||
}
|
||||
}
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& = {}, const std::string = "") override {
|
||||
if (dual) {
|
||||
params["adaln_img.bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 6 * config.hidden_size);
|
||||
params["adaln_txt.bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 6 * config.hidden_size);
|
||||
} else {
|
||||
params["adaln.bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 6 * config.hidden_size);
|
||||
}
|
||||
}
|
||||
|
||||
ggml_tensor* norm(GGMLRunnerContext* ctx, const std::string& name, ggml_tensor* x) {
|
||||
return std::dynamic_pointer_cast<RMSNorm>(blocks[name])->forward(ctx, x);
|
||||
}
|
||||
|
||||
ggml_tensor* linear(GGMLRunnerContext* ctx, const std::string& name, ggml_tensor* x) {
|
||||
return std::dynamic_pointer_cast<Linear>(blocks[name])->forward(ctx, x);
|
||||
}
|
||||
|
||||
std::array<ggml_tensor*, 3> project(GGMLRunnerContext* ctx, ggml_tensor* h, const std::string& suffix) {
|
||||
auto c = ctx->ggml_ctx;
|
||||
std::string a = dual ? "attn." : "";
|
||||
auto q = linear(ctx, a + "q_proj" + suffix, h);
|
||||
auto k = linear(ctx, a + "k_proj" + suffix, h);
|
||||
auto v = linear(ctx, a + "v_proj" + suffix, h);
|
||||
q = ggml_reshape_4d(c, q, config.head_dim, config.num_heads, h->ne[1], h->ne[2]);
|
||||
k = ggml_reshape_4d(c, k, config.head_dim, config.kv_heads, h->ne[1], h->ne[2]);
|
||||
v = ggml_reshape_4d(c, v, config.head_dim, config.kv_heads, h->ne[1], h->ne[2]);
|
||||
return {norm(ctx, a + "q_norm" + suffix, q), norm(ctx, a + "k_norm" + suffix, k), v};
|
||||
}
|
||||
|
||||
ggml_tensor* finish(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* h, ggml_tensor* out, const std::vector<ggml_tensor*>& mods, const std::string& suffix) {
|
||||
auto c = ctx->ggml_ctx;
|
||||
out = ggml_mul(c, out, ggml_sigmoid(c, linear(ctx, "attn_gate" + suffix, h)));
|
||||
out = linear(ctx, dual ? "attn.proj" + suffix : "attn_proj", out);
|
||||
out = norm(ctx, "attn_post_norm" + suffix, out);
|
||||
x = ggml_add(c, x, ggml_mul(c, out, mods[2]));
|
||||
h = Pid::apply_adaln(c, norm(ctx, "norm" + suffix + "2", x), mods[3], mods[4]);
|
||||
h = std::dynamic_pointer_cast<Pid::FeedForward>(blocks["mlp" + suffix])->forward(ctx, h);
|
||||
h = norm(ctx, "mlp_post_norm" + suffix, h);
|
||||
return ggml_add(c, x, ggml_mul(c, h, mods[5]));
|
||||
}
|
||||
|
||||
std::pair<ggml_tensor*, ggml_tensor*> forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* y, ggml_tensor* img_mod, ggml_tensor* txt_mod, ggml_tensor* pe) {
|
||||
auto c = ctx->ggml_ctx;
|
||||
int64_t nt = y->ne[1];
|
||||
auto mx = ggml_ext_chunk(c, ggml_add(c, img_mod, params[dual ? "adaln_img.bias" : "adaln.bias"]), 6, 0);
|
||||
if (!dual) {
|
||||
auto tokens = ggml_concat(c, y, x, 1);
|
||||
auto h = Pid::apply_adaln(c, norm(ctx, "norm1", tokens), mx[0], mx[1]);
|
||||
auto qkv = project(ctx, h, "");
|
||||
auto out = Rope::attention(ctx, qkv[0], qkv[1], qkv[2], pe, nullptr);
|
||||
tokens = finish(ctx, tokens, h, out, mx, "");
|
||||
return {ggml_ext_slice(c, tokens, 1, nt, tokens->ne[1]), ggml_ext_slice(c, tokens, 1, 0, nt)};
|
||||
}
|
||||
auto my = ggml_ext_chunk(c, ggml_add(c, txt_mod, params["adaln_txt.bias"]), 6, 0);
|
||||
auto hx = Pid::apply_adaln(c, norm(ctx, "norm_x1", x), mx[0], mx[1]);
|
||||
auto hy = Pid::apply_adaln(c, norm(ctx, "norm_y1", y), my[0], my[1]);
|
||||
auto qx = project(ctx, hx, "_x");
|
||||
auto qy = project(ctx, hy, "_y");
|
||||
auto out = Rope::attention(ctx, ggml_concat(c, qy[0], qx[0], 2), ggml_concat(c, qy[1], qx[1], 2),
|
||||
ggml_concat(c, qy[2], qx[2], 2), pe, nullptr);
|
||||
x = finish(ctx, x, hx, ggml_ext_slice(c, out, 1, nt, out->ne[1]), mx, "_x");
|
||||
y = finish(ctx, y, hy, ggml_ext_slice(c, out, 1, 0, nt), my, "_y");
|
||||
return {x, y};
|
||||
}
|
||||
};
|
||||
|
||||
struct PixelBlock : public GGMLBlock {
|
||||
IrisConfig config;
|
||||
|
||||
PixelBlock(const IrisConfig& config)
|
||||
: config(config) {
|
||||
auto d = config.pixel_dim;
|
||||
auto flat = d * config.patch_size * config.patch_size;
|
||||
blocks["norm1"] = std::make_shared<RMSNorm>(d, 1e-6f);
|
||||
blocks["norm2"] = std::make_shared<RMSNorm>(d, 1e-6f);
|
||||
blocks["compress"] = std::make_shared<Linear>(flat, config.pixel_attn_dim, true);
|
||||
blocks["expand"] = std::make_shared<Linear>(config.pixel_attn_dim, flat, true);
|
||||
blocks["adaln"] = std::make_shared<Linear>(config.hidden_size, 4 * flat, true);
|
||||
blocks["attn"] = std::make_shared<SelfAttention>(config.pixel_attn_dim, config.pixel_heads);
|
||||
blocks["mlp.fc1"] = std::make_shared<Linear>(d, d * 4, true);
|
||||
blocks["mlp.fc2"] = std::make_shared<Linear>(d * 4, d, true);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* cond, ggml_tensor* pe) {
|
||||
auto c = ctx->ggml_ctx;
|
||||
auto d = config.pixel_dim;
|
||||
auto p2 = config.patch_size * config.patch_size;
|
||||
auto patches = cond->ne[1];
|
||||
auto batch = cond->ne[2];
|
||||
auto mods = std::dynamic_pointer_cast<Linear>(blocks["adaln"])->forward(ctx, cond);
|
||||
mods = ggml_reshape_3d(c, mods, 4 * d, p2, patches * batch);
|
||||
auto m = ggml_ext_chunk(c, mods, 4, 0);
|
||||
auto h = std::dynamic_pointer_cast<RMSNorm>(blocks["norm1"])->forward(ctx, x);
|
||||
h = ggml_reshape_3d(c, h, d * p2, patches, batch);
|
||||
h = std::dynamic_pointer_cast<Linear>(blocks["compress"])->forward(ctx, h);
|
||||
h = std::dynamic_pointer_cast<SelfAttention>(blocks["attn"])->forward(ctx, h, pe);
|
||||
h = std::dynamic_pointer_cast<Linear>(blocks["expand"])->forward(ctx, h);
|
||||
h = ggml_reshape_3d(c, h, d, p2, patches * batch);
|
||||
x = ggml_add(c, x, Pid::apply_adaln(c, h, m[1], m[0]));
|
||||
h = std::dynamic_pointer_cast<RMSNorm>(blocks["norm2"])->forward(ctx, x);
|
||||
h = std::dynamic_pointer_cast<Linear>(blocks["mlp.fc1"])->forward(ctx, h);
|
||||
h = ggml_gelu_erf(c, h);
|
||||
h = std::dynamic_pointer_cast<Linear>(blocks["mlp.fc2"])->forward(ctx, h);
|
||||
return ggml_add(c, x, Pid::apply_adaln(c, h, m[3], m[2]));
|
||||
}
|
||||
};
|
||||
|
||||
struct IrisModel : public GGMLBlock {
|
||||
IrisConfig config;
|
||||
|
||||
IrisModel(const IrisConfig& config)
|
||||
: config(config) {
|
||||
blocks["s_embedder"] = std::make_shared<Pid::PatchTokenEmbedder>(3 * config.patch_size * config.patch_size, config.hidden_size);
|
||||
blocks["t_embedder"] = std::make_shared<Pid::PixelDiTTimestepEmbedder>(config.hidden_size);
|
||||
blocks["y_embedder"] = std::make_shared<TextEmbedder>(config);
|
||||
blocks["modulation_cores.adaln_img"] = std::make_shared<Linear>(config.hidden_size, 6 * config.hidden_size, true);
|
||||
blocks["modulation_cores.adaln_txt"] = std::make_shared<Linear>(config.hidden_size, 6 * config.hidden_size, true);
|
||||
for (int64_t i = 0; i < config.depth; ++i) {
|
||||
blocks["blocks." + std::to_string(i)] = std::make_shared<TrunkBlock>(config, i < config.dual_depth);
|
||||
}
|
||||
blocks["pixel_embedder"] = std::make_shared<Pid::PixelTokenEmbedder>(3, config.pixel_dim);
|
||||
for (int64_t i = 0; i < config.pixel_depth; ++i) {
|
||||
blocks["pixel_blocks." + std::to_string(i)] = std::make_shared<PixelBlock>(config);
|
||||
}
|
||||
blocks["final_layer"] = std::make_shared<Pid::FinalLayer>(config.pixel_dim, 3);
|
||||
}
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& = {}, const std::string = "") override {
|
||||
params["y_pos_embedding"] = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, config.hidden_size, config.text_len, 1);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* image, ggml_tensor* t, ggml_tensor* text, ggml_tensor* mask, ggml_tensor* joint_pe, ggml_tensor* pixel_pe, ggml_tensor* pixel_pos) {
|
||||
auto c = ctx->ggml_ctx;
|
||||
int p = static_cast<int>(config.patch_size);
|
||||
auto x = DiT::patchify(c, image, p, p, true);
|
||||
x = std::dynamic_pointer_cast<Pid::PatchTokenEmbedder>(blocks["s_embedder"])->forward(ctx, x);
|
||||
auto te = std::dynamic_pointer_cast<Pid::PixelDiTTimestepEmbedder>(blocks["t_embedder"])->forward(ctx, t);
|
||||
te = ggml_reshape_3d(c, te, config.hidden_size, 1, image->ne[3]);
|
||||
auto cond = ggml_silu(c, te);
|
||||
auto im = std::dynamic_pointer_cast<Linear>(blocks["modulation_cores.adaln_img"])->forward(ctx, cond);
|
||||
auto tm = std::dynamic_pointer_cast<Linear>(blocks["modulation_cores.adaln_txt"])->forward(ctx, cond);
|
||||
auto y = std::dynamic_pointer_cast<TextEmbedder>(blocks["y_embedder"])->forward(ctx, text, mask);
|
||||
y = ggml_add(c, y, params["y_pos_embedding"]);
|
||||
for (int64_t i = 0; i < config.depth; ++i) {
|
||||
auto block = std::dynamic_pointer_cast<TrunkBlock>(blocks["blocks." + std::to_string(i)]);
|
||||
std::tie(x, y) = block->forward(ctx, x, y, im, tm, joint_pe);
|
||||
sd::ggml_graph_cut::mark_graph_cut(x, "iris.blocks." + std::to_string(i), "x");
|
||||
sd::ggml_graph_cut::mark_graph_cut(y, "iris.blocks." + std::to_string(i), "y");
|
||||
}
|
||||
cond = ggml_silu(c, ggml_add(c, x, te));
|
||||
x = std::dynamic_pointer_cast<Pid::PixelTokenEmbedder>(blocks["pixel_embedder"])->forward(ctx, image, p, pixel_pos);
|
||||
for (int64_t i = 0; i < config.pixel_depth; ++i) {
|
||||
x = std::dynamic_pointer_cast<PixelBlock>(blocks["pixel_blocks." + std::to_string(i)])->forward(ctx, x, cond, pixel_pe);
|
||||
sd::ggml_graph_cut::mark_graph_cut(x, "iris.pixel_blocks." + std::to_string(i), "x");
|
||||
}
|
||||
x = std::dynamic_pointer_cast<Pid::FinalLayer>(blocks["final_layer"])->forward(ctx, x);
|
||||
x = ggml_reshape_3d(c, x, 3 * p * p, cond->ne[1], image->ne[3]);
|
||||
return DiT::unpatchify(c, x, image->ne[1] / p, image->ne[0] / p, p, p, false);
|
||||
}
|
||||
};
|
||||
|
||||
inline std::vector<float> image_rope(int64_t height, int64_t width, int64_t dim) {
|
||||
std::vector<float> pe;
|
||||
pe.reserve(height * width * dim * 2);
|
||||
float step = 16.f / static_cast<float>(std::max<int64_t>(std::max(height, width) - 1, 1));
|
||||
for (int64_t y = 0; y < height; ++y) {
|
||||
for (int64_t x = 0; x < width; ++x) {
|
||||
for (int64_t i = 0; i < dim / 4; ++i) {
|
||||
float frequency = std::pow(10000.f, -4.f * static_cast<float>(i) / static_cast<float>(dim));
|
||||
for (int64_t pos : {x, y}) {
|
||||
float angle = static_cast<float>(pos) * step * frequency;
|
||||
pe.insert(pe.end(), {std::cos(angle), -std::sin(angle), std::sin(angle), std::cos(angle)});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return pe;
|
||||
}
|
||||
|
||||
struct IrisRunner : public DiffusionModelRunner {
|
||||
IrisConfig config;
|
||||
IrisModel model;
|
||||
std::vector<float> joint_pe_data;
|
||||
std::vector<float> pixel_pe_data;
|
||||
std::vector<float> pixel_pos_data;
|
||||
std::vector<float> text_mask_data;
|
||||
|
||||
IrisRunner(ggml_backend_t backend, const String2TensorStorage& weights, const std::string& prefix, std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
: DiffusionModelRunner(backend, prefix, weight_manager),
|
||||
config(IrisConfig::detect_from_weights(weights, prefix)),
|
||||
model(config) {
|
||||
model.init(params_ctx, weights, prefix);
|
||||
}
|
||||
|
||||
std::string get_desc() override { return "Iris-3B"; }
|
||||
|
||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors, const std::string& prefix) override {
|
||||
model.get_param_tensors(tensors, prefix);
|
||||
}
|
||||
|
||||
sd::Tensor<float> compute(int n_threads, const DiffusionParams& inputs) override {
|
||||
if (!inputs.x || !inputs.timesteps || !inputs.context || !inputs.y || inputs.y->empty()) {
|
||||
LOG_ERROR("Iris-3B requires text features and their padding mask");
|
||||
return {};
|
||||
}
|
||||
auto get_graph = [&]() {
|
||||
auto gf = new_graph_custom(196608);
|
||||
auto x = make_input(*inputs.x);
|
||||
auto t = make_input(*inputs.timesteps);
|
||||
auto text = make_input(*inputs.context);
|
||||
int h = static_cast<int>(x->ne[1]);
|
||||
int w = static_cast<int>(x->ne[0]);
|
||||
int nt = static_cast<int>(config.text_len);
|
||||
GGML_ASSERT(h % config.patch_size == 0 && w % config.patch_size == 0);
|
||||
GGML_ASSERT(text->ne[0] == config.text_dim * config.text_layers && text->ne[1] == nt);
|
||||
GGML_ASSERT(inputs.y->numel() == nt && x->ne[3] == 1);
|
||||
joint_pe_data = Pid::make_rope_1d(nt, static_cast<int>(config.head_dim), 10000.f);
|
||||
auto img = image_rope(h / config.patch_size, w / config.patch_size, config.head_dim);
|
||||
joint_pe_data.insert(joint_pe_data.end(), img.begin(), img.end());
|
||||
pixel_pe_data = image_rope(h / config.patch_size, w / config.patch_size, config.pixel_attn_dim / config.pixel_heads);
|
||||
pixel_pos_data = Pid::make_pixel_abs_pos(h, w, static_cast<int>(config.pixel_dim));
|
||||
text_mask_data.resize(nt * nt);
|
||||
for (int q = 0; q < nt; ++q) {
|
||||
for (int k = 0; k < nt; ++k) {
|
||||
text_mask_data[q * nt + k] = inputs.y->values()[k] != 0.f || q == k ? 0.f : -INFINITY;
|
||||
}
|
||||
}
|
||||
auto pe = ggml_new_tensor_4d(compute_ctx, GGML_TYPE_F32, 2, 2, config.head_dim / 2, nt + img.size() / (2 * config.head_dim));
|
||||
auto pp = ggml_new_tensor_4d(compute_ctx, GGML_TYPE_F32, 2, 2, config.pixel_attn_dim / config.pixel_heads / 2, (h / config.patch_size) * (w / config.patch_size));
|
||||
auto pos = ggml_new_tensor_3d(compute_ctx, GGML_TYPE_F32, config.pixel_dim, h * w, 1);
|
||||
auto mask = ggml_new_tensor_2d(compute_ctx, GGML_TYPE_F32, nt, nt);
|
||||
set_backend_tensor_data(pe, joint_pe_data.data());
|
||||
set_backend_tensor_data(pp, pixel_pe_data.data());
|
||||
set_backend_tensor_data(pos, pixel_pos_data.data());
|
||||
set_backend_tensor_data(mask, text_mask_data.data());
|
||||
auto ctx = get_context();
|
||||
auto out = model.forward(&ctx, x, t, text, mask, pe, pp, pos);
|
||||
ggml_build_forward_expand(gf, out);
|
||||
return gf;
|
||||
};
|
||||
return restore_trailing_singleton_dims(GGMLRunner::compute(get_graph, n_threads, false), inputs.x->dim());
|
||||
}
|
||||
};
|
||||
} // namespace Iris
|
||||
|
||||
#endif // __SD_MODEL_DIFFUSION_IRIS_HPP__
|
||||
@@ -548,21 +548,22 @@ namespace LTXV {
|
||||
return build_rope_matrix_from_frequencies(freqs, dim);
|
||||
}
|
||||
|
||||
__STATIC_INLINE__ ggml_tensor* apply_hidden_rope(ggml_context* ctx,
|
||||
__STATIC_INLINE__ ggml_tensor* apply_hidden_rope(GGMLRunnerContext* runner_ctx,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* pe,
|
||||
int64_t heads,
|
||||
int64_t dim_head,
|
||||
bool rope_interleaved) {
|
||||
auto ctx = runner_ctx->ggml_ctx;
|
||||
GGML_ASSERT(x->ne[0] == heads * dim_head);
|
||||
auto x4 = ggml_reshape_4d(ctx, x, dim_head, heads, x->ne[1], x->ne[2]);
|
||||
if (pe != nullptr && pe->ne[3] == x->ne[1] * heads) {
|
||||
auto x_flat = ggml_reshape_4d(ctx, x4, dim_head, 1, x->ne[1] * heads, x->ne[2]);
|
||||
auto out_flat = Rope::apply_rope(ctx, x_flat, pe, rope_interleaved);
|
||||
auto out_flat = Rope::apply_rope(runner_ctx, x_flat, pe, rope_interleaved);
|
||||
auto out4 = ggml_reshape_4d(ctx, out_flat, dim_head, heads, x->ne[1], x->ne[2]);
|
||||
return ggml_reshape_3d(ctx, out4, heads * dim_head, x->ne[1], x->ne[2]);
|
||||
}
|
||||
return Rope::apply_rope(ctx, x4, pe, rope_interleaved);
|
||||
return Rope::apply_rope(runner_ctx, x4, pe, rope_interleaved);
|
||||
}
|
||||
|
||||
struct TimestepEmbedder : public GGMLBlock {
|
||||
@@ -705,8 +706,8 @@ namespace LTXV {
|
||||
if (k_pe == nullptr) {
|
||||
k_pe = pe;
|
||||
}
|
||||
q = apply_hidden_rope(ctx->ggml_ctx, q, pe, heads, dim_head, rope_interleaved);
|
||||
k = apply_hidden_rope(ctx->ggml_ctx, k, k_pe, heads, dim_head, rope_interleaved);
|
||||
q = apply_hidden_rope(ctx, q, pe, heads, dim_head, rope_interleaved);
|
||||
k = apply_hidden_rope(ctx, k, k_pe, heads, dim_head, rope_interleaved);
|
||||
}
|
||||
|
||||
auto out = ggml_ext_attention_ext(ctx,
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
#ifndef __SD_MODEL_DIFFUSION_MING_IMAGE_HPP__
|
||||
#define __SD_MODEL_DIFFUSION_MING_IMAGE_HPP__
|
||||
|
||||
#include "z_image.hpp"
|
||||
|
||||
namespace MingImage {
|
||||
struct MingImageConfig : ZImage::ZImageConfig {
|
||||
bool split_qkv = true;
|
||||
|
||||
static MingImageConfig detect_from_weights(const String2TensorStorage& tensors, const std::string& prefix) {
|
||||
MingImageConfig config;
|
||||
static_cast<ZImage::ZImageConfig&>(config) = ZImage::ZImageConfig::detect_from_weights(tensors, prefix);
|
||||
config.split_qkv = tensors.count(prefix + ".layers.0.attention.qkv.weight") == 0;
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
class MingImageModel : public GGMLBlock {
|
||||
MingImageConfig config;
|
||||
|
||||
public:
|
||||
explicit MingImageModel(const MingImageConfig& config)
|
||||
: config(config) {
|
||||
blocks["x_embedder"] = std::make_shared<Linear>(config.patch_size * config.patch_size * config.in_channels, config.hidden_size);
|
||||
blocks["t_embedder"] = std::make_shared<TimestepEmbedder>(1024, 256, std::min<int64_t>(config.hidden_size, 256));
|
||||
blocks["cap_embedder.0"] = std::make_shared<RMSNorm>(config.cap_feat_dim, config.norm_eps);
|
||||
blocks["cap_embedder.1"] = std::make_shared<Linear>(config.cap_feat_dim, config.hidden_size);
|
||||
auto add_blocks = [&](const std::string& prefix, int64_t count, bool modulation) {
|
||||
for (int64_t i = 0; i < count; ++i) {
|
||||
blocks[prefix + std::to_string(i)] = std::make_shared<ZImage::JointTransformerBlock>(
|
||||
static_cast<int>(i), config.hidden_size, config.head_dim, config.num_heads,
|
||||
config.num_kv_heads, config.multiple_of, config.ffn_dim_multiplier,
|
||||
config.norm_eps, config.qk_norm, modulation, true, config.split_qkv, 1e-5f);
|
||||
}
|
||||
};
|
||||
add_blocks("noise_refiner.", config.num_refiner_layers, true);
|
||||
add_blocks("context_refiner.", config.num_refiner_layers, false);
|
||||
add_blocks("layers.", config.num_layers, true);
|
||||
blocks["final_layer"] = std::make_shared<ZImage::FinalLayer>(config.hidden_size, config.patch_size, config.out_channels);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* timestep, ggml_tensor* context, ggml_tensor* direct, ggml_tensor* pe) {
|
||||
auto gctx = ctx->ggml_ctx;
|
||||
const int64_t width = x->ne[0], height = x->ne[1];
|
||||
auto img = DiT::pad_and_patchify(ctx, x, config.patch_size, config.patch_size, false);
|
||||
img = std::dynamic_pointer_cast<Linear>(blocks["x_embedder"])->forward(ctx, img);
|
||||
auto txt = std::dynamic_pointer_cast<RMSNorm>(blocks["cap_embedder.0"])->forward(ctx, context);
|
||||
txt = std::dynamic_pointer_cast<Linear>(blocks["cap_embedder.1"])->forward(ctx, txt);
|
||||
txt = ggml_concat(gctx, txt, direct, 1);
|
||||
auto t = std::dynamic_pointer_cast<TimestepEmbedder>(blocks["t_embedder"])->forward(ctx, timestep);
|
||||
const int64_t n_txt = txt->ne[1], n_img = img->ne[1];
|
||||
auto txt_pe = ggml_ext_slice(gctx, pe, 3, 0, n_txt);
|
||||
auto img_pe = ggml_ext_slice(gctx, pe, 3, n_txt, n_txt + n_img);
|
||||
for (int64_t i = 0; i < config.num_refiner_layers; ++i) {
|
||||
txt = std::dynamic_pointer_cast<ZImage::JointTransformerBlock>(blocks["context_refiner." + std::to_string(i)])->forward(ctx, txt, txt_pe);
|
||||
sd::ggml_graph_cut::mark_graph_cut(txt, "ming_image.context_refiner." + std::to_string(i), "txt");
|
||||
}
|
||||
for (int64_t i = 0; i < config.num_refiner_layers; ++i) {
|
||||
img = std::dynamic_pointer_cast<ZImage::JointTransformerBlock>(blocks["noise_refiner." + std::to_string(i)])->forward(ctx, img, img_pe, nullptr, t);
|
||||
sd::ggml_graph_cut::mark_graph_cut(img, "ming_image.noise_refiner." + std::to_string(i), "img");
|
||||
}
|
||||
auto combined = ggml_concat(gctx, txt, img, 1);
|
||||
for (int64_t i = 0; i < config.num_layers; ++i) {
|
||||
combined = std::dynamic_pointer_cast<ZImage::JointTransformerBlock>(blocks["layers." + std::to_string(i)])->forward(ctx, combined, pe, nullptr, t);
|
||||
sd::ggml_graph_cut::mark_graph_cut(combined, "ming_image.layers." + std::to_string(i), "combined");
|
||||
}
|
||||
img = ggml_ext_slice(gctx, combined, 1, n_txt, n_txt + n_img);
|
||||
img = std::dynamic_pointer_cast<ZImage::FinalLayer>(blocks["final_layer"])->forward(ctx, img, t);
|
||||
img = DiT::unpatchify_and_crop(gctx, img, height, width, config.patch_size, config.patch_size, false);
|
||||
return ggml_scale(gctx, img, -1.f);
|
||||
}
|
||||
};
|
||||
|
||||
struct MingImageRunner : DiffusionModelRunner {
|
||||
MingImageConfig config;
|
||||
MingImageModel model;
|
||||
std::vector<float> pe_values;
|
||||
|
||||
MingImageRunner(ggml_backend_t backend, const String2TensorStorage& tensors, const std::string& prefix, std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
: DiffusionModelRunner(backend, prefix, weight_manager),
|
||||
config(MingImageConfig::detect_from_weights(tensors, prefix)),
|
||||
model(config) {
|
||||
model.init(params_ctx, tensors, prefix);
|
||||
}
|
||||
|
||||
std::string get_desc() override { return "ming_image"; }
|
||||
|
||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors, const std::string& prefix) override {
|
||||
model.get_param_tensors(tensors, prefix);
|
||||
}
|
||||
|
||||
sd::Tensor<float> compute(int n_threads, const DiffusionParams& inputs) override {
|
||||
const auto* extra = diffusion_extra_as<MingImageDiffusionExtra>(inputs);
|
||||
if (inputs.ref_latents != nullptr && !inputs.ref_latents->empty()) {
|
||||
LOG_ERROR("Ming-Image reference-image conditioning is not supported");
|
||||
return {};
|
||||
}
|
||||
if (inputs.context == nullptr || extra->direct_context == nullptr) {
|
||||
LOG_ERROR("Ming-Image requires both query and direct text conditions");
|
||||
return {};
|
||||
}
|
||||
auto graph = [&]() {
|
||||
auto gf = new_graph_custom(ZImage::Z_IMAGE_GRAPH_SIZE);
|
||||
auto x = make_input(*inputs.x);
|
||||
auto t = make_input(*inputs.timesteps);
|
||||
auto context = make_input(*inputs.context);
|
||||
auto direct = make_input(*extra->direct_context);
|
||||
GGML_ASSERT(x->ne[3] == 1);
|
||||
const int64_t n_txt = context->ne[1] + direct->ne[1];
|
||||
const int64_t n_img = ((x->ne[0] + config.patch_size - 1) / config.patch_size) *
|
||||
((x->ne[1] + config.patch_size - 1) / config.patch_size);
|
||||
auto padded = finish_rope_pe(Rope::gen_z_image_pe(
|
||||
static_cast<int>(x->ne[1]), static_cast<int>(x->ne[0]), config.patch_size, 1,
|
||||
static_cast<int>(n_txt), ZImage::SEQ_MULTI_OF, {}, Rope::RefIndexMode::FIXED,
|
||||
config.theta, config.axes_dim));
|
||||
// Zero-masked alignment tokens cannot affect valid queries. Omit them while
|
||||
// retaining the padded caption length used to position image tokens.
|
||||
const size_t stride = config.axes_dim_sum * 2;
|
||||
const int64_t padded_txt = n_txt + Rope::bound_mod(static_cast<int>(n_txt), ZImage::SEQ_MULTI_OF);
|
||||
pe_values.assign(padded.begin(), padded.begin() + n_txt * stride);
|
||||
pe_values.insert(pe_values.end(), padded.begin() + padded_txt * stride,
|
||||
padded.begin() + (padded_txt + n_img) * stride);
|
||||
auto pe = ggml_new_tensor_4d(compute_ctx, GGML_TYPE_F32, 2, 2, config.axes_dim_sum / 2, n_txt + n_img);
|
||||
set_backend_tensor_data(pe, pe_values.data());
|
||||
auto ctx = get_context();
|
||||
auto out = model.forward(&ctx, x, t, context, direct, pe);
|
||||
ggml_build_forward_expand(gf, out);
|
||||
return gf;
|
||||
};
|
||||
return restore_trailing_singleton_dims(GGMLRunner::compute(graph, n_threads, false), inputs.x->dim());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif // __SD_MODEL_DIFFUSION_MING_IMAGE_HPP__
|
||||
@@ -160,12 +160,13 @@ namespace MiniMaxH3 {
|
||||
return ggml_reshape_3d(ctx, x, x->ne[0], x->ne[1], x->ne[2] * x->ne[3]);
|
||||
}
|
||||
|
||||
static ggml_tensor* apply_partial_rope(ggml_context* ctx,
|
||||
static ggml_tensor* apply_partial_rope(GGMLRunnerContext* runner_ctx,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* pe) {
|
||||
auto ctx = runner_ctx->ggml_ctx;
|
||||
int64_t rot_dim = pe->ne[2] * 2;
|
||||
GGML_ASSERT(rot_dim <= x->ne[0]);
|
||||
auto rotated = Rope::apply_rope(ctx,
|
||||
auto rotated = Rope::apply_rope(runner_ctx,
|
||||
ggml_ext_slice(ctx, x, 0, 0, rot_dim),
|
||||
pe,
|
||||
false);
|
||||
@@ -209,8 +210,8 @@ namespace MiniMaxH3 {
|
||||
q = q_norm->forward(ctx, q);
|
||||
k = k_norm->forward(ctx, k);
|
||||
if (pe != nullptr) {
|
||||
q = apply_partial_rope(ctx->ggml_ctx, q, pe);
|
||||
k = apply_partial_rope(ctx->ggml_ctx, k, pe);
|
||||
q = apply_partial_rope(ctx, q, pe);
|
||||
k = apply_partial_rope(ctx, k, pe);
|
||||
} else {
|
||||
q = attention_layout(ctx->ggml_ctx, q);
|
||||
k = attention_layout(ctx->ggml_ctx, k);
|
||||
|
||||
@@ -141,6 +141,10 @@ struct LLaDAImageDiffusionExtra {
|
||||
const sd::Tensor<float>* semantic = nullptr;
|
||||
};
|
||||
|
||||
struct MingImageDiffusionExtra {
|
||||
const sd::Tensor<float>* direct_context = nullptr;
|
||||
};
|
||||
|
||||
using DiffusionExtraParams = std::variant<std::monostate,
|
||||
UNetDiffusionExtra,
|
||||
SkipLayerDiffusionExtra,
|
||||
@@ -154,7 +158,8 @@ using DiffusionExtraParams = std::variant<std::monostate,
|
||||
MiniT2IDiffusionExtra,
|
||||
SenseNovaU1DiffusionExtra,
|
||||
HunyuanVideoDiffusionExtra,
|
||||
LLaDAImageDiffusionExtra>;
|
||||
LLaDAImageDiffusionExtra,
|
||||
MingImageDiffusionExtra>;
|
||||
|
||||
struct DiffusionParams {
|
||||
const sd::Tensor<float>* x = nullptr;
|
||||
|
||||
@@ -41,28 +41,28 @@ namespace PixArt {
|
||||
auto it = weights.find(prefix + "." + suffix);
|
||||
return it == weights.end() ? nullptr : &it->second;
|
||||
};
|
||||
if (auto w = find("pos_embed.proj.weight")) {
|
||||
if (auto w = find("x_embedder.proj.weight")) {
|
||||
config.hidden_size = w->ne[3];
|
||||
config.in_channels = w->ne[2];
|
||||
config.patch_size = w->ne[0];
|
||||
}
|
||||
if (auto w = find("proj_out.weight")) {
|
||||
if (auto w = find("final_layer.linear.weight")) {
|
||||
config.out_channels = w->ne[1] / (config.patch_size * config.patch_size);
|
||||
}
|
||||
if (auto w = find("caption_projection.linear_1.weight")) {
|
||||
if (auto w = find("y_embedder.y_proj.fc1.weight")) {
|
||||
config.caption_channels = w->ne[0];
|
||||
}
|
||||
if (auto w = find("transformer_blocks.0.attn2.to_k.weight")) {
|
||||
if (auto w = find("blocks.0.cross_attn.kv_linear.weight")) {
|
||||
config.cross_attention_dim = w->ne[0];
|
||||
}
|
||||
if (auto w = find("transformer_blocks.0.ff.net.0.proj.weight")) {
|
||||
if (auto w = find("blocks.0.mlp.fc1.weight")) {
|
||||
config.ffn_dim = w->ne[1];
|
||||
}
|
||||
if (find("adaln_single.emb.resolution_embedder.linear_1.weight") != nullptr) {
|
||||
if (find("csize_embedder.mlp.0.weight") != nullptr) {
|
||||
LOG_WARN("pixart: resolution/aspect-ratio micro conditions are not supported; output may differ from the reference");
|
||||
}
|
||||
int layers = 0;
|
||||
const std::string block_prefix = prefix + ".transformer_blocks.";
|
||||
const std::string block_prefix = prefix + ".blocks.";
|
||||
for (const auto& [name, _] : weights) {
|
||||
if (starts_with(name, block_prefix)) {
|
||||
layers = std::max(layers, atoi(name.substr(block_prefix.size()).c_str()) + 1);
|
||||
@@ -108,37 +108,46 @@ namespace PixArt {
|
||||
class PixArtTimestepEmbedding : public GGMLBlock {
|
||||
public:
|
||||
PixArtTimestepEmbedding(int64_t in_channels, int64_t out_dim) {
|
||||
blocks["linear_1"] = std::make_shared<Linear>(in_channels, out_dim);
|
||||
blocks["linear_2"] = std::make_shared<Linear>(out_dim, out_dim);
|
||||
blocks["mlp.0"] = std::make_shared<Linear>(in_channels, out_dim);
|
||||
blocks["mlp.2"] = std::make_shared<Linear>(out_dim, out_dim);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
x = std::dynamic_pointer_cast<Linear>(blocks["linear_1"])->forward(ctx, x);
|
||||
x = std::dynamic_pointer_cast<Linear>(blocks["mlp.0"])->forward(ctx, x);
|
||||
x = ggml_silu(ctx->ggml_ctx, x);
|
||||
return std::dynamic_pointer_cast<Linear>(blocks["linear_2"])->forward(ctx, x);
|
||||
return std::dynamic_pointer_cast<Linear>(blocks["mlp.2"])->forward(ctx, x);
|
||||
}
|
||||
};
|
||||
|
||||
class PixArtAttention : public GGMLBlock {
|
||||
int64_t num_heads;
|
||||
bool self_attention;
|
||||
|
||||
public:
|
||||
PixArtAttention(int64_t dim, int64_t num_heads, int64_t context_dim)
|
||||
: num_heads(num_heads) {
|
||||
blocks["to_q"] = std::make_shared<Linear>(dim, dim);
|
||||
blocks["to_k"] = std::make_shared<Linear>(context_dim, dim);
|
||||
blocks["to_v"] = std::make_shared<Linear>(context_dim, dim);
|
||||
blocks["to_out.0"] = std::make_shared<Linear>(dim, dim);
|
||||
PixArtAttention(int64_t dim, int64_t num_heads, int64_t context_dim, bool self_attention)
|
||||
: num_heads(num_heads), self_attention(self_attention) {
|
||||
if (self_attention) {
|
||||
blocks["qkv"] = std::make_shared<Linear>(dim, 3 * dim);
|
||||
} else {
|
||||
blocks["q_linear"] = std::make_shared<Linear>(dim, dim);
|
||||
blocks["kv_linear"] = std::make_shared<Linear>(context_dim, 2 * dim);
|
||||
}
|
||||
blocks["proj"] = std::make_shared<Linear>(dim, dim);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* context, ggml_tensor* mask = nullptr) {
|
||||
// x: [N, n_token, dim], context: [N, n_context, context_dim]
|
||||
auto q = std::dynamic_pointer_cast<Linear>(blocks["to_q"])->forward(ctx, x);
|
||||
auto k = std::dynamic_pointer_cast<Linear>(blocks["to_k"])->forward(ctx, context);
|
||||
auto v = std::dynamic_pointer_cast<Linear>(blocks["to_v"])->forward(ctx, context);
|
||||
|
||||
auto out = ggml_ext_attention_ext(ctx, q, k, v, num_heads, mask, false, ctx->flash_attn_enabled);
|
||||
return std::dynamic_pointer_cast<Linear>(blocks["to_out.0"])->forward(ctx, out);
|
||||
std::vector<ggml_tensor*> qkv;
|
||||
if (self_attention) {
|
||||
auto projected = std::dynamic_pointer_cast<Linear>(blocks["qkv"])->forward(ctx, x);
|
||||
qkv = ggml_ext_chunk(ctx->ggml_ctx, projected, 3, 0);
|
||||
} else {
|
||||
auto q = std::dynamic_pointer_cast<Linear>(blocks["q_linear"])->forward(ctx, x);
|
||||
auto kv = std::dynamic_pointer_cast<Linear>(blocks["kv_linear"])->forward(ctx, context);
|
||||
auto parts = ggml_ext_chunk(ctx->ggml_ctx, kv, 2, 0);
|
||||
qkv = {q, parts[0], parts[1]};
|
||||
}
|
||||
auto out = ggml_ext_attention_ext(ctx, qkv[0], qkv[1], qkv[2], num_heads, mask, false, ctx->flash_attn_enabled);
|
||||
return std::dynamic_pointer_cast<Linear>(blocks["proj"])->forward(ctx, out);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -155,10 +164,10 @@ namespace PixArt {
|
||||
public:
|
||||
PixArtBlock(int64_t dim, int64_t num_heads, int64_t context_dim, int64_t ffn_dim)
|
||||
: dim(dim) {
|
||||
blocks["attn1"] = std::make_shared<PixArtAttention>(dim, num_heads, dim);
|
||||
blocks["attn2"] = std::make_shared<PixArtAttention>(dim, num_heads, context_dim);
|
||||
blocks["ff.net.0.proj"] = std::make_shared<Linear>(dim, ffn_dim);
|
||||
blocks["ff.net.2"] = std::make_shared<Linear>(ffn_dim, dim);
|
||||
blocks["attn"] = std::make_shared<PixArtAttention>(dim, num_heads, dim, true);
|
||||
blocks["cross_attn"] = std::make_shared<PixArtAttention>(dim, num_heads, context_dim, false);
|
||||
blocks["mlp.fc1"] = std::make_shared<Linear>(dim, ffn_dim);
|
||||
blocks["mlp.fc2"] = std::make_shared<Linear>(ffn_dim, dim);
|
||||
}
|
||||
|
||||
static ggml_tensor* norm(ggml_context* ctx, ggml_tensor* x) {
|
||||
@@ -174,14 +183,14 @@ namespace PixArt {
|
||||
if (table->type != GGML_TYPE_F32) {
|
||||
table = ggml_cast(ctx->ggml_ctx, table, GGML_TYPE_F32);
|
||||
}
|
||||
table = ggml_reshape_3d(ctx->ggml_ctx, table, dim, 6, 1);
|
||||
auto m = ggml_add(ctx->ggml_ctx, ggml_reshape_3d(ctx->ggml_ctx, mod, dim, 6, N), table);
|
||||
auto mv = ggml_ext_chunk(ctx->ggml_ctx, ggml_reshape_2d(ctx->ggml_ctx, ggml_ext_cont(ctx->ggml_ctx, m), dim * 6, N), 6, 0);
|
||||
table = ggml_reshape_3d(ctx->ggml_ctx, table, dim, 6, 1);
|
||||
auto m = ggml_add(ctx->ggml_ctx, ggml_reshape_3d(ctx->ggml_ctx, mod, dim, 6, N), table);
|
||||
auto mv = ggml_ext_chunk(ctx->ggml_ctx, ggml_reshape_2d(ctx->ggml_ctx, ggml_ext_cont(ctx->ggml_ctx, m), dim * 6, N), 6, 0);
|
||||
|
||||
auto attn1 = std::dynamic_pointer_cast<PixArtAttention>(blocks["attn1"]);
|
||||
auto attn2 = std::dynamic_pointer_cast<PixArtAttention>(blocks["attn2"]);
|
||||
auto proj = std::dynamic_pointer_cast<Linear>(blocks["ff.net.0.proj"]);
|
||||
auto fc2 = std::dynamic_pointer_cast<Linear>(blocks["ff.net.2"]);
|
||||
auto attn1 = std::dynamic_pointer_cast<PixArtAttention>(blocks["attn"]);
|
||||
auto attn2 = std::dynamic_pointer_cast<PixArtAttention>(blocks["cross_attn"]);
|
||||
auto proj = std::dynamic_pointer_cast<Linear>(blocks["mlp.fc1"]);
|
||||
auto fc2 = std::dynamic_pointer_cast<Linear>(blocks["mlp.fc2"]);
|
||||
|
||||
auto gate = [&](ggml_tensor* y, ggml_tensor* g) {
|
||||
g = ggml_reshape_3d(ctx->ggml_ctx, g, dim, 1, N);
|
||||
@@ -206,29 +215,29 @@ namespace PixArt {
|
||||
void init_params(ggml_context* ctx,
|
||||
const String2TensorStorage& tensor_storage_map = {},
|
||||
const std::string prefix = "") override {
|
||||
ggml_type wtype = get_type(prefix + "scale_shift_table", tensor_storage_map, GGML_TYPE_F32);
|
||||
params["scale_shift_table"] = ggml_new_tensor_2d(ctx, wtype, config.hidden_size, 2);
|
||||
ggml_type wtype = get_type(prefix + "final_layer.scale_shift_table", tensor_storage_map, GGML_TYPE_F32);
|
||||
params["final_layer.scale_shift_table"] = ggml_new_tensor_2d(ctx, wtype, config.hidden_size, 2);
|
||||
}
|
||||
|
||||
public:
|
||||
PixArtModel() = default;
|
||||
PixArtModel(const PixArtConfig& config)
|
||||
: config(config) {
|
||||
blocks["pos_embed.proj"] = std::make_shared<Conv2d>(config.in_channels,
|
||||
config.hidden_size,
|
||||
std::pair<int, int>{static_cast<int>(config.patch_size), static_cast<int>(config.patch_size)},
|
||||
std::pair<int, int>{static_cast<int>(config.patch_size), static_cast<int>(config.patch_size)});
|
||||
blocks["adaln_single.emb.timestep_embedder"] = std::make_shared<PixArtTimestepEmbedding>(ADALN_EMBED_DIM, config.hidden_size);
|
||||
blocks["adaln_single.linear"] = std::make_shared<Linear>(config.hidden_size, 6 * config.hidden_size);
|
||||
blocks["caption_projection.linear_1"] = std::make_shared<Linear>(config.caption_channels, config.hidden_size);
|
||||
blocks["caption_projection.linear_2"] = std::make_shared<Linear>(config.hidden_size, config.cross_attention_dim);
|
||||
blocks["x_embedder.proj"] = std::make_shared<Conv2d>(config.in_channels,
|
||||
config.hidden_size,
|
||||
std::pair<int, int>{static_cast<int>(config.patch_size), static_cast<int>(config.patch_size)},
|
||||
std::pair<int, int>{static_cast<int>(config.patch_size), static_cast<int>(config.patch_size)});
|
||||
blocks["t_embedder"] = std::make_shared<PixArtTimestepEmbedding>(ADALN_EMBED_DIM, config.hidden_size);
|
||||
blocks["t_block.1"] = std::make_shared<Linear>(config.hidden_size, 6 * config.hidden_size);
|
||||
blocks["y_embedder.y_proj.fc1"] = std::make_shared<Linear>(config.caption_channels, config.hidden_size);
|
||||
blocks["y_embedder.y_proj.fc2"] = std::make_shared<Linear>(config.hidden_size, config.cross_attention_dim);
|
||||
for (int i = 0; i < config.num_layers; ++i) {
|
||||
blocks["transformer_blocks." + std::to_string(i)] =
|
||||
blocks["blocks." + std::to_string(i)] =
|
||||
std::make_shared<PixArtBlock>(config.hidden_size, config.num_heads, config.cross_attention_dim, config.ffn_dim);
|
||||
}
|
||||
blocks["norm_out"] = std::make_shared<LayerNorm>(config.hidden_size, 1e-6f, false);
|
||||
blocks["proj_out"] = std::make_shared<Linear>(config.hidden_size,
|
||||
config.patch_size * config.patch_size * config.out_channels);
|
||||
blocks["final_layer.norm_final"] = std::make_shared<LayerNorm>(config.hidden_size, 1e-6f, false);
|
||||
blocks["final_layer.linear"] = std::make_shared<Linear>(config.hidden_size,
|
||||
config.patch_size * config.patch_size * config.out_channels);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
@@ -245,29 +254,29 @@ namespace PixArt {
|
||||
int64_t wp = W / p;
|
||||
int64_t hp = H / p;
|
||||
|
||||
auto h = std::dynamic_pointer_cast<Conv2d>(blocks["pos_embed.proj"])->forward(ctx, x); // [N, hidden, hp, wp]
|
||||
h = ggml_ext_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, h, 1, 2, 0, 3)); // [N, hp, wp, hidden] -> [N, hp*wp, hidden]
|
||||
h = ggml_reshape_3d(ctx->ggml_ctx, h, config.hidden_size, wp * hp, N); // [N, hp*wp, hidden]
|
||||
auto h = std::dynamic_pointer_cast<Conv2d>(blocks["x_embedder.proj"])->forward(ctx, x); // [N, hidden, hp, wp]
|
||||
h = ggml_ext_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, h, 1, 2, 0, 3)); // [N, hp, wp, hidden] -> [N, hp*wp, hidden]
|
||||
h = ggml_reshape_3d(ctx->ggml_ctx, h, config.hidden_size, wp * hp, N); // [N, hp*wp, hidden]
|
||||
h = ggml_add(ctx->ggml_ctx, h, pos_embed);
|
||||
|
||||
auto t = ggml_ext_timestep_embedding(ctx->ggml_ctx, timesteps, ADALN_EMBED_DIM, 10000);
|
||||
auto emb = std::dynamic_pointer_cast<PixArtTimestepEmbedding>(blocks["adaln_single.emb.timestep_embedder"])->forward(ctx, t);
|
||||
auto emb = std::dynamic_pointer_cast<PixArtTimestepEmbedding>(blocks["t_embedder"])->forward(ctx, t);
|
||||
|
||||
auto mod = std::dynamic_pointer_cast<Linear>(blocks["adaln_single.linear"])
|
||||
auto mod = std::dynamic_pointer_cast<Linear>(blocks["t_block.1"])
|
||||
->forward(ctx, ggml_silu(ctx->ggml_ctx, emb)); // [N, 6 * hidden]
|
||||
|
||||
auto ctx_emb = std::dynamic_pointer_cast<Linear>(blocks["caption_projection.linear_1"])->forward(ctx, context);
|
||||
auto ctx_emb = std::dynamic_pointer_cast<Linear>(blocks["y_embedder.y_proj.fc1"])->forward(ctx, context);
|
||||
ctx_emb = ggml_ext_gelu(ctx->ggml_ctx, ctx_emb, true);
|
||||
ctx_emb = std::dynamic_pointer_cast<Linear>(blocks["caption_projection.linear_2"])->forward(ctx, ctx_emb);
|
||||
ctx_emb = std::dynamic_pointer_cast<Linear>(blocks["y_embedder.y_proj.fc2"])->forward(ctx, ctx_emb);
|
||||
|
||||
for (int i = 0; i < config.num_layers; ++i) {
|
||||
auto block = std::dynamic_pointer_cast<PixArtBlock>(blocks["transformer_blocks." + std::to_string(i)]);
|
||||
auto block = std::dynamic_pointer_cast<PixArtBlock>(blocks["blocks." + std::to_string(i)]);
|
||||
h = block->forward(ctx, h, mod, ctx_emb, context_mask);
|
||||
sd::ggml_graph_cut::mark_graph_cut(h, "pixart.transformer_blocks." + std::to_string(i), "h");
|
||||
sd::ggml_graph_cut::mark_graph_cut(h, "pixart.blocks." + std::to_string(i), "h");
|
||||
}
|
||||
|
||||
// scale_shift_table + emb -> (shift, scale) for the affine-free final norm
|
||||
auto tail_table = params["scale_shift_table"];
|
||||
auto tail_table = params["final_layer.scale_shift_table"];
|
||||
if (tail_table->type != GGML_TYPE_F32) {
|
||||
tail_table = ggml_cast(ctx->ggml_ctx, tail_table, GGML_TYPE_F32);
|
||||
}
|
||||
@@ -277,9 +286,9 @@ namespace PixArt {
|
||||
auto parts = ggml_ext_chunk(ctx->ggml_ctx,
|
||||
ggml_reshape_2d(ctx->ggml_ctx, ggml_ext_cont(ctx->ggml_ctx, ss), config.hidden_size * 2, N),
|
||||
2, 0);
|
||||
h = std::dynamic_pointer_cast<LayerNorm>(blocks["norm_out"])->forward(ctx, h);
|
||||
h = std::dynamic_pointer_cast<LayerNorm>(blocks["final_layer.norm_final"])->forward(ctx, h);
|
||||
h = modulate(ctx->ggml_ctx, h, parts[0], parts[1]);
|
||||
h = std::dynamic_pointer_cast<Linear>(blocks["proj_out"])->forward(ctx, h); // [N, hp*wp, p*p*out_ch]
|
||||
h = std::dynamic_pointer_cast<Linear>(blocks["final_layer.linear"])->forward(ctx, h); // [N, hp*wp, p*p*out_ch]
|
||||
h = DiT::unpatchify(ctx->ggml_ctx, h, hp, wp, static_cast<int>(p), static_cast<int>(p), false);
|
||||
return h; // [N, out_channels, H, W]
|
||||
}
|
||||
|
||||
@@ -188,8 +188,8 @@ namespace Qwen {
|
||||
auto v = project("to_v");
|
||||
q = std::dynamic_pointer_cast<RMSNorm>(blocks["norm_q"])->forward(ctx, q);
|
||||
k = std::dynamic_pointer_cast<RMSNorm>(blocks["norm_k"])->forward(ctx, k);
|
||||
q = Rope::apply_rope(ctx->ggml_ctx, q, pe);
|
||||
k = Rope::apply_rope(ctx->ggml_ctx, k, pe);
|
||||
q = Rope::apply_rope(ctx, q, pe);
|
||||
k = Rope::apply_rope(ctx, k, pe);
|
||||
if (cache.mode == QwenImage21PrefixCache::Mode::STORE) {
|
||||
// Preserve query-first attention evaluation while writing each layer's
|
||||
// prefix before its full-sequence K/V can accumulate across layers.
|
||||
|
||||
@@ -587,7 +587,9 @@ public:
|
||||
label_emb = ggml_silu_inplace(ctx->ggml_ctx, label_emb);
|
||||
label_emb = label_embed_2->forward(ctx, label_emb); // [N, time_embed_dim]
|
||||
|
||||
emb = ggml_add(ctx->ggml_ctx, emb, label_emb); // [N, time_embed_dim]
|
||||
emb = label_emb->ne[1] > emb->ne[1]
|
||||
? ggml_add(ctx->ggml_ctx, label_emb, emb)
|
||||
: ggml_add(ctx->ggml_ctx, emb, label_emb); // [N, time_embed_dim]
|
||||
}
|
||||
// sd::ggml_graph_cut::mark_graph_cut(emb, "unet.prelude", "emb");
|
||||
|
||||
|
||||
@@ -140,7 +140,8 @@ namespace ZImage {
|
||||
int64_t num_kv_heads,
|
||||
bool qk_norm,
|
||||
bool norm_elementwise_affine = true,
|
||||
bool split_qkv = false)
|
||||
bool split_qkv = false,
|
||||
float qk_norm_eps = 1e-6f)
|
||||
: head_dim(head_dim), num_heads(num_heads), num_kv_heads(num_kv_heads), qk_norm(qk_norm), split_qkv(split_qkv) {
|
||||
float scale = 1.f;
|
||||
if (split_qkv) {
|
||||
@@ -153,8 +154,8 @@ namespace ZImage {
|
||||
blocks["out"] = std::make_shared<Linear>(num_heads * head_dim, hidden_size, false, false, false, scale);
|
||||
}
|
||||
if (qk_norm) {
|
||||
blocks["q_norm"] = std::make_shared<RMSNorm>(head_dim, 1e-06f, norm_elementwise_affine);
|
||||
blocks["k_norm"] = std::make_shared<RMSNorm>(head_dim, 1e-06f, norm_elementwise_affine);
|
||||
blocks["q_norm"] = std::make_shared<RMSNorm>(head_dim, qk_norm_eps, norm_elementwise_affine);
|
||||
blocks["k_norm"] = std::make_shared<RMSNorm>(head_dim, qk_norm_eps, norm_elementwise_affine);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,13 +168,16 @@ namespace ZImage {
|
||||
int64_t N = x->ne[2];
|
||||
auto out_proj = std::dynamic_pointer_cast<Linear>(blocks[split_qkv ? "to_out.0" : "out"]);
|
||||
|
||||
if (out_proj->has_quantized_weight()) {
|
||||
out_proj->set_scale(1.f / 16.f);
|
||||
}
|
||||
|
||||
if (split_qkv) {
|
||||
auto q_proj = std::dynamic_pointer_cast<Linear>(blocks["to_q"]);
|
||||
auto k_proj = std::dynamic_pointer_cast<Linear>(blocks["to_k"]);
|
||||
auto v_proj = std::dynamic_pointer_cast<Linear>(blocks["to_v"]);
|
||||
|
||||
if (sd_backend_is(ctx->backend, "ROCm")) {
|
||||
out_proj->set_scale(1.f / 16.f);
|
||||
out_proj->set_force_prec_f32(true);
|
||||
q_proj->set_force_prec_f32(true);
|
||||
k_proj->set_force_prec_f32(true);
|
||||
@@ -196,7 +200,6 @@ namespace ZImage {
|
||||
auto qkv_proj = std::dynamic_pointer_cast<Linear>(blocks["qkv"]);
|
||||
|
||||
if (sd_backend_is(ctx->backend, "ROCm")) {
|
||||
out_proj->set_scale(1.f / 16.f);
|
||||
out_proj->set_force_prec_f32(true);
|
||||
qkv_proj->set_force_prec_f32(true);
|
||||
}
|
||||
@@ -318,9 +321,10 @@ namespace ZImage {
|
||||
bool qk_norm,
|
||||
bool modulation = true,
|
||||
bool norm_elementwise_affine = true,
|
||||
bool split_qkv = false)
|
||||
bool split_qkv = false,
|
||||
float qk_norm_eps = 1e-6f)
|
||||
: modulation(modulation) {
|
||||
blocks["attention"] = std::make_shared<JointAttention>(hidden_size, head_dim, num_heads, num_kv_heads, qk_norm, norm_elementwise_affine, split_qkv);
|
||||
blocks["attention"] = std::make_shared<JointAttention>(hidden_size, head_dim, num_heads, num_kv_heads, qk_norm, norm_elementwise_affine, split_qkv, qk_norm_eps);
|
||||
blocks["feed_forward"] = std::make_shared<FeedForward>(hidden_size, hidden_size, multiple_of, ffn_dim_multiplier);
|
||||
blocks["attention_norm1"] = std::make_shared<RMSNorm>(hidden_size, norm_eps, norm_elementwise_affine);
|
||||
blocks["ffn_norm1"] = std::make_shared<RMSNorm>(hidden_size, norm_eps, norm_elementwise_affine);
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
#ifndef __SD_MODEL_DIFFUSION_Z_IMAGE_L2P_HPP__
|
||||
#define __SD_MODEL_DIFFUSION_Z_IMAGE_L2P_HPP__
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "z_image.hpp"
|
||||
|
||||
// Ref: https://github.com/TencentYoutuResearch/T2I-L2P/blob/main/diffsynth/models/z_image_dit_L2P.py
|
||||
|
||||
namespace ZImageL2P {
|
||||
struct ZImageL2PConfig : ZImage::ZImageConfig {
|
||||
static ZImageL2PConfig detect_from_weights(const String2TensorStorage& tensors, const std::string& prefix) {
|
||||
ZImageL2PConfig config;
|
||||
static_cast<ZImage::ZImageConfig&>(config) = ZImage::ZImageConfig::detect_from_weights(tensors, prefix);
|
||||
config.in_channels = 3;
|
||||
config.out_channels = 3;
|
||||
config.patch_size = 16;
|
||||
auto x_embedder = tensors.find(prefix + ".x_embedder.weight");
|
||||
if (x_embedder != tensors.end()) {
|
||||
config.patch_size = static_cast<int>(std::lround(std::sqrt(static_cast<double>(x_embedder->second.ne[0] / config.in_channels))));
|
||||
}
|
||||
// L2P ships split to_q/to_k/to_v, which name conversion maps to qkv.weight plus
|
||||
// .1/.2 parts; the base detection only sees the q part and undercounts kv heads.
|
||||
auto k_part = tensors.find(prefix + ".layers.0.attention.qkv.weight.1");
|
||||
if (k_part != tensors.end()) {
|
||||
config.num_kv_heads = k_part->second.ne[1] / config.head_dim;
|
||||
}
|
||||
LOG_VERBOSE("z_image_l2p: patch_size = %d, in_channels = %" PRId64 ", num_heads = %" PRId64 ", num_kv_heads = %" PRId64,
|
||||
config.patch_size,
|
||||
config.in_channels,
|
||||
config.num_heads,
|
||||
config.num_kv_heads);
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
// U-Net over the full-resolution noisy image. Its bottleneck sits at the DiT token
|
||||
// grid, so the number of pooling levels is tied to patch_size == 16.
|
||||
class LocalDecoder : public GGMLBlock {
|
||||
static constexpr int LEVELS = 4;
|
||||
static constexpr int64_t CHANNELS[LEVELS] = {64, 128, 256, 512};
|
||||
static constexpr int64_t BOTTLENECK_CHANNELS = 512;
|
||||
|
||||
public:
|
||||
LocalDecoder(int64_t in_channels, int64_t cond_channels) {
|
||||
int64_t prev = in_channels;
|
||||
for (int i = 0; i < LEVELS; ++i) {
|
||||
blocks["enc" + std::to_string(i + 1) + ".0"] = std::make_shared<Conv2d>(prev, CHANNELS[i], std::pair{3, 3}, std::pair{1, 1}, std::pair{1, 1});
|
||||
prev = CHANNELS[i];
|
||||
}
|
||||
blocks["bottleneck.0"] = std::make_shared<Conv2d>(prev + cond_channels, BOTTLENECK_CHANNELS, std::pair{1, 1});
|
||||
prev = BOTTLENECK_CHANNELS;
|
||||
for (int i = LEVELS - 1; i >= 0; --i) {
|
||||
const std::string level = std::to_string(i + 1);
|
||||
const int64_t out = i == 0 ? CHANNELS[0] : CHANNELS[i - 1];
|
||||
blocks["up" + level + ".1"] = std::make_shared<Conv2d>(prev, prev, std::pair{3, 3}, std::pair{1, 1}, std::pair{1, 1});
|
||||
blocks["dec" + level + ".0"] = std::make_shared<Conv2d>(prev + CHANNELS[i], out, std::pair{3, 3}, std::pair{1, 1}, std::pair{1, 1});
|
||||
prev = out;
|
||||
}
|
||||
blocks["out_conv"] = std::make_shared<Conv2d>(prev, in_channels, std::pair{1, 1});
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* cond) {
|
||||
// x: [N, C, H, W]
|
||||
// cond: [N, cond_channels, H / 16, W / 16]
|
||||
// return: [N, C, H, W]
|
||||
auto gctx = ctx->ggml_ctx;
|
||||
auto conv = [&](const std::string& name, ggml_tensor* h) {
|
||||
return std::dynamic_pointer_cast<Conv2d>(blocks[name])->forward(ctx, h);
|
||||
};
|
||||
|
||||
std::vector<ggml_tensor*> skips;
|
||||
auto h = x;
|
||||
for (int i = 0; i < LEVELS; ++i) {
|
||||
h = ggml_silu(gctx, conv("enc" + std::to_string(i + 1) + ".0", h));
|
||||
skips.push_back(h);
|
||||
h = ggml_pool_2d(gctx, h, GGML_OP_POOL_MAX, 2, 2, 2, 2, 0, 0);
|
||||
}
|
||||
|
||||
h = ggml_concat(gctx, h, cond, 2);
|
||||
h = ggml_silu(gctx, conv("bottleneck.0", h));
|
||||
|
||||
for (int i = LEVELS - 1; i >= 0; --i) {
|
||||
const std::string level = std::to_string(i + 1);
|
||||
h = ggml_upscale(gctx, h, 2, GGML_SCALE_MODE_NEAREST);
|
||||
h = conv("up" + level + ".1", h);
|
||||
h = ggml_concat(gctx, h, skips[i], 2);
|
||||
h = ggml_silu(gctx, conv("dec" + level + ".0", h));
|
||||
}
|
||||
return conv("out_conv", h);
|
||||
}
|
||||
};
|
||||
|
||||
class ZImageL2PModel : public GGMLBlock {
|
||||
ZImageL2PConfig config;
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map = {}, const std::string prefix = "") override {
|
||||
params["cap_pad_token"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, config.hidden_size);
|
||||
params["x_pad_token"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, config.hidden_size);
|
||||
}
|
||||
|
||||
public:
|
||||
explicit ZImageL2PModel(const ZImageL2PConfig& config)
|
||||
: config(config) {
|
||||
blocks["x_embedder"] = std::make_shared<Linear>(config.patch_size * config.patch_size * config.in_channels, config.hidden_size);
|
||||
blocks["t_embedder"] = std::make_shared<TimestepEmbedder>(MIN(config.hidden_size, 1024), 256, 256);
|
||||
blocks["cap_embedder.0"] = std::make_shared<RMSNorm>(config.cap_feat_dim, config.norm_eps);
|
||||
blocks["cap_embedder.1"] = std::make_shared<Linear>(config.cap_feat_dim, config.hidden_size);
|
||||
auto add_blocks = [&](const std::string& prefix, int64_t count, bool modulation) {
|
||||
for (int64_t i = 0; i < count; ++i) {
|
||||
blocks[prefix + std::to_string(i)] = std::make_shared<ZImage::JointTransformerBlock>(
|
||||
static_cast<int>(i), config.hidden_size, config.head_dim, config.num_heads,
|
||||
config.num_kv_heads, config.multiple_of, config.ffn_dim_multiplier,
|
||||
config.norm_eps, config.qk_norm, modulation, true, false, 1e-5f);
|
||||
}
|
||||
};
|
||||
add_blocks("noise_refiner.", config.num_refiner_layers, true);
|
||||
add_blocks("context_refiner.", config.num_refiner_layers, false);
|
||||
add_blocks("layers.", config.num_layers, true);
|
||||
blocks["local_decoder"] = std::make_shared<LocalDecoder>(config.in_channels, config.hidden_size);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* timestep, ggml_tensor* context, ggml_tensor* pe) {
|
||||
// x: [N, C, H, W], H and W are multiples of patch_size
|
||||
// context: [N, L, cap_feat_dim]
|
||||
// return: [N, C, H, W]
|
||||
auto gctx = ctx->ggml_ctx;
|
||||
const int64_t W = x->ne[0];
|
||||
const int64_t H = x->ne[1];
|
||||
const int64_t N = x->ne[3];
|
||||
const int64_t w = W / config.patch_size;
|
||||
const int64_t h = H / config.patch_size;
|
||||
const int64_t n_img = w * h;
|
||||
const int64_t n_txt = context->ne[1];
|
||||
|
||||
auto img = DiT::patchify(gctx, x, config.patch_size, config.patch_size, false);
|
||||
img = std::dynamic_pointer_cast<Linear>(blocks["x_embedder"])->forward(ctx, img);
|
||||
auto txt = std::dynamic_pointer_cast<RMSNorm>(blocks["cap_embedder.0"])->forward(ctx, context);
|
||||
txt = std::dynamic_pointer_cast<Linear>(blocks["cap_embedder.1"])->forward(ctx, txt);
|
||||
auto t = std::dynamic_pointer_cast<TimestepEmbedder>(blocks["t_embedder"])->forward(ctx, timestep);
|
||||
sd::ggml_graph_cut::mark_graph_cut(txt, "z_image_l2p.prelude", "txt");
|
||||
sd::ggml_graph_cut::mark_graph_cut(img, "z_image_l2p.prelude", "img");
|
||||
sd::ggml_graph_cut::mark_graph_cut(t, "z_image_l2p.prelude", "t_emb");
|
||||
|
||||
const int64_t n_txt_pad = Rope::bound_mod(static_cast<int>(n_txt), ZImage::SEQ_MULTI_OF);
|
||||
if (n_txt_pad > 0) {
|
||||
auto pad = params["cap_pad_token"];
|
||||
txt = ggml_concat(gctx, txt, ggml_repeat_4d(gctx, pad, pad->ne[0], n_txt_pad, N, 1), 1);
|
||||
}
|
||||
const int64_t n_img_pad = Rope::bound_mod(static_cast<int>(n_img), ZImage::SEQ_MULTI_OF);
|
||||
if (n_img_pad > 0) {
|
||||
auto pad = params["x_pad_token"];
|
||||
img = ggml_concat(gctx, img, ggml_repeat_4d(gctx, pad, pad->ne[0], n_img_pad, N, 1), 1);
|
||||
}
|
||||
GGML_ASSERT(txt->ne[1] + img->ne[1] == pe->ne[3]);
|
||||
|
||||
auto txt_pe = ggml_ext_slice(gctx, pe, 3, 0, txt->ne[1]);
|
||||
auto img_pe = ggml_ext_slice(gctx, pe, 3, txt->ne[1], pe->ne[3]);
|
||||
for (int64_t i = 0; i < config.num_refiner_layers; ++i) {
|
||||
txt = std::dynamic_pointer_cast<ZImage::JointTransformerBlock>(blocks["context_refiner." + std::to_string(i)])->forward(ctx, txt, txt_pe);
|
||||
sd::ggml_graph_cut::mark_graph_cut(txt, "z_image_l2p.context_refiner." + std::to_string(i), "txt");
|
||||
}
|
||||
for (int64_t i = 0; i < config.num_refiner_layers; ++i) {
|
||||
img = std::dynamic_pointer_cast<ZImage::JointTransformerBlock>(blocks["noise_refiner." + std::to_string(i)])->forward(ctx, img, img_pe, nullptr, t);
|
||||
sd::ggml_graph_cut::mark_graph_cut(img, "z_image_l2p.noise_refiner." + std::to_string(i), "img");
|
||||
}
|
||||
|
||||
auto txt_img = ggml_concat(gctx, txt, img, 1);
|
||||
for (int64_t i = 0; i < config.num_layers; ++i) {
|
||||
txt_img = std::dynamic_pointer_cast<ZImage::JointTransformerBlock>(blocks["layers." + std::to_string(i)])->forward(ctx, txt_img, pe, nullptr, t);
|
||||
sd::ggml_graph_cut::mark_graph_cut(txt_img, "z_image_l2p.layers." + std::to_string(i), "txt_img");
|
||||
}
|
||||
|
||||
// The local decoder consumes the raw last-layer hidden states: there is no final
|
||||
// norm or adaLN modulation in front of it, unlike Z-Image's final_layer.
|
||||
const int64_t img_start = n_txt + n_txt_pad;
|
||||
auto feat = ggml_ext_slice(gctx, txt_img, 1, img_start, img_start + n_img); // [N, h*w, hidden_size]
|
||||
feat = ggml_reshape_4d(gctx, feat, config.hidden_size, w, h, N); // [N, h, w, hidden_size]
|
||||
feat = ggml_cont(gctx, ggml_permute(gctx, feat, 2, 0, 1, 3)); // [N, hidden_size, h, w]
|
||||
auto out = std::dynamic_pointer_cast<LocalDecoder>(blocks["local_decoder"])->forward(ctx, x, feat); // [N, C, H, W]
|
||||
return ggml_ext_scale(gctx, out, -1.f);
|
||||
}
|
||||
};
|
||||
|
||||
struct ZImageL2PRunner : public DiffusionModelRunner {
|
||||
ZImageL2PConfig config;
|
||||
ZImageL2PModel model;
|
||||
std::vector<float> pe_vec;
|
||||
|
||||
ZImageL2PRunner(ggml_backend_t backend,
|
||||
const String2TensorStorage& tensors,
|
||||
const std::string& prefix,
|
||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
: DiffusionModelRunner(backend, prefix, weight_manager),
|
||||
config(ZImageL2PConfig::detect_from_weights(tensors, prefix)),
|
||||
model(config) {
|
||||
model.init(params_ctx, tensors, prefix);
|
||||
}
|
||||
|
||||
std::string get_desc() override { return "z_image_l2p"; }
|
||||
|
||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors, const std::string& prefix) override {
|
||||
model.get_param_tensors(tensors, prefix);
|
||||
}
|
||||
|
||||
sd::Tensor<float> compute(int n_threads, const DiffusionParams& inputs) override {
|
||||
GGML_ASSERT(inputs.x != nullptr);
|
||||
GGML_ASSERT(inputs.timesteps != nullptr);
|
||||
if (inputs.ref_latents != nullptr && !inputs.ref_latents->empty()) {
|
||||
LOG_ERROR("Z-Image L2P reference-image conditioning is not supported");
|
||||
return {};
|
||||
}
|
||||
if (inputs.context == nullptr || inputs.context->empty()) {
|
||||
LOG_ERROR("Z-Image L2P requires a text condition");
|
||||
return {};
|
||||
}
|
||||
auto graph = [&]() {
|
||||
auto gf = new_graph_custom(ZImage::Z_IMAGE_GRAPH_SIZE);
|
||||
auto x = make_input(*inputs.x);
|
||||
auto t = make_input(*inputs.timesteps);
|
||||
auto context = make_input(*inputs.context);
|
||||
GGML_ASSERT(x->ne[3] == 1);
|
||||
GGML_ASSERT(x->ne[0] % config.patch_size == 0 && x->ne[1] % config.patch_size == 0);
|
||||
pe_vec = finish_rope_pe(Rope::gen_z_image_pe(static_cast<int>(x->ne[1]),
|
||||
static_cast<int>(x->ne[0]),
|
||||
config.patch_size,
|
||||
static_cast<int>(x->ne[3]),
|
||||
static_cast<int>(context->ne[1]),
|
||||
ZImage::SEQ_MULTI_OF,
|
||||
{},
|
||||
Rope::RefIndexMode::FIXED,
|
||||
config.theta,
|
||||
config.axes_dim));
|
||||
int pos_len = static_cast<int>(pe_vec.size() / config.axes_dim_sum / 2);
|
||||
auto pe = ggml_new_tensor_4d(compute_ctx, GGML_TYPE_F32, 2, 2, config.axes_dim_sum / 2, pos_len);
|
||||
set_backend_tensor_data(pe, pe_vec.data());
|
||||
auto ctx = get_context();
|
||||
auto out = model.forward(&ctx, x, t, context, pe);
|
||||
ggml_build_forward_expand(gf, out);
|
||||
return gf;
|
||||
};
|
||||
return restore_trailing_singleton_dims(GGMLRunner::compute(graph, n_threads, false), inputs.x->dim());
|
||||
}
|
||||
};
|
||||
} // namespace ZImageL2P
|
||||
|
||||
#endif // __SD_MODEL_DIFFUSION_Z_IMAGE_L2P_HPP__
|
||||
+102
-33
@@ -50,6 +50,8 @@ namespace LLM {
|
||||
GEMMA4_12B,
|
||||
GPT_OSS_20B,
|
||||
LLADA2_MOE,
|
||||
BAILING_MOE,
|
||||
QWEN2,
|
||||
ARCH_COUNT,
|
||||
};
|
||||
|
||||
@@ -64,6 +66,8 @@ namespace LLM {
|
||||
"gemma4_12b",
|
||||
"gpt_oss_20b",
|
||||
"llada2_moe",
|
||||
"bailing_moe",
|
||||
"qwen2",
|
||||
};
|
||||
|
||||
enum class MLPActivation {
|
||||
@@ -225,7 +229,7 @@ namespace LLM {
|
||||
config.intermediate_size = 9216;
|
||||
config.num_layers = 26;
|
||||
config.vocab_size = 256000;
|
||||
} else if (arch == LLMArch::LLADA2_MOE) {
|
||||
} else if (arch == LLMArch::LLADA2_MOE || arch == LLMArch::BAILING_MOE) {
|
||||
config.head_dim = 128;
|
||||
config.num_heads = 16;
|
||||
config.num_kv_heads = 4;
|
||||
@@ -240,7 +244,7 @@ namespace LLM {
|
||||
config.max_position_embeddings = 16384;
|
||||
config.rope_thetas = {600000.f};
|
||||
config.qkv_fused = true;
|
||||
config.bidirectional = true;
|
||||
config.bidirectional = arch == LLMArch::LLADA2_MOE;
|
||||
config.partial_rotary = 0.5f;
|
||||
config.num_experts = 256;
|
||||
config.num_experts_per_tok = 8;
|
||||
@@ -250,6 +254,18 @@ namespace LLM {
|
||||
config.n_group = 8;
|
||||
config.topk_group = 4;
|
||||
config.routed_scaling_factor = 2.5f;
|
||||
if (arch == LLMArch::BAILING_MOE) {
|
||||
config.vocab_size = 157184;
|
||||
config.max_position_embeddings = 32768;
|
||||
}
|
||||
} else if (arch == LLMArch::QWEN2) {
|
||||
config.hidden_size = 1536;
|
||||
config.intermediate_size = 8960;
|
||||
config.num_heads = 12;
|
||||
config.num_kv_heads = 2;
|
||||
config.vocab_size = 151936;
|
||||
config.max_position_embeddings = 32768;
|
||||
config.bidirectional = true;
|
||||
} else if (arch == LLMArch::GPT_OSS_20B) {
|
||||
config.head_dim = 64;
|
||||
config.num_heads = 64;
|
||||
@@ -283,22 +299,22 @@ namespace LLM {
|
||||
if (contains(name, "attn.q_proj")) {
|
||||
config.llama_cpp_style = true;
|
||||
}
|
||||
if (contains(name, "visual.patch_embed.proj.1.weight")) {
|
||||
if (ends_with(name, "visual.patch_embed.proj.1.weight")) {
|
||||
config.vision.split_patch_embed = true;
|
||||
}
|
||||
if (contains(name, "visual.patch_embed.proj.0.weight")) {
|
||||
if (ends_with(name, "visual.patch_embed.proj.0.weight")) {
|
||||
config.vision.patch_size = static_cast<int>(tensor_storage.ne[0]);
|
||||
config.vision.in_channels = tensor_storage.ne[2];
|
||||
config.vision.hidden_size = tensor_storage.ne[3];
|
||||
}
|
||||
// HF-format checkpoints keep the patch embed unsplit under a single name.
|
||||
if (contains(name, "visual.patch_embed.proj.weight")) {
|
||||
if (ends_with(name, "visual.patch_embed.proj.weight")) {
|
||||
config.vision.patch_size = static_cast<int>(tensor_storage.ne[0]);
|
||||
}
|
||||
if (contains(name, "visual.patch_embed.bias") || contains(name, "visual.patch_embed.proj.bias")) {
|
||||
config.vision.hidden_size = tensor_storage.ne[0];
|
||||
}
|
||||
if (contains(name, "visual.pos_embed.weight")) {
|
||||
if (ends_with(name, "visual.pos_embed.weight") && tensor_storage.n_dims == 2) {
|
||||
config.vision.hidden_size = tensor_storage.ne[0];
|
||||
config.vision.num_position_embeddings = static_cast<int>(tensor_storage.ne[1]);
|
||||
}
|
||||
@@ -332,7 +348,7 @@ namespace LLM {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (contains(name, "embed_tokens.weight")) {
|
||||
if (ends_with(name, "embed_tokens.weight") && tensor_storage.n_dims == 2) {
|
||||
config.hidden_size = tensor_storage.ne[0];
|
||||
config.vocab_size = tensor_storage.ne[1];
|
||||
}
|
||||
@@ -471,6 +487,8 @@ namespace LLM {
|
||||
int64_t n_group;
|
||||
int64_t topk_group;
|
||||
float routed_scaling_factor;
|
||||
bool image_router;
|
||||
bool fused_experts = false;
|
||||
|
||||
void init_params(ggml_context* ctx,
|
||||
const String2TensorStorage& tensor_storage_map = {},
|
||||
@@ -488,6 +506,10 @@ namespace LLM {
|
||||
// scores and the group sums match.
|
||||
params["gate.weight"] = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, hidden_size, num_experts);
|
||||
params["gate.expert_bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, num_experts);
|
||||
if (image_router) {
|
||||
params["image_gate.weight"] = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, hidden_size, num_experts);
|
||||
params["image_gate.expert_bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, num_experts);
|
||||
}
|
||||
|
||||
ggml_type gate_type = supported_type(get_type(prefix + "experts.gate_proj.weight", tensor_storage_map, GGML_TYPE_F32), hidden_size);
|
||||
ggml_type up_type = supported_type(get_type(prefix + "experts.up_proj.weight", tensor_storage_map, GGML_TYPE_F32), hidden_size);
|
||||
@@ -506,8 +528,14 @@ namespace LLM {
|
||||
}
|
||||
};
|
||||
|
||||
declare_experts("experts.gate_proj.weight", gate_type, hidden_size, moe_intermediate_size);
|
||||
declare_experts("experts.up_proj.weight", up_type, hidden_size, moe_intermediate_size);
|
||||
fused_experts = tensor_storage_map.count(prefix + "experts.gate_up_proj.weight") != 0;
|
||||
if (fused_experts) {
|
||||
auto type = supported_type(get_type(prefix + "experts.gate_up_proj.weight", tensor_storage_map, GGML_TYPE_F32), hidden_size);
|
||||
declare_experts("experts.gate_up_proj.weight", type, hidden_size, 2 * moe_intermediate_size);
|
||||
} else {
|
||||
declare_experts("experts.gate_proj.weight", gate_type, hidden_size, moe_intermediate_size);
|
||||
declare_experts("experts.up_proj.weight", up_type, hidden_size, moe_intermediate_size);
|
||||
}
|
||||
declare_experts("experts.down_proj.weight", down_type, moe_intermediate_size, hidden_size);
|
||||
}
|
||||
|
||||
@@ -519,7 +547,8 @@ namespace LLM {
|
||||
num_experts_per_tok(config.num_experts_per_tok),
|
||||
n_group(config.n_group),
|
||||
topk_group(config.topk_group),
|
||||
routed_scaling_factor(config.routed_scaling_factor) {
|
||||
routed_scaling_factor(config.routed_scaling_factor),
|
||||
image_router(config.arch == LLMArch::BAILING_MOE) {
|
||||
if (config.num_shared_experts > 0) {
|
||||
blocks["shared_experts"] = std::make_shared<MLP>(config.hidden_size,
|
||||
config.moe_intermediate_size * config.num_shared_experts,
|
||||
@@ -582,7 +611,7 @@ namespace LLM {
|
||||
return ggml_mul_mat_id(ctx->ggml_ctx, w, x, selected_experts);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* image_mask = nullptr) {
|
||||
// x: [N, n_token, hidden_size]
|
||||
GGML_ASSERT(num_experts > 0 && num_experts_per_tok > 0);
|
||||
GGML_ASSERT(n_group > 0 && topk_group > 0 && num_experts % n_group == 0);
|
||||
@@ -596,10 +625,21 @@ namespace LLM {
|
||||
|
||||
auto logits = ggml_mul_mat(gctx, params["gate.weight"], x);
|
||||
logits = ggml_reshape_2d(gctx, logits, num_experts, n_token_total);
|
||||
auto scores = ggml_sigmoid(gctx, logits); // [num_experts, tokens]
|
||||
auto bias = params["gate.expert_bias"];
|
||||
if (image_router) {
|
||||
GGML_ASSERT(image_mask != nullptr);
|
||||
auto mask = ggml_reshape_2d(gctx, image_mask, 1, n_token_total);
|
||||
auto inverse_mask = ggml_scale_bias(gctx, mask, -1.f, 1.f);
|
||||
auto image_logits = ggml_reshape_2d(gctx, ggml_mul_mat(gctx, params["image_gate.weight"], x), num_experts, n_token_total);
|
||||
logits = ggml_add(gctx, ggml_mul(gctx, logits, inverse_mask), ggml_mul(gctx, image_logits, mask));
|
||||
bias = ggml_add(gctx,
|
||||
ggml_mul(gctx, ggml_repeat_4d(gctx, bias, num_experts, n_token_total, 1, 1), inverse_mask),
|
||||
ggml_mul(gctx, ggml_repeat_4d(gctx, params["image_gate.expert_bias"], num_experts, n_token_total, 1, 1), mask));
|
||||
}
|
||||
auto scores = ggml_sigmoid(gctx, logits);
|
||||
|
||||
// The bias steers selection only; the combine weights come from the unbiased scores.
|
||||
auto routing = ggml_add(gctx, scores, params["gate.expert_bias"]);
|
||||
auto routing = ggml_add(gctx, scores, bias);
|
||||
routing = ggml_add(gctx, routing, group_limited_mask(ctx, routing, n_token_total));
|
||||
|
||||
auto selected_experts = ggml_argsort_top_k(gctx, routing, (int)num_experts_per_tok); // [top_k, tokens]
|
||||
@@ -614,12 +654,17 @@ namespace LLM {
|
||||
weights = ggml_scale(gctx, weights, routed_scaling_factor);
|
||||
weights = ggml_reshape_3d(gctx, weights, 1, num_experts_per_tok, n_token_total);
|
||||
|
||||
auto xf = ggml_reshape_3d(gctx, x, hidden_size, 1, n_token_total);
|
||||
auto gate = expert_linear(ctx, "experts.gate_proj.weight", xf, selected_experts);
|
||||
auto up = expert_linear(ctx, "experts.up_proj.weight", xf, selected_experts);
|
||||
auto activated = ggml_swiglu_split(gctx, gate, up);
|
||||
auto experts = expert_linear(ctx, "experts.down_proj.weight", activated, selected_experts);
|
||||
experts = ggml_mul(gctx, experts, weights);
|
||||
auto xf = ggml_reshape_3d(gctx, x, hidden_size, 1, n_token_total);
|
||||
ggml_tensor* activated;
|
||||
if (fused_experts) {
|
||||
activated = ggml_swiglu(gctx, expert_linear(ctx, "experts.gate_up_proj.weight", xf, selected_experts));
|
||||
} else {
|
||||
auto gate = expert_linear(ctx, "experts.gate_proj.weight", xf, selected_experts);
|
||||
auto up = expert_linear(ctx, "experts.up_proj.weight", xf, selected_experts);
|
||||
activated = ggml_swiglu_split(gctx, gate, up);
|
||||
}
|
||||
auto experts = expert_linear(ctx, "experts.down_proj.weight", activated, selected_experts);
|
||||
experts = ggml_mul(gctx, experts, weights);
|
||||
|
||||
ggml_tensor* out = nullptr;
|
||||
for (int64_t i = 0; i < num_experts_per_tok; ++i) {
|
||||
@@ -1428,7 +1473,9 @@ namespace LLM {
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* input_pos,
|
||||
ggml_tensor* attention_mask = nullptr,
|
||||
int rope_index = 0) {
|
||||
int rope_index = 0,
|
||||
ggml_tensor* rope_cos = nullptr,
|
||||
ggml_tensor* rope_sin = nullptr) {
|
||||
// x: [N, n_token, hidden_size]
|
||||
int64_t n_token = x->ne[1];
|
||||
int64_t N = x->ne[2];
|
||||
@@ -1471,13 +1518,28 @@ namespace LLM {
|
||||
v = ggml_rms_norm(ctx->ggml_ctx, v, rms_norm_eps);
|
||||
}
|
||||
|
||||
if (arch == LLMArch::MISTRAL_SMALL_3_2) {
|
||||
if (rope_cos != nullptr) {
|
||||
GGML_ASSERT(rope_sin != nullptr);
|
||||
// Bailing video RoPE interleaves spatial frequencies within a partial NEOX head.
|
||||
auto rotate = [&](ggml_tensor* input) {
|
||||
auto gctx = ctx->ggml_ctx;
|
||||
int64_t half = rope_cos->ne[0];
|
||||
auto first = ggml_ext_slice(gctx, input, 0, 0, half);
|
||||
auto second = ggml_ext_slice(gctx, input, 0, half, 2 * half);
|
||||
auto left = ggml_sub(gctx, ggml_mul(gctx, first, rope_cos), ggml_mul(gctx, second, rope_sin));
|
||||
auto right = ggml_add(gctx, ggml_mul(gctx, second, rope_cos), ggml_mul(gctx, first, rope_sin));
|
||||
auto rotated = ggml_concat(gctx, left, right, 0);
|
||||
return ggml_concat(gctx, rotated, ggml_ext_slice(gctx, input, 0, 2 * half, input->ne[0]), 0);
|
||||
};
|
||||
q = rotate(q);
|
||||
k = rotate(k);
|
||||
} else if (arch == LLMArch::MISTRAL_SMALL_3_2) {
|
||||
q = ggml_rope_ext(ctx->ggml_ctx, q, input_pos, nullptr, 128, GGML_ROPE_TYPE_NORMAL, 8192, 1000000000.f, 1.f, 0.f, 1.f, 32.f, 1.f);
|
||||
k = ggml_rope_ext(ctx->ggml_ctx, k, input_pos, nullptr, 128, GGML_ROPE_TYPE_NORMAL, 8192, 1000000000.f, 1.f, 0.f, 1.f, 32.f, 1.f);
|
||||
} else if (arch == LLMArch::MINISTRAL_3_3B) {
|
||||
q = ggml_rope_ext(ctx->ggml_ctx, q, input_pos, nullptr, 128, GGML_ROPE_TYPE_NEOX, 262144, 1000000.f, 1.f, 0.f, 1.f, 32.f, 1.f);
|
||||
k = ggml_rope_ext(ctx->ggml_ctx, k, input_pos, nullptr, 128, GGML_ROPE_TYPE_NEOX, 262144, 1000000.f, 1.f, 0.f, 1.f, 32.f, 1.f);
|
||||
} else if (arch == LLMArch::QWEN3) {
|
||||
} else if (arch == LLMArch::QWEN3 || arch == LLMArch::QWEN2) {
|
||||
q = ggml_rope_ext(ctx->ggml_ctx, q, input_pos, nullptr, 128, GGML_ROPE_TYPE_NEOX, 40960, 1000000.f, 1.f, 0.f, 1.f, 32.f, 1.f);
|
||||
k = ggml_rope_ext(ctx->ggml_ctx, k, input_pos, nullptr, 128, GGML_ROPE_TYPE_NEOX, 40960, 1000000.f, 1.f, 0.f, 1.f, 32.f, 1.f);
|
||||
} else if (arch == LLMArch::GPT_OSS_20B) {
|
||||
@@ -1655,7 +1717,7 @@ namespace LLM {
|
||||
v_attn = ggml_reshape_3d(ctx->ggml_ctx, v_attn, n_token, head_dim, num_kv_heads * N);
|
||||
|
||||
auto kq = ggml_mul_mat(ctx->ggml_ctx, k, q);
|
||||
ggml_mul_mat_set_prec(kq, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(kq, GGML_PREC_F32);
|
||||
kq = ggml_scale_inplace(ctx->ggml_ctx, kq, 1.0f / std::sqrt(static_cast<float>(head_dim)));
|
||||
if (attention_mask != nullptr) {
|
||||
kq = ggml_add_inplace(ctx->ggml_ctx, kq, attention_mask);
|
||||
@@ -1724,7 +1786,7 @@ namespace LLM {
|
||||
blocks["self_attn"] = std::make_shared<Attention>(config, sliding_attention == 0);
|
||||
if (config.arch == LLMArch::GPT_OSS_20B) {
|
||||
blocks["mlp"] = std::make_shared<GPTOSSMLP>(config);
|
||||
} else if (config.arch == LLMArch::LLADA2_MOE && layer_index >= config.first_k_dense_replace) {
|
||||
} else if ((config.arch == LLMArch::LLADA2_MOE || config.arch == LLMArch::BAILING_MOE) && layer_index >= config.first_k_dense_replace) {
|
||||
blocks["mlp"] = std::make_shared<LLaDA2MoEMLP>(config);
|
||||
} else {
|
||||
blocks["mlp"] = std::make_shared<MLP>(config.hidden_size,
|
||||
@@ -1746,7 +1808,10 @@ namespace LLM {
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* input_pos,
|
||||
ggml_tensor* attention_mask = nullptr,
|
||||
ggml_tensor* sliding_attention_mask = nullptr) {
|
||||
ggml_tensor* sliding_attention_mask = nullptr,
|
||||
ggml_tensor* image_mask = nullptr,
|
||||
ggml_tensor* rope_cos = nullptr,
|
||||
ggml_tensor* rope_sin = nullptr) {
|
||||
// x: [N, n_token, hidden_size]
|
||||
auto self_attn = std::dynamic_pointer_cast<Attention>(blocks["self_attn"]);
|
||||
auto input_layernorm = std::dynamic_pointer_cast<LLMRMSNorm>(blocks["input_layernorm"]);
|
||||
@@ -1768,7 +1833,7 @@ namespace LLM {
|
||||
|
||||
auto residual = x;
|
||||
x = input_layernorm->forward(ctx, x);
|
||||
x = self_attn->forward(ctx, x, input_pos, block_attention_mask, rope_index);
|
||||
x = self_attn->forward(ctx, x, input_pos, block_attention_mask, rope_index, rope_cos, rope_sin);
|
||||
if (post_attention_norm != nullptr) {
|
||||
x = post_attention_norm->forward(ctx, x);
|
||||
}
|
||||
@@ -1782,7 +1847,7 @@ namespace LLM {
|
||||
} else if (auto moe_mlp = std::dynamic_pointer_cast<LLaDA2MoEMLP>(blocks["mlp"])) {
|
||||
// LLaDA2 is dense for the first first_k_dense_replace layers and MoE afterwards,
|
||||
// so the block type varies per layer rather than per arch.
|
||||
x = moe_mlp->forward(ctx, x);
|
||||
x = moe_mlp->forward(ctx, x, image_mask);
|
||||
} else {
|
||||
auto mlp = std::dynamic_pointer_cast<MLP>(blocks["mlp"]);
|
||||
x = mlp->forward(ctx, x);
|
||||
@@ -1804,10 +1869,11 @@ namespace LLM {
|
||||
protected:
|
||||
int64_t num_layers;
|
||||
LLMConfig config;
|
||||
std::string graph_cut_prefix;
|
||||
|
||||
public:
|
||||
TextModel(const LLMConfig& config)
|
||||
: num_layers(config.num_layers), config(config) {
|
||||
TextModel(const LLMConfig& config, const std::string& graph_cut_prefix = "llm.text")
|
||||
: num_layers(config.num_layers), config(config), graph_cut_prefix(graph_cut_prefix) {
|
||||
blocks["embed_tokens"] = std::shared_ptr<GGMLBlock>(new Embedding(config.vocab_size, config.hidden_size));
|
||||
for (int i = 0; i < num_layers; i++) {
|
||||
blocks["layers." + std::to_string(i)] = std::shared_ptr<GGMLBlock>(new TransformerBlock(config, i));
|
||||
@@ -1831,7 +1897,10 @@ namespace LLM {
|
||||
std::set<int> out_layers,
|
||||
const std::vector<std::vector<std::pair<int, ggml_tensor*>>>& deepstack_image_embeds = {},
|
||||
ggml_tensor* sliding_attention_mask = nullptr,
|
||||
bool return_all_hidden_states = false) {
|
||||
bool return_all_hidden_states = false,
|
||||
ggml_tensor* image_mask = nullptr,
|
||||
ggml_tensor* rope_cos = nullptr,
|
||||
ggml_tensor* rope_sin = nullptr) {
|
||||
auto norm = config.final_norm ? std::dynamic_pointer_cast<LLMRMSNorm>(blocks["norm"])
|
||||
: nullptr;
|
||||
std::vector<ggml_tensor*> intermediate_outputs;
|
||||
@@ -1843,18 +1912,18 @@ namespace LLM {
|
||||
intermediate_outputs.push_back(x);
|
||||
}
|
||||
|
||||
sd::ggml_graph_cut::mark_graph_cut(x, "llm.text.prelude", "x");
|
||||
sd::ggml_graph_cut::mark_graph_cut(x, graph_cut_prefix + ".prelude", "x");
|
||||
for (int i = 0; i < num_layers; i++) {
|
||||
auto block = std::dynamic_pointer_cast<TransformerBlock>(blocks["layers." + std::to_string(i)]);
|
||||
|
||||
x = block->forward(ctx, x, input_pos, attention_mask, sliding_attention_mask);
|
||||
x = block->forward(ctx, x, input_pos, attention_mask, sliding_attention_mask, image_mask, rope_cos, rope_sin);
|
||||
if (i < static_cast<int>(deepstack_image_embeds.size())) {
|
||||
x = add_deepstack_image_embeds(ctx, x, deepstack_image_embeds[static_cast<size_t>(i)]);
|
||||
}
|
||||
if (return_all_hidden_states || out_layers.size() > 1) {
|
||||
x = ggml_cont(ctx->ggml_ctx, x);
|
||||
}
|
||||
sd::ggml_graph_cut::mark_graph_cut(x, "llm.text.layers." + std::to_string(i), "x");
|
||||
sd::ggml_graph_cut::mark_graph_cut(x, graph_cut_prefix + ".layers." + std::to_string(i), "x");
|
||||
if (return_all_hidden_states) {
|
||||
if (i + 1 < num_layers) {
|
||||
intermediate_outputs.push_back(x);
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
#ifndef __SD_MODEL_TE_MING_IMAGE_TE_HPP__
|
||||
#define __SD_MODEL_TE_MING_IMAGE_TE_HPP__
|
||||
|
||||
#include "llm.hpp"
|
||||
|
||||
namespace MingImageTE {
|
||||
struct MingImageTEConfig {
|
||||
LLM::LLMConfig backbone;
|
||||
LLM::LLMConfig connector;
|
||||
int64_t num_queries = 256;
|
||||
int64_t caption_dim = 2560;
|
||||
int64_t diffusion_dim = 3840;
|
||||
|
||||
static MingImageTEConfig detect_from_weights(const String2TensorStorage& tensors, const std::string& prefix) {
|
||||
MingImageTEConfig config;
|
||||
for (const auto& entry : tensors) {
|
||||
if (starts_with(entry.first, prefix + ".backbone.") &&
|
||||
contains(entry.first, ".mlp.experts.") && entry.second.type == GGML_TYPE_I8) {
|
||||
throw std::runtime_error("Ming-Image INT8/W4A8 text encoder experts are not supported; use the BF16 text encoder");
|
||||
}
|
||||
}
|
||||
bool vision = false;
|
||||
config.backbone = LLM::LLMConfig::detect_from_weights(tensors, prefix + ".backbone.", LLM::LLMArch::BAILING_MOE, vision);
|
||||
config.connector = LLM::LLMConfig::detect_from_weights(tensors, prefix + ".connector.", LLM::LLMArch::QWEN2, vision);
|
||||
const auto query = tensors.find(prefix + ".query_tokens_dict.16x16");
|
||||
const auto projection = tensors.find(prefix + ".proj_out.weight");
|
||||
const auto direct = tensors.find(prefix + ".proj_directvlm.1.weight");
|
||||
if (query == tensors.end() || projection == tensors.end() || direct == tensors.end()) {
|
||||
throw std::runtime_error("Ming-Image requires the learned queries, connector and both condition projections");
|
||||
}
|
||||
config.num_queries = query->second.ne[1];
|
||||
config.caption_dim = projection->second.ne[1];
|
||||
config.diffusion_dim = direct->second.ne[1];
|
||||
if (config.num_queries != 256 || config.caption_dim != 2560 || config.diffusion_dim != 3840 ||
|
||||
config.backbone.num_layers != 20 || config.backbone.hidden_size != 2048 ||
|
||||
config.connector.num_layers != 28 || config.connector.hidden_size != 1536) {
|
||||
throw std::runtime_error("unsupported Ming-Image text encoder configuration");
|
||||
}
|
||||
LOG_VERBOSE("ming_image_te: queries = %" PRId64 ", caption_dim = %" PRId64 ", diffusion_dim = %" PRId64,
|
||||
config.num_queries, config.caption_dim, config.diffusion_dim);
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
struct ConnectorModel : LLM::TextModel {
|
||||
explicit ConnectorModel(const LLM::LLMConfig& config)
|
||||
: LLM::TextModel(config, "ming_image.connector") {
|
||||
blocks.erase("embed_tokens");
|
||||
}
|
||||
};
|
||||
|
||||
class MingImageTextModel : public GGMLBlock {
|
||||
MingImageTEConfig config;
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensors = {}, const std::string prefix = "") override {
|
||||
params["query_tokens_dict.16x16"] = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, config.backbone.hidden_size, config.num_queries);
|
||||
}
|
||||
|
||||
public:
|
||||
explicit MingImageTextModel(const MingImageTEConfig& config)
|
||||
: config(config) {
|
||||
blocks["backbone"] = std::make_shared<LLM::TextModel>(config.backbone, "ming_image.backbone");
|
||||
blocks["connector"] = std::make_shared<ConnectorModel>(config.connector);
|
||||
blocks["proj_in"] = std::make_shared<Linear>(config.backbone.hidden_size, config.connector.hidden_size);
|
||||
blocks["proj_out"] = std::make_shared<Linear>(config.connector.hidden_size, config.caption_dim);
|
||||
blocks["proj_directvlm.0"] = std::make_shared<RMSNorm>(config.backbone.hidden_size * 3, 1e-5f);
|
||||
blocks["proj_directvlm.1"] = std::make_shared<Linear>(config.backbone.hidden_size * 3, config.diffusion_dim);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* ids, int64_t prompt_length, ggml_tensor* mask, ggml_tensor* image_mask, ggml_tensor* cos, ggml_tensor* sin, ggml_tensor* connector_positions) {
|
||||
auto gctx = ctx->ggml_ctx;
|
||||
auto backbone = std::dynamic_pointer_cast<LLM::TextModel>(blocks["backbone"]);
|
||||
auto connector = std::dynamic_pointer_cast<ConnectorModel>(blocks["connector"]);
|
||||
auto x = backbone->embed(ctx, ids);
|
||||
const int64_t query_start = prompt_length + 1;
|
||||
auto before = ggml_ext_slice(gctx, x, 1, 0, query_start);
|
||||
auto after = ggml_ext_slice(gctx, x, 1, query_start + config.num_queries, x->ne[1]);
|
||||
x = ggml_concat(gctx, ggml_concat(gctx, before, params["query_tokens_dict.16x16"], 1), after, 1);
|
||||
// HF hidden_states[20] includes final RMSNorm; sd.cpp selects it as num_layers + 1.
|
||||
x = backbone->forward_embeds(ctx, x, nullptr, mask, {5, 12, 21}, {}, nullptr, false, image_mask, cos, sin);
|
||||
auto direct = ggml_ext_slice(gctx, x, 1, 0, prompt_length);
|
||||
direct = std::dynamic_pointer_cast<RMSNorm>(blocks["proj_directvlm.0"])->forward(ctx, direct);
|
||||
direct = std::dynamic_pointer_cast<Linear>(blocks["proj_directvlm.1"])->forward(ctx, direct);
|
||||
auto queries = ggml_ext_slice(gctx, x, 0, config.backbone.hidden_size * 2, config.backbone.hidden_size * 3);
|
||||
queries = ggml_ext_slice(gctx, queries, 1, query_start, query_start + config.num_queries);
|
||||
queries = std::dynamic_pointer_cast<Linear>(blocks["proj_in"])->forward(ctx, queries);
|
||||
queries = connector->forward_embeds(ctx, queries, connector_positions, nullptr, {});
|
||||
queries = std::dynamic_pointer_cast<Linear>(blocks["proj_out"])->forward(ctx, queries);
|
||||
queries = ggml_pad(gctx, queries, static_cast<int>(config.diffusion_dim - config.caption_dim), 0, 0, 0);
|
||||
return ggml_concat(gctx, queries, direct, 1);
|
||||
}
|
||||
};
|
||||
|
||||
struct MingImageTextRunner : GGMLRunner {
|
||||
MingImageTEConfig config;
|
||||
MingImageTextModel model;
|
||||
|
||||
MingImageTextRunner(ggml_backend_t backend, const String2TensorStorage& tensors, const std::string& prefix, std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
: GGMLRunner(backend, weight_manager), config(MingImageTEConfig::detect_from_weights(tensors, prefix)), model(config) {
|
||||
model.init(params_ctx, tensors, prefix);
|
||||
}
|
||||
|
||||
std::string get_desc() override { return "ming_image_text"; }
|
||||
|
||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors, const std::string& prefix) {
|
||||
model.get_param_tensors(tensors, prefix);
|
||||
}
|
||||
|
||||
void get_param_tensor_ops(std::map<ggml_tensor*, enum ggml_op>& ops) {
|
||||
model.get_param_tensor_ops(ops);
|
||||
}
|
||||
|
||||
sd::Tensor<float> compute(int n_threads, const std::vector<int>& tokens) {
|
||||
const int64_t prompt_length = tokens.size();
|
||||
const int64_t query_start = prompt_length + 1;
|
||||
const int64_t total = prompt_length + config.num_queries + 2;
|
||||
std::vector<int32_t> ids(tokens.begin(), tokens.end());
|
||||
ids.push_back(157158);
|
||||
ids.insert(ids.end(), config.num_queries, 157157);
|
||||
ids.push_back(157159);
|
||||
auto input = sd::Tensor<int32_t>({total}, std::move(ids));
|
||||
sd::Tensor<float> attention_mask({total, total});
|
||||
sd::Tensor<float> image_mask({1, total});
|
||||
sd::Tensor<float> cos({32, 1, total}), sin({32, 1, total});
|
||||
std::vector<int32_t> positions(config.num_queries);
|
||||
std::iota(positions.begin(), positions.end(), 0);
|
||||
auto connector_positions = sd::Tensor<int32_t>({config.num_queries}, std::move(positions));
|
||||
for (int64_t token = 0; token < total; ++token) {
|
||||
const bool query = token >= query_start && token < query_start + config.num_queries;
|
||||
image_mask[token] = query ? 1.f : 0.f;
|
||||
for (int64_t key = 0; key < total; ++key) {
|
||||
attention_mask[key + token * total] = key > token ? -INFINITY : 0.f;
|
||||
}
|
||||
// A 16x16 query bank is represented upstream as a [1, 2, 512] image grid,
|
||||
// then spatially merged and centered to [1, 1, 256].
|
||||
int64_t temporal = query ? query_start : (token == total - 1 ? query_start + 1 : token);
|
||||
int64_t width = query ? token - 127 : temporal;
|
||||
for (int j = 0; j < 32; ++j) {
|
||||
int64_t position = query && j < 24 && j % 2 ? width : temporal;
|
||||
float frequency = 1.f / std::pow(600000.f, static_cast<float>(2 * j) / 64.f);
|
||||
float angle = static_cast<float>(position) * frequency;
|
||||
cos[token * 32 + j] = std::cos(angle);
|
||||
sin[token * 32 + j] = std::sin(angle);
|
||||
}
|
||||
}
|
||||
auto graph = [&]() {
|
||||
auto gf = new_graph_custom(LLM::LLM_GRAPH_SIZE);
|
||||
auto ctx = get_context();
|
||||
auto out = model.forward(&ctx, make_input(input), prompt_length, make_input(attention_mask),
|
||||
make_input(image_mask), make_input(cos), make_input(sin), make_input(connector_positions));
|
||||
ggml_build_forward_expand(gf, out);
|
||||
return gf;
|
||||
};
|
||||
return take_or_empty(GGMLRunner::compute(graph, n_threads));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif // __SD_MODEL_TE_MING_IMAGE_TE_HPP__
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user