mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-10-18 05:50:10 +08:00
Compare commits
47
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
484baa41e5 | ||
|
|
2bb0389683 | ||
|
|
ccda89e09c | ||
|
|
f0271076ad | ||
|
|
3b6c9ca97c | ||
|
|
57e19fa954 | ||
|
|
61a637bfd5 | ||
|
|
3ec374aecc | ||
|
|
0484600284 | ||
|
|
c1790754d3 | ||
|
|
9f855c933b | ||
|
|
7b5f34d93e | ||
|
|
d77b8f5ee8 | ||
|
|
03e9a22f4d | ||
|
|
f54e45e81c | ||
|
|
9956436c92 | ||
|
|
ec4cb8104b | ||
|
|
3973015ed7 | ||
|
|
9ee77fc227 | ||
|
|
39f7962d69 | ||
|
|
8caa3f908a | ||
|
|
2938272d82 | ||
|
|
f440ad9c29 | ||
|
|
41f7acbfb0 | ||
|
|
b395a6972d | ||
|
|
854bebfe02 | ||
|
|
787d229d84 | ||
|
|
b12098f5d0 | ||
|
|
2bd249c971 | ||
|
|
e9e952462f | ||
|
|
e8e012eef2 | ||
|
|
7f0e728b7d | ||
|
|
92a3b73cdb | ||
|
|
710bc91c8f | ||
|
|
5a34bc7f6e | ||
|
|
146b6cc49e | ||
|
|
93527fda74 | ||
|
|
6e66a1a4a4 | ||
|
|
bb90bfa00f | ||
|
|
517abc777d | ||
|
|
6f00939f75 | ||
|
|
c2df4e1228 | ||
|
|
9838264c49 | ||
|
|
17d70b91e6 | ||
|
|
5db680c2c7 | ||
|
|
749186c0eb | ||
|
|
bdb431ad95 |
+31
-38
@@ -83,7 +83,7 @@ jobs:
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DGGML_AVX2=ON -DSD_BUILD_SHARED_LIBS=ON
|
||||
cmake .. -DSD_BUILD_SHARED_LIBS=ON -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_INSTALL_RPATH='$ORIGIN'
|
||||
cmake --build . --config Release
|
||||
|
||||
- name: Get commit hash
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DSD_BUILD_SHARED_LIBS=ON -DSD_VULKAN=ON
|
||||
cmake .. -DSD_BUILD_SHARED_LIBS=ON -DSD_VULKAN=ON -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_INSTALL_RPATH='$ORIGIN'
|
||||
cmake --build . --config Release
|
||||
|
||||
- name: Get commit hash
|
||||
@@ -207,6 +207,7 @@ jobs:
|
||||
UBUNTU_VERSION=24.04
|
||||
CUDA_ARCHITECTURES=121
|
||||
GGML_CUDA_FA_ALL_QUANTS=ON
|
||||
GGML_CUDA_ENABLE_DYNAMIC_CPU_BACKENDS=OFF
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
@@ -341,18 +342,12 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- build: "noavx"
|
||||
defines: "-DGGML_NATIVE=OFF -DGGML_AVX=OFF -DGGML_AVX2=OFF -DGGML_FMA=OFF -DSD_BUILD_SHARED_LIBS=ON"
|
||||
- build: "avx2"
|
||||
defines: "-DGGML_NATIVE=OFF -DGGML_AVX2=ON -DSD_BUILD_SHARED_LIBS=ON"
|
||||
- build: "avx"
|
||||
defines: "-DGGML_NATIVE=OFF -DGGML_AVX=ON -DGGML_AVX2=OFF -DSD_BUILD_SHARED_LIBS=ON"
|
||||
- build: "avx512"
|
||||
defines: "-DGGML_NATIVE=OFF -DGGML_AVX512=ON -DGGML_AVX=ON -DGGML_AVX2=ON -DSD_BUILD_SHARED_LIBS=ON"
|
||||
- build: "cpu"
|
||||
defines: "-DGGML_NATIVE=OFF -DSD_BUILD_SHARED_LIBS=ON -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON"
|
||||
- build: "cuda12"
|
||||
defines: "-DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_CUDA_ARCHITECTURES='61;70;75;80;86;89;90;100;120' -DCMAKE_CUDA_FLAGS='-Xcudafe \"--diag_suppress=177\" -Xcudafe \"--diag_suppress=550\"'"
|
||||
defines: "-DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_CUDA_ARCHITECTURES='61;70;75;80;86;89;90;100;120' -DCMAKE_CUDA_FLAGS='-Xcudafe \"--diag_suppress=177\" -Xcudafe \"--diag_suppress=550\"' -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON"
|
||||
- build: "vulkan"
|
||||
defines: "-DSD_VULKAN=ON -DSD_BUILD_SHARED_LIBS=ON"
|
||||
defines: "-DSD_VULKAN=ON -DSD_BUILD_SHARED_LIBS=ON -DGGML_NATIVE=OFF -DSD_BUILD_SHARED_GGML_LIB=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON"
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
@@ -400,19 +395,6 @@ jobs:
|
||||
cmake .. -DCMAKE_CXX_FLAGS='/bigobj' -G Ninja -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe -DCMAKE_BUILD_TYPE=Release ${{ matrix.defines }}
|
||||
cmake --build .
|
||||
|
||||
- name: Check AVX512F support
|
||||
id: check_avx512f
|
||||
if: ${{ matrix.build == 'avx512' }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cd build
|
||||
$vcdir = $(vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath)
|
||||
$msvc = $(join-path $vcdir $('VC\Tools\MSVC\'+$(gc -raw $(join-path $vcdir 'VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt')).Trim()))
|
||||
$cl = $(join-path $msvc 'bin\Hostx64\x64\cl.exe')
|
||||
echo 'int main(void){unsigned int a[4];__cpuid(a,7);return !(a[1]&65536);}' >> avx512f.c
|
||||
& $cl /O2 /GS- /kernel avx512f.c /link /nodefaultlib /entry:main
|
||||
.\avx512f.exe && echo "AVX512F: YES" && ( echo HAS_AVX512F=1 >> $env:GITHUB_ENV ) || echo "AVX512F: NO"
|
||||
|
||||
- name: Get commit hash
|
||||
id: commit
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
@@ -722,6 +704,25 @@ jobs:
|
||||
run: |
|
||||
sudo apt install -y build-essential cmake wget zip ninja-build
|
||||
|
||||
- name: Free disk space
|
||||
run: |
|
||||
df -h
|
||||
|
||||
# Remove preinstalled SDKs and caches not needed for this job before
|
||||
# installing ROCm. The legacy ROCm apt packages are large enough to
|
||||
# exhaust ubuntu-latest if cleanup runs after installation.
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /usr/local/lib/android || true
|
||||
sudo rm -rf /opt/ghc || true
|
||||
sudo rm -rf /usr/local/.ghcup || true
|
||||
sudo rm -rf /opt/hostedtoolcache || true
|
||||
sudo rm -rf /usr/share/swift || true
|
||||
sudo rm -rf /usr/local/share/boost || true
|
||||
docker system prune -af || true
|
||||
|
||||
sudo apt clean
|
||||
df -h
|
||||
|
||||
- name: Setup Legacy ROCm
|
||||
if: matrix.ROCM_VERSION == '7.2.1'
|
||||
id: legacy_env
|
||||
@@ -743,19 +744,6 @@ jobs:
|
||||
sudo apt update
|
||||
sudo apt-get install -y libssl-dev rocm-hip-sdk
|
||||
|
||||
- name: Free disk space
|
||||
run: |
|
||||
# Remove preinstalled SDKs and caches not needed for this job
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /usr/local/lib/android || true
|
||||
sudo rm -rf /opt/ghc || true
|
||||
sudo rm -rf /usr/local/.ghcup || true
|
||||
sudo rm -rf /opt/hostedtoolcache || true
|
||||
|
||||
# Remove old package lists and caches
|
||||
sudo rm -rf /var/lib/apt/lists/* || true
|
||||
sudo apt clean
|
||||
|
||||
- name: Setup TheRock
|
||||
if: matrix.ROCM_VERSION != '7.2.1'
|
||||
id: therock_env
|
||||
@@ -794,6 +782,11 @@ jobs:
|
||||
-DGPU_TARGETS="${{ matrix.gpu_targets }}" \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
|
||||
-DCMAKE_INSTALL_RPATH='$ORIGIN' \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DSD_BUILD_SHARED_GGML_LIB=ON \
|
||||
-DGGML_BACKEND_DL=ON \
|
||||
-DGGML_CPU_ALL_VARIANTS=ON \
|
||||
-DSD_BUILD_SHARED_LIBS=ON
|
||||
cmake --build . --config Release
|
||||
|
||||
|
||||
+62
-4
@@ -204,6 +204,12 @@ if(SD_WEBM)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (SD_RPC)
|
||||
message("-- Use RPC as backend stable-diffusion")
|
||||
set(GGML_RPC ON)
|
||||
add_definitions(-DSD_USE_RPC)
|
||||
endif ()
|
||||
|
||||
set(SD_LIB stable-diffusion)
|
||||
|
||||
file(GLOB SD_LIB_SOURCES CONFIGURE_DEPENDS
|
||||
@@ -325,7 +331,8 @@ endif()
|
||||
|
||||
add_subdirectory(thirdparty)
|
||||
|
||||
target_link_libraries(${SD_LIB} PUBLIC ggml zip)
|
||||
target_sources(${SD_LIB} PRIVATE $<TARGET_OBJECTS:zip>)
|
||||
target_link_libraries(${SD_LIB} PUBLIC ggml)
|
||||
target_include_directories(${SD_LIB} PUBLIC . src include)
|
||||
target_include_directories(${SD_LIB} PRIVATE src/core)
|
||||
target_include_directories(${SD_LIB} PUBLIC . thirdparty)
|
||||
@@ -336,7 +343,58 @@ if (SD_BUILD_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
|
||||
set(SD_PUBLIC_HEADERS include/stable-diffusion.h)
|
||||
set_target_properties(${SD_LIB} PROPERTIES PUBLIC_HEADER "${SD_PUBLIC_HEADERS}")
|
||||
|
||||
install(TARGETS ${SD_LIB} LIBRARY PUBLIC_HEADER)
|
||||
|
||||
#
|
||||
# install
|
||||
#
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(SD_INSTALL_VERSION "${SDCPP_BUILD_VERSION}")
|
||||
set(SD_INSTALL_COMMIT "${SDCPP_BUILD_COMMIT}")
|
||||
set(SD_SHARED_LIB ${SD_BUILD_SHARED_LIBS})
|
||||
|
||||
set(SD_INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE PATH "Location of header files")
|
||||
set(SD_LIB_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR} CACHE PATH "Location of library files")
|
||||
set(SD_BIN_INSTALL_DIR ${CMAKE_INSTALL_BINDIR} CACHE PATH "Location of binary files")
|
||||
|
||||
set(SD_PUBLIC_HEADERS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/stable-diffusion.h)
|
||||
|
||||
set_target_properties(${SD_LIB}
|
||||
PROPERTIES
|
||||
PUBLIC_HEADER "${SD_PUBLIC_HEADERS}")
|
||||
|
||||
|
||||
install(TARGETS ${SD_LIB}
|
||||
ARCHIVE
|
||||
LIBRARY
|
||||
RUNTIME
|
||||
PUBLIC_HEADER)
|
||||
|
||||
|
||||
configure_package_config_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cmake/stable-diffusion-config.cmake.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/stable-diffusion-config.cmake
|
||||
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/stable-diffusion
|
||||
PATH_VARS SD_INCLUDE_INSTALL_DIR
|
||||
SD_LIB_INSTALL_DIR
|
||||
SD_BIN_INSTALL_DIR )
|
||||
|
||||
write_basic_package_version_file(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/stable-diffusion-version.cmake
|
||||
VERSION ${SD_INSTALL_VERSION}
|
||||
COMPATIBILITY SameMajorVersion)
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/stable-diffusion-config.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/stable-diffusion-version.cmake
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/stable-diffusion)
|
||||
|
||||
configure_file(cmake/stable-diffusion.pc.in
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/stable-diffusion.pc"
|
||||
@ONLY)
|
||||
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/stable-diffusion.pc"
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
|
||||
+12
-3
@@ -19,7 +19,14 @@ WORKDIR /sd.cpp
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN cmake . -B ./build
|
||||
RUN cmake . -B ./build \
|
||||
-DSD_BUILD_SHARED_LIBS=ON \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DSD_BUILD_SHARED_GGML_LIB=ON \
|
||||
-DGGML_BACKEND_DL=ON \
|
||||
-DGGML_CPU_ALL_VARIANTS=ON \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DCMAKE_INSTALL_RPATH='$ORIGIN'
|
||||
RUN cmake --build ./build --config Release --parallel
|
||||
|
||||
FROM ubuntu:$UBUNTU_VERSION AS runtime
|
||||
@@ -28,7 +35,9 @@ RUN apt-get update && \
|
||||
apt-get install --yes --no-install-recommends libgomp1 && \
|
||||
apt-get clean
|
||||
|
||||
COPY --from=build /sd.cpp/build/bin/sd-cli /sd-cli
|
||||
COPY --from=build /sd.cpp/build/bin/sd-server /sd-server
|
||||
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" ]
|
||||
|
||||
+24
-6
@@ -23,11 +23,27 @@ COPY . .
|
||||
ARG CUDACXX=/usr/local/cuda/bin/nvcc
|
||||
ARG CUDA_ARCHITECTURES=""
|
||||
ARG GGML_CUDA_FA_ALL_QUANTS=""
|
||||
ARG GGML_CUDA_ENABLE_DYNAMIC_CPU_BACKENDS=ON
|
||||
|
||||
RUN cmake . -B ./build \
|
||||
-DSD_CUDA=ON \
|
||||
${CUDA_ARCHITECTURES:+-DCMAKE_CUDA_ARCHITECTURES="${CUDA_ARCHITECTURES}"} \
|
||||
${GGML_CUDA_FA_ALL_QUANTS:+-DGGML_CUDA_FA_ALL_QUANTS=${GGML_CUDA_FA_ALL_QUANTS}}
|
||||
RUN set -- \
|
||||
-DSD_CUDA=ON; \
|
||||
if [ "${GGML_CUDA_ENABLE_DYNAMIC_CPU_BACKENDS}" = "ON" ]; then \
|
||||
set -- "$@" \
|
||||
-DSD_BUILD_SHARED_LIBS=ON \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DSD_BUILD_SHARED_GGML_LIB=ON \
|
||||
-DGGML_BACKEND_DL=ON \
|
||||
-DGGML_CPU_ALL_VARIANTS=ON \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
"-DCMAKE_INSTALL_RPATH=\$ORIGIN"; \
|
||||
fi; \
|
||||
if [ -n "${CUDA_ARCHITECTURES}" ]; then \
|
||||
set -- "$@" "-DCMAKE_CUDA_ARCHITECTURES=${CUDA_ARCHITECTURES}"; \
|
||||
fi; \
|
||||
if [ -n "${GGML_CUDA_FA_ALL_QUANTS}" ]; then \
|
||||
set -- "$@" "-DGGML_CUDA_FA_ALL_QUANTS=${GGML_CUDA_FA_ALL_QUANTS}"; \
|
||||
fi; \
|
||||
cmake . -B ./build "$@"
|
||||
RUN cmake --build ./build --config Release -j$(nproc)
|
||||
|
||||
FROM nvidia/cuda:${CUDA_VERSION}-cudnn-runtime-ubuntu${UBUNTU_VERSION} AS runtime
|
||||
@@ -36,7 +52,9 @@ RUN apt-get update && \
|
||||
apt-get install --yes --no-install-recommends libgomp1 && \
|
||||
apt-get clean
|
||||
|
||||
COPY --from=build /sd.cpp/build/bin/sd-cli /sd-cli
|
||||
COPY --from=build /sd.cpp/build/bin/sd-server /sd-server
|
||||
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" ]
|
||||
|
||||
+13
-3
@@ -24,12 +24,22 @@ RUN mkdir build && cd build && \
|
||||
cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_C_FLAGS="${CMAKE_C_FLAGS} -fopenmp -I/usr/lib/llvm-14/lib/clang/14.0.0/include -L/usr/lib/llvm-14/lib" \
|
||||
-DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -fopenmp -I/usr/lib/llvm-14/lib/clang/14.0.0/include -L/usr/lib/llvm-14/lib" \
|
||||
-DSD_MUSA=ON -DCMAKE_BUILD_TYPE=Release && \
|
||||
-DSD_MUSA=ON \
|
||||
-DSD_BUILD_SHARED_LIBS=ON \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DSD_BUILD_SHARED_GGML_LIB=ON \
|
||||
-DGGML_BACKEND_DL=ON \
|
||||
-DGGML_CPU_ALL_VARIANTS=ON \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DCMAKE_INSTALL_RPATH='$ORIGIN' \
|
||||
-DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build . --config Release
|
||||
|
||||
FROM mthreads/musa:${MUSA_VERSION}-runtime-ubuntu${UBUNTU_VERSION}-amd64 as runtime
|
||||
|
||||
COPY --from=build /sd.cpp/build/bin/sd-cli /sd-cli
|
||||
COPY --from=build /sd.cpp/build/bin/sd-server /sd-server
|
||||
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" ]
|
||||
|
||||
+1
-1
@@ -29,4 +29,4 @@ FROM intel/oneapi-basekit:${SYCL_VERSION}-devel-ubuntu24.04 AS runtime
|
||||
COPY --from=build /sd.cpp/build/bin/sd-cli /sd-cli
|
||||
COPY --from=build /sd.cpp/build/bin/sd-server /sd-server
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
+14
-4
@@ -19,8 +19,16 @@ WORKDIR /sd.cpp
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN cmake . -B ./build -DSD_VULKAN=ON
|
||||
RUN cmake --build ./build --config Release --parallel
|
||||
RUN cmake . -B ./build \
|
||||
-DSD_VULKAN=ON \
|
||||
-DSD_BUILD_SHARED_LIBS=ON \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DSD_BUILD_SHARED_GGML_LIB=ON \
|
||||
-DGGML_BACKEND_DL=ON \
|
||||
-DGGML_CPU_ALL_VARIANTS=ON \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DCMAKE_INSTALL_RPATH='$ORIGIN'
|
||||
RUN cmake --build ./build --config Release -j$(nproc)
|
||||
|
||||
FROM ubuntu:$UBUNTU_VERSION AS runtime
|
||||
|
||||
@@ -28,7 +36,9 @@ RUN apt-get update && \
|
||||
apt-get install --yes --no-install-recommends libgomp1 libvulkan1 mesa-vulkan-drivers && \
|
||||
apt-get clean
|
||||
|
||||
COPY --from=build /sd.cpp/build/bin/sd-cli /sd-cli
|
||||
COPY --from=build /sd.cpp/build/bin/sd-server /sd-server
|
||||
COPY --from=build /sd.cpp/build/bin /sd.cpp/bin
|
||||
RUN printf '#!/bin/sh\nexec /sd.cpp/bin/sd-cli "$@"\n' > /sd-cli && \
|
||||
printf '#!/bin/sh\nexec /sd.cpp/bin/sd-server "$@"\n' > /sd-server && \
|
||||
chmod +x /sd-cli /sd-server
|
||||
|
||||
ENTRYPOINT [ "/sd-cli" ]
|
||||
|
||||
@@ -15,6 +15,7 @@ API and command-line option may change frequently.***
|
||||
|
||||
## 🔥Important News
|
||||
|
||||
* **2026/06/25** 🚀 stable-diffusion.cpp now supports **Krea2**
|
||||
* **2026/06/04** 🚀 stable-diffusion.cpp now supports **Ideogram4**
|
||||
* **2026/05/31** 🚀 stable-diffusion.cpp now supports **PiD**
|
||||
* **2026/05/27** 🚀 stable-diffusion.cpp now supports **Lens**
|
||||
@@ -34,8 +35,8 @@ API and command-line option may change frequently.***
|
||||
- Super lightweight and without external dependencies
|
||||
- Supported models
|
||||
- Image Models
|
||||
- SD1.x, SD2.x, [SD-Turbo](https://huggingface.co/stabilityai/sd-turbo)
|
||||
- SDXL, [SDXL-Turbo](https://huggingface.co/stabilityai/sdxl-turbo)
|
||||
- [SD1.x, SD2.x, SD-Turbo](./docs/sd.md)
|
||||
- [SDXL, SDXL-Turbo](./docs/sd.md)
|
||||
- [Some SD1.x and SDXL distilled models](./docs/distilled_sd.md)
|
||||
- [SD3/SD3.5](./docs/sd3.md)
|
||||
- [FLUX.1-dev/FLUX.1-schnell](./docs/flux.md)
|
||||
@@ -50,21 +51,25 @@ API and command-line option may change frequently.***
|
||||
- [Ovis-Image](./docs/ovis_image.md)
|
||||
- [Anima](./docs/anima.md)
|
||||
- [ERNIE-Image](./docs/ernie_image.md)
|
||||
- [Boogu Image](./docs/boogu_image.md)
|
||||
- [Krea2](./docs/krea2.md)
|
||||
- [SeFi-Image](./docs/sefi_image.md)
|
||||
- [HiDream-O1-Image](./docs/hidream_o1_image.md)
|
||||
- [Ideogram4](./docs/ideogram4.md)
|
||||
- Image Edit Models
|
||||
- [FLUX.1-Kontext-dev](./docs/kontext.md)
|
||||
- [Qwen Image Edit series](./docs/qwen_image_edit.md)
|
||||
- [LongCat Image Edit](./docs/longcat_image.md)
|
||||
- [Boogu Image Edit](./docs/boogu_image.md)
|
||||
- Video Models
|
||||
- [Wan2.1/Wan2.2](./docs/wan.md)
|
||||
- [LTX-2.3](./docs/ltx2.md)
|
||||
- [PhotoMaker](https://github.com/TencentARC/PhotoMaker) support.
|
||||
- [PhotoMaker](./docs/photo_maker.md) support.
|
||||
- Control Net support with SD 1.5
|
||||
- LoRA support, same as [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#lora)
|
||||
- Latent Consistency Models support (LCM/LCM-LoRA)
|
||||
- Faster and memory efficient latent decoding with [TAESD](https://github.com/madebyollin/taesd)
|
||||
- Upscale images generated with [ESRGAN](https://github.com/xinntao/Real-ESRGAN)
|
||||
- Faster and memory efficient latent decoding with [TAESD](./docs/taesd.md)
|
||||
- Upscale images generated with [ESRGAN](./docs/esrgan.md)
|
||||
- Supported backends
|
||||
- CPU (AVX, AVX2 and AVX512 support for x86 architectures)
|
||||
- CUDA
|
||||
@@ -133,28 +138,9 @@ For runtime and parameter backend placement, see the [backend selection guide](.
|
||||
## More Guides
|
||||
|
||||
- [Backend selection](./docs/backend.md)
|
||||
- [SD1.x/SD2.x/SDXL](./docs/sd.md)
|
||||
- [SD3/SD3.5](./docs/sd3.md)
|
||||
- [FLUX.1-dev/FLUX.1-schnell](./docs/flux.md)
|
||||
- [FLUX.2-dev/FLUX.2-klein](./docs/flux2.md)
|
||||
- [FLUX.1-Kontext-dev](./docs/kontext.md)
|
||||
- [Chroma](./docs/chroma.md)
|
||||
- [🔥Qwen Image](./docs/qwen_image.md)
|
||||
- [🔥Qwen Image Edit series](./docs/qwen_image_edit.md)
|
||||
- [🔥Wan2.1/Wan2.2](./docs/wan.md)
|
||||
- [🔥LTX-2.3](./docs/ltx2.md)
|
||||
- [🔥Z-Image](./docs/z_image.md)
|
||||
- [Ovis-Image](./docs/ovis_image.md)
|
||||
- [Anima](./docs/anima.md)
|
||||
- [ERNIE-Image](./docs/ernie_image.md)
|
||||
- [HiDream-O1-Image](./docs/hidream_o1_image.md)
|
||||
- [Lens](./docs/lens.md)
|
||||
- [LongCat Image / LongCat Image Edit](./docs/longcat_image.md)
|
||||
- [RPC](./docs/rpc.md)
|
||||
- [LoRA](./docs/lora.md)
|
||||
- [LCM/LCM-LoRA](./docs/lcm.md)
|
||||
- [Using PhotoMaker to personalize image generation](./docs/photo_maker.md)
|
||||
- [Using ESRGAN to upscale results](./docs/esrgan.md)
|
||||
- [Using TAESD to faster decoding](./docs/taesd.md)
|
||||
- [Docker](./docs/docker.md)
|
||||
- [Quantization and GGUF](./docs/quantization_and_gguf.md)
|
||||
- [Inference acceleration via caching](./docs/caching.md)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 477 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 489 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 289 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
@@ -0,0 +1,37 @@
|
||||
set(SD_VERSION "@SD_INSTALL_VERSION@")
|
||||
set(SD_BUILD_COMMIT "@SD_INSTALL_COMMIT@")
|
||||
set(SD_SHARED_LIB @SD_SHARED_LIB@)
|
||||
|
||||
@PACKAGE_INIT@
|
||||
|
||||
set_and_check(SD_INCLUDE_DIR "@PACKAGE_SD_INCLUDE_INSTALL_DIR@")
|
||||
set_and_check(SD_LIB_DIR "@PACKAGE_SD_LIB_INSTALL_DIR@")
|
||||
set(SD_BIN_DIR "@PACKAGE_SD_BIN_INSTALL_DIR@")
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
find_dependency(ggml REQUIRED HINTS "${SD_LIB_DIR}/cmake")
|
||||
|
||||
if(NOT TARGET stable-diffusion)
|
||||
find_library(stable-diffusion_LIBRARY stable-diffusion
|
||||
REQUIRED
|
||||
HINTS "${SD_LIB_DIR}"
|
||||
NO_CMAKE_FIND_ROOT_PATH
|
||||
)
|
||||
|
||||
add_library(stable-diffusion UNKNOWN IMPORTED)
|
||||
set_target_properties(stable-diffusion
|
||||
PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${SD_INCLUDE_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "ggml::ggml"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
|
||||
IMPORTED_LOCATION "${stable-diffusion_LIBRARY}"
|
||||
INTERFACE_COMPILE_FEATURES "c_std_11;cxx_std_17"
|
||||
POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
if(SD_SHARED_LIB)
|
||||
target_compile_definitions(stable-diffusion
|
||||
INTERFACE SD_BUILD_SHARED_LIB)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
check_required_components(stable-diffusion)
|
||||
@@ -0,0 +1,11 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
|
||||
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
|
||||
|
||||
Name: stable-diffusion
|
||||
Description: Diffusion model(SD,Flux,Wan,Qwen Image,Z-Image,...) inference in pure C/C++
|
||||
Version: @SDCPP_BUILD_VERSION@
|
||||
Libs: -L${libdir} -lstable-diffusion
|
||||
Libs.private: -lggml -lggml-base
|
||||
Cflags: -I${includedir}
|
||||
+37
-7
@@ -3,7 +3,7 @@
|
||||
`stable-diffusion.cpp` has two backend assignments:
|
||||
|
||||
- `--backend` selects the runtime backend used to execute model graphs.
|
||||
- `--params-backend` selects the backend used to allocate model parameters.
|
||||
- `--params-backend` selects where model parameters are kept.
|
||||
|
||||
If `--params-backend` is not set, parameters use the same backend as their module runtime backend.
|
||||
|
||||
@@ -29,6 +29,20 @@ The same syntax is used for parameter placement:
|
||||
sd-cli -m model.safetensors -p "a cat" --backend cuda0 --params-backend te=cpu,vae=cpu
|
||||
```
|
||||
|
||||
`--params-backend` also accepts the special value `disk`:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend cuda0 --params-backend disk
|
||||
```
|
||||
|
||||
`--max-vram` can target resolved backend/device names:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend diffusion=cuda0,vae=vulkan0 --max-vram cuda0=6,vulkan0=2
|
||||
```
|
||||
|
||||
The budget applies to every module running on that backend.
|
||||
|
||||
Module names are case-insensitive. Hyphens and underscores in module names are ignored, so `clip_vision`, `clip-vision`, and `clipvision` are equivalent.
|
||||
|
||||
`all=`, `default=`, and `*=` can be used to set the default backend inside a mixed assignment:
|
||||
@@ -64,9 +78,11 @@ The special values `auto`, `default`, and an empty backend name select the defau
|
||||
|
||||
The special value `gpu` selects the first GPU backend, falling back to the first integrated GPU backend.
|
||||
|
||||
The special value `disk` is accepted only by `--params-backend`. `--backend disk` is invalid because `disk` is a parameter residency mode, not a runtime compute backend.
|
||||
|
||||
## Runtime backend vs. parameter backend
|
||||
|
||||
The runtime backend controls where graph execution runs. The parameter backend controls where model weights are allocated.
|
||||
The runtime backend controls where graph execution runs. The parameter backend controls where model weights are allocated or whether they are reloaded from disk on demand.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -76,6 +92,16 @@ sd-cli -m model.safetensors -p "a cat" --backend cuda0 --params-backend cpu
|
||||
|
||||
This runs all modules on `cuda0`, but stores parameters in CPU RAM. During execution, parameters are moved to the runtime backend as needed.
|
||||
|
||||
For example:
|
||||
|
||||
```shell
|
||||
sd-cli -m model.safetensors -p "a cat" --backend cuda0 --params-backend disk
|
||||
```
|
||||
|
||||
This runs all modules on `cuda0`, reloads parameters from the model file as needed, and releases those parameter buffers after use.
|
||||
|
||||
`disk` is never selected implicitly. If `--params-backend` is not set, parameters use the runtime backend.
|
||||
|
||||
Per-module assignments can be mixed:
|
||||
|
||||
```shell
|
||||
@@ -100,23 +126,27 @@ uses one shared CPU backend for both `te` and `vae` runtime execution.
|
||||
|
||||
Runtime and parameter assignments also share the same backend cache. If `--backend diffusion=cuda0` and `--params-backend diffusion=cuda0` resolve to the same device, both use the same backend instance.
|
||||
|
||||
`--params-backend disk` does not create a separate backend instance. Parameters are loaded lazily using the module runtime backend.
|
||||
|
||||
`SDBackendManager` owns the backend instances and frees them when the context or upscaler is destroyed. Model runners receive non-owning runtime and parameter backend pointers and do not free them.
|
||||
|
||||
## Compatibility flags
|
||||
|
||||
The older CPU placement flags are still supported:
|
||||
The example CLI/server still accepts these older CPU placement flags as compatibility aliases:
|
||||
|
||||
- `--clip-on-cpu`
|
||||
- `--vae-on-cpu`
|
||||
- `--control-net-cpu`
|
||||
- `--offload-to-cpu`
|
||||
|
||||
`--clip-on-cpu`, `--vae-on-cpu`, and `--control-net-cpu` affect runtime backend assignment only when `--backend` is not set. They map to `te=cpu`, `vae=cpu`, and `controlnet=cpu`.
|
||||
`--clip-on-cpu`, `--vae-on-cpu`, and `--control-net-cpu` are deprecated. The example argument layer prepends `te=cpu`, `vae=cpu`, and `controlnet=cpu` to `--backend` before creating the context.
|
||||
|
||||
`--offload-to-cpu` affects parameter backend assignment only when `--params-backend` is not set. It is equivalent to:
|
||||
`--offload-to-cpu` prepends a CPU default to the parameter assignment in the caller before creating the context:
|
||||
|
||||
```shell
|
||||
--params-backend cpu
|
||||
--params-backend '*=cpu'
|
||||
```
|
||||
|
||||
Explicit `--backend` and `--params-backend` assignments are preferred for new commands.
|
||||
Because this default is inserted first, later explicit `--params-backend` entries can still override it, for example `--offload-to-cpu --params-backend te=disk` keeps non-TE parameters on CPU and reloads TE parameters from disk.
|
||||
|
||||
Library callers should set `backend` and `params_backend` directly. The old CPU/offload fields are no longer part of the C API. Explicit `--backend` and `--params-backend` assignments are preferred for new commands.
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# How to Use
|
||||
|
||||
Boogu Image uses a Boogu diffusion transformer, the FLUX VAE, and Qwen3-VL as the LLM text and vision encoder.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download Boogu Image
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Boogu-Image/tree/main/diffusion_models
|
||||
- Download vae
|
||||
- safetensors: https://huggingface.co/black-forest-labs/FLUX.1-dev/blob/main/ae.safetensors
|
||||
- Download Qwen3-VL 8B
|
||||
- gguf: https://huggingface.co/unsloth/Qwen3-VL-8B-Instruct-GGUF/tree/main
|
||||
- For image editing with GGUF text encoders, also download the matching mmproj file and pass it with `--llm_vision`.
|
||||
|
||||
## Examples
|
||||
|
||||
### Boogu Image Base
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\..\ComfyUI\models\diffusion_models\boogu_image_base_bf16.safetensors --llm ..\..\llm\Qwen3VL-8B-Instruct-Q4_K_M.gguf --vae ..\..\ComfyUI\models\vae\ae.sft -p "a lovely cat" --diffusion-fa -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img width="256" alt="Boogu Image Base example" src="../assets/boogu/example.png" />
|
||||
|
||||
### Boogu Image Edit
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\..\ComfyUI\models\diffusion_models\boogu_image_edit_bf16.safetensors --llm ..\..\llm\Qwen3VL-8B-Instruct-Q4_K_M.gguf --llm_vision ..\..\llm\mmproj-Qwen3VL-8B-Instruct-F16.gguf --vae ..\..\ComfyUI\models\vae\ae.sft --diffusion-fa -v --offload-to-cpu -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'boogu.cpp'"
|
||||
```
|
||||
|
||||
<img width="256" alt="Boogu Image Edit example" src="../assets/boogu/edit_example.png" />
|
||||
@@ -0,0 +1,27 @@
|
||||
# How to Use
|
||||
|
||||
Krea2 uses a Krea2 diffusion transformer, the Wan2.1 VAE, and Qwen3-VL 4B as the LLM text encoder.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download Krea2 Raw
|
||||
- safetensors: https://huggingface.co/krea/Krea-2-Raw/tree/main
|
||||
- gguf: https://huggingface.co/realrebelai/KREA-2_GGUFs/tree/main/BASE
|
||||
- Download Krea2 Turbo
|
||||
- safetensors: https://huggingface.co/krea/Krea-2-Turbo/tree/main
|
||||
- gguf: https://huggingface.co/realrebelai/KREA-2_GGUFs/tree/main/TURBO
|
||||
- Download vae
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/blob/main/split_files/vae/wan_2.1_vae.safetensors
|
||||
- Download Qwen3-VL 4B
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Krea-2/tree/main/text_encoders
|
||||
- gguf: https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct-GGUF/tree/main
|
||||
|
||||
## Examples
|
||||
|
||||
### Krea2
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\..\ComfyUI\models\diffusion_models\Krea-2-Raw-Q8_0.gguf --llm ..\..\ComfyUI\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\..\ComfyUI\models\vae\wan_2.1_vae.safetensors -p "a lovely cat holding a sign says 'krea2.cpp'" --diffusion-fa -v --offload-to-cpu
|
||||
```
|
||||
|
||||
<img width="256" alt="Krea2 Raw example" src="../assets/krea2/example.png" />
|
||||
+33
-1
@@ -21,6 +21,38 @@ and the compute buffer shrink in the debug log:
|
||||
|
||||
Using `--offload-to-cpu` allows you to offload weights to the CPU, saving VRAM without reducing generation speed.
|
||||
|
||||
## Use params backend to reduce VRAM or RAM usage.
|
||||
|
||||
`--params-backend` controls where model parameters are kept. If it is not set, parameters use the same backend as `--backend`, so a GPU runtime backend also keeps parameters in VRAM.
|
||||
|
||||
Use CPU params to reduce VRAM usage:
|
||||
|
||||
```shell
|
||||
--backend cuda0 --params-backend cpu
|
||||
```
|
||||
|
||||
This keeps model weights in system RAM and moves them to the runtime backend when needed. In the example CLI/server, `--offload-to-cpu` is a compatibility shortcut that prepends `*=cpu` to `--params-backend` before creating the context, so explicit module assignments can still override it:
|
||||
|
||||
```shell
|
||||
--offload-to-cpu --params-backend te=disk
|
||||
```
|
||||
|
||||
Use disk params to reduce both VRAM and RAM usage:
|
||||
|
||||
```shell
|
||||
--backend cuda0 --params-backend disk
|
||||
```
|
||||
|
||||
This reloads parameters from the model file on demand and releases them after use. It has the lowest memory residency, but can be slower because weights must be read again. `disk` is never selected implicitly; set it explicitly when RAM usage matters more than reload cost.
|
||||
|
||||
Per-module assignments can target only the largest modules:
|
||||
|
||||
```shell
|
||||
--backend cuda0 --params-backend diffusion=disk,te=cpu,vae=cpu
|
||||
```
|
||||
|
||||
See [backend selection](./backend.md) for full syntax.
|
||||
|
||||
## Use quantization to reduce memory usage.
|
||||
|
||||
[quantization](./quantization_and_gguf.md)
|
||||
[quantization](./quantization_and_gguf.md)
|
||||
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
# PuLID-Flux face-identity preservation
|
||||
|
||||
stable-diffusion.cpp supports the [PuLID-Flux](https://github.com/ToTheBeginning/PuLID)
|
||||
identity-injection technique on top of Flux.1 (schnell or dev) models.
|
||||
Given a single source portrait, PuLID-Flux produces new generations that
|
||||
preserve the source person's face across arbitrary scenes, poses, and
|
||||
prompts.
|
||||
|
||||
Unlike PhotoMaker (which extracts the identity inside the inference
|
||||
process from a directory of images), PuLID-Flux's identity extractor is
|
||||
a heavy stack (insightface ArcFace + EVA-CLIP-L + IDFormer encoder) that
|
||||
is impractical to port to C++/ggml. To keep this implementation small and
|
||||
cross-vendor, **stable-diffusion.cpp consumes a precomputed identity
|
||||
embedding** produced by an external Python tool that runs once per source
|
||||
portrait. Everything downstream of that one-shot extraction is C++ and
|
||||
runs on any backend (Vulkan, CUDA, Metal, ROCm, CPU).
|
||||
|
||||
## Architecture summary
|
||||
|
||||
The PuLID-Flux contribution to the Flux denoise loop is a stack of 20
|
||||
small cross-attention modules (`PerceiverAttentionCA`) inserted between
|
||||
the Flux transformer blocks:
|
||||
|
||||
- After every 2nd of the 19 double-stream blocks (10 hook points)
|
||||
- After every 4th of the 38 single-stream blocks (10 hook points)
|
||||
|
||||
Each cross-attention layer takes the current image tokens as query, the
|
||||
32-token / 2048-dim identity embedding as key+value, and adds its output
|
||||
(scaled by `id_weight`, typically 1.0) back to the image tokens.
|
||||
|
||||
## Required weights
|
||||
|
||||
Three files in addition to the standard Flux weight set:
|
||||
|
||||
1. **Flux base** (transformer + VAE + clip_l + t5xxl) -- exactly as
|
||||
[docs/flux.md](flux.md) describes.
|
||||
2. **PuLID weights** -- download from
|
||||
[guozinan/PuLID](https://huggingface.co/guozinan/PuLID):
|
||||
- `pulid_flux_v0.9.0.safetensors` or `pulid_flux_v0.9.1.safetensors`
|
||||
(recommended; this implementation is verified against v0.9.1)
|
||||
- **v1.1 (`pulid_v1.1.safetensors`) is NOT yet supported** -- it uses
|
||||
renamed keys (`id_adapter_attn_layers.*` instead of `pulid_ca.*`)
|
||||
and possibly different module structure. Future PR.
|
||||
3. **Identity embedding (.pulidembd)** -- produced by the precompute
|
||||
tool below.
|
||||
|
||||
## Precompute the identity embedding
|
||||
|
||||
The precompute tool runs the PyTorch identity-extraction stack on a
|
||||
single portrait image and writes the resulting `(32, 2048)` embedding
|
||||
to a `.pulidembd` binary file (about 131 KB). Run it once per source
|
||||
person; the same file is reused for any number of generations.
|
||||
|
||||
A reference Python script is provided alongside this docs file at
|
||||
[`script/pulid_extract_id.py`](../script/pulid_extract_id.py). It
|
||||
requires:
|
||||
- A working CUDA / CPU PyTorch stack
|
||||
- `insightface`, `facexlib`, `eva-clip`, `torchvision`, `opencv-python`,
|
||||
`huggingface_hub`, `gguf`
|
||||
- The PuLID weights file (same one stable-diffusion.cpp will load below)
|
||||
- The ToTheBeginning/PuLID repo's `pulid/` package (including
|
||||
`pulid/pipeline_flux.py`) and `eva_clip/` package on `PYTHONPATH`; `flux/`
|
||||
is not needed for embedding extraction
|
||||
|
||||
Run it as:
|
||||
|
||||
```
|
||||
python pulid_extract_id.py \
|
||||
--portrait /path/to/source-photo.jpg \
|
||||
--pulid-weights /path/to/pulid_flux_v0.9.1.safetensors \
|
||||
--out /path/to/source.pulidembd
|
||||
```
|
||||
|
||||
## Format (gguf)
|
||||
|
||||
The embedding is a standard **gguf** container holding a single tensor:
|
||||
|
||||
```
|
||||
tensor name : "pulid_id"
|
||||
shape : [token_dim, num_tokens] (ggml order; typically [2048, 32])
|
||||
type : F16 (also accepts F32 / BF16)
|
||||
metadata : general.architecture = "pulid", pulid.version = 1
|
||||
```
|
||||
|
||||
stable-diffusion.cpp loads it with the normal gguf reader
|
||||
(`gguf_init_from_file`) and converts to fp32 at load time -- no bespoke
|
||||
parser. Total file size for the typical (32, 2048, fp16) case is ~131 KB.
|
||||
|
||||
## Command-line usage
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe \
|
||||
--diffusion-model models\flux1-schnell-Q4_K_S.gguf \
|
||||
--vae models\ae.safetensors \
|
||||
--clip_l models\clip_l.safetensors \
|
||||
--t5xxl models\t5xxl_fp16.safetensors \
|
||||
--pulid-weights models\pulid_flux_v0.9.1.safetensors \
|
||||
--pulid-id-embedding source.pulidembd \
|
||||
--pulid-id-weight 1.0 \
|
||||
-p "candid photograph of a young woman on a beach at sunset" \
|
||||
--cfg-scale 1.0 --sampling-method euler --steps 4 -W 512 -H 512 \
|
||||
--seed 42 --clip-on-cpu \
|
||||
-o out.png
|
||||
```
|
||||
|
||||
For Flux Dev (instead of Schnell), add `--guidance 3.5` and `--steps 20`.
|
||||
|
||||
## Flags
|
||||
|
||||
| Flag | Purpose |
|
||||
|----------------------------|-------------------------------------------------------------------|
|
||||
| `--pulid-weights <path>` | Path to `pulid_flux_v0.9.x.safetensors`. Loaded with the model. |
|
||||
| `--pulid-id-embedding <p>` | Path to a `.pulidembd` binary produced by the precompute tool. |
|
||||
| `--pulid-id-weight <f>` | Identity-injection strength. Typical 0.7-1.2; default 1.0. |
|
||||
|
||||
All three flags must be set together to activate PuLID. Setting only
|
||||
`--pulid-weights` (no embedding) loads the weights but disables injection
|
||||
at runtime. Setting `--pulid-id-weight 0` zeros out the contribution
|
||||
(useful for falsification testing: outputs should be byte-identical to
|
||||
a no-PuLID run with the same seed).
|
||||
|
||||
## Memory budget
|
||||
|
||||
At 512x512, 4 steps (Schnell), the 20 cross-attention layers add roughly
|
||||
10% to denoise time and almost nothing to peak VRAM. Tested on a 12 GB
|
||||
consumer card alongside Flux Schnell Q4 GGUF + CPU-offloaded clip_l and
|
||||
t5xxl + GPU-resident VAE.
|
||||
|
||||
At 1024x1024 with Flux Dev Q4 + 20 steps + PuLID, the VAE decode compute
|
||||
buffer doesn't fit on a 12 GB card even with `--vae-on-cpu`. Workaround:
|
||||
explicitly route VAE to the CPU backend instead of the offload flag:
|
||||
|
||||
```
|
||||
--backend "diffusion=vulkan0,vae=cpu"
|
||||
```
|
||||
|
||||
The `--vae-on-cpu` flag offloads VAE weights but leaves the compute graph
|
||||
on the default backend; this is existing stable-diffusion.cpp behavior,
|
||||
not a PuLID-specific issue. Documented here because anyone running PuLID
|
||||
at 1024 will hit it.
|
||||
|
||||
## Backend selection
|
||||
|
||||
The standard `--backend` flag works as documented. Common patterns:
|
||||
|
||||
```
|
||||
# AMD Vulkan
|
||||
--backend "diffusion=vulkan0,vae=cpu"
|
||||
|
||||
# NVIDIA Vulkan
|
||||
--backend "diffusion=vulkan1,vae=cpu"
|
||||
|
||||
# CUDA
|
||||
--backend "diffusion=cuda0,vae=cpu"
|
||||
```
|
||||
|
||||
The PuLID cross-attention layers run on the same backend as the main
|
||||
diffusion model. They have not yet been independently profiled on every
|
||||
backend; only Vulkan and CPU have been tested by the original contributor.
|
||||
|
||||
## Verification
|
||||
|
||||
A three-way SHA-256 check is the recommended sanity test when bringing up
|
||||
a new combination of model + backend + hardware:
|
||||
|
||||
| Run | Expected hash relation |
|
||||
|----------------------------------------------|------------------------------------|
|
||||
| A: no `--pulid-*` flags | baseline |
|
||||
| B: PuLID flags, `--pulid-id-weight 0.0` | **byte-identical to A** |
|
||||
| C: PuLID flags, `--pulid-id-weight 1.0` | **different from A,B**, preserves source identity |
|
||||
|
||||
If A and C differ but A and B differ too, the injection is allocating
|
||||
or computing something even at zero weight -- likely a bug.
|
||||
|
||||
## Limitations / not yet supported
|
||||
|
||||
- **`--skip-layers` (skip-layer-guidance / SLG) combined with PuLID** is not
|
||||
supported. The `pulid_ca` index advances per non-skipped block, so a
|
||||
skipped block silently misaligns the cross-attention weight assignment
|
||||
vs. the trained intervals. The reference PyTorch implementation does
|
||||
not have SLG either, so there is no well-defined behavior to emulate.
|
||||
Use either feature alone.
|
||||
- **PuLID v1.1 weights** (`pulid_v1.1.safetensors`, renamed key layout).
|
||||
- **Multiple ID images.** The reference PyTorch implementation can fuse
|
||||
several portraits into one embedding for stronger identity. This
|
||||
implementation accepts a single embedding produced from one or more
|
||||
images by the external precompute tool.
|
||||
- **Negative-prompt branch of CFG.** PuLID only injects on the positive
|
||||
conditioning path in the published reference, and the implementation
|
||||
here follows that. Flux's distilled guidance doesn't run a separate
|
||||
uncond branch in normal use, so this matters only for `--true-cfg`
|
||||
workflows that aren't standard for Flux.
|
||||
- **Backends other than Vulkan and CPU** are untested by the original
|
||||
contributor. The implementation is pure-ggml and should work on CUDA,
|
||||
ROCm, and Metal, but verification by users on those backends is
|
||||
welcomed.
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
# Building and Using the RPC Server with `stable-diffusion.cpp`
|
||||
|
||||
This guide covers how to build a version of [the RPC server from `llama.cpp`](https://github.com/ggml-org/llama.cpp/blob/master/tools/rpc/README.md) that is compatible with your version of `stable-diffusion.cpp` to manage multi-backends setups. RPC allows you to offload specific model components to a remote server.
|
||||
|
||||
> **Note on Model Location:** The model files (e.g., `.safetensors` or `.gguf`) remain on the **Client** machine. The client parses the file and transmits the necessary tensor data and computational graphs to the server. The server does not need to store the model files locally.
|
||||
|
||||
## 1. Building `stable-diffusion.cpp` with RPC client
|
||||
|
||||
First, you should build the client application from source. It requires `SD_RPC=ON` to include the RPC backend to your client.
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. \
|
||||
-DSD_RPC=ON \
|
||||
# Add other build flags here (e.g., -DSD_VULKAN=ON)
|
||||
cmake --build . --config Release -j $(nproc)
|
||||
```
|
||||
|
||||
> **Note:** Ensure you add the other flags you would normally use (e.g., `-DSD_VULKAN=ON`, `-DSD_CUDA=ON`, `-DSD_HIPBLAS=ON`, or `-DGGML_METAL=ON`), for more information about building `stable-diffusion.cpp` from source, please refer to the [build.md](build.md) documentation.
|
||||
|
||||
## 2. Ensure `llama.cpp` is at the correct commit
|
||||
|
||||
`stable-diffusion.cpp`'s RPC client is designed to work with a specific version of `llama.cpp` (compatible with the `ggml` submodule) to ensure API compatibility. The commit hash for `llama.cpp` is stored in `ggml/scripts/sync-llama.last`.
|
||||
|
||||
> **Start from Root:** Perform these steps from the root of your `stable-diffusion.cpp` directory.
|
||||
|
||||
1. Read the target commit hash from the submodule tracker:
|
||||
|
||||
```bash
|
||||
# Linux / WSL / MacOS
|
||||
HASH=$(cat ggml/scripts/sync-llama.last)
|
||||
|
||||
# Windows (PowerShell)
|
||||
$HASH = Get-Content -Path "ggml\scripts\sync-llama.last"
|
||||
```
|
||||
|
||||
2. Clone `llama.cpp` at the target commit .
|
||||
```bash
|
||||
git clone https://github.com/ggml-org/llama.cpp.git
|
||||
cd llama.cpp
|
||||
git checkout $HASH
|
||||
```
|
||||
To save on download time and storage, you can use a shallow clone to download only the target commit:
|
||||
```bash
|
||||
mkdir -p llama.cpp
|
||||
cd llama.cpp
|
||||
git init
|
||||
git remote add origin https://github.com/ggml-org/llama.cpp.git
|
||||
git fetch --depth 1 origin $HASH
|
||||
git checkout FETCH_HEAD
|
||||
```
|
||||
|
||||
## 3. Build `llama.cpp` (RPC Server)
|
||||
|
||||
The RPC server acts as the worker. You must explicitly enable the **backend** (the hardware interface, such as CUDA for Nvidia, Metal for Apple Silicon, or Vulkan) when building, otherwise the server will default to using only the CPU.
|
||||
|
||||
To find the correct flags for your system, refer to the official documentation for the [`llama.cpp`](https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md) repository.
|
||||
|
||||
> **Crucial:** You must include the compiler flags required to satisfy the API compatibility with `stable-diffusion.cpp` (`-DGGML_MAX_NAME=128`). Without this flag, `GGML_MAX_NAME` will default to `64` for the server, and data transfers between the client and server will fail. Of course, `-DGGML_RPC` must also be enabled.
|
||||
>
|
||||
> I recommend disabling the `LLAMA_CURL` flag to avoid unnecessary dependencies, and disabling shared library builds to avoid potential conflicts.
|
||||
|
||||
> **Build Target:** We are specifically building the `rpc-server` target. This prevents the build system from compiling the entire `llama.cpp` suite (like `llama-server`), making the build significantly faster.
|
||||
|
||||
### Linux / WSL (Vulkan)
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DGGML_RPC=ON \
|
||||
-DGGML_VULKAN=ON \ # Ensure backend is enabled
|
||||
-DGGML_BUILD_SHARED_LIBS=OFF \
|
||||
-DLLAMA_CURL=OFF \
|
||||
-DCMAKE_C_FLAGS=-DGGML_MAX_NAME=128 \
|
||||
-DCMAKE_CXX_FLAGS=-DGGML_MAX_NAME=128
|
||||
cmake --build . --config Release --target rpc-server -j $(nproc)
|
||||
```
|
||||
|
||||
### macOS (Metal)
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DGGML_RPC=ON \
|
||||
-DGGML_METAL=ON \
|
||||
-DGGML_BUILD_SHARED_LIBS=OFF \
|
||||
-DLLAMA_CURL=OFF \
|
||||
-DCMAKE_C_FLAGS=-DGGML_MAX_NAME=128 \
|
||||
-DCMAKE_CXX_FLAGS=-DGGML_MAX_NAME=128
|
||||
cmake --build . --config Release --target rpc-server
|
||||
```
|
||||
|
||||
### Windows (Visual Studio 2022, Vulkan)
|
||||
|
||||
```powershell
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G "Visual Studio 17 2022" -A x64 `
|
||||
-DGGML_RPC=ON `
|
||||
-DGGML_VULKAN=ON `
|
||||
-DGGML_BUILD_SHARED_LIBS=OFF `
|
||||
-DLLAMA_CURL=OFF `
|
||||
-DCMAKE_C_FLAGS=-DGGML_MAX_NAME=128 `
|
||||
-DCMAKE_CXX_FLAGS=-DGGML_MAX_NAME=128
|
||||
cmake --build . --config Release --target rpc-server
|
||||
```
|
||||
|
||||
## 4. Usage
|
||||
|
||||
Once both applications are built, you can run the server and the client to manage your GPU allocation.
|
||||
|
||||
### Step A: Run the RPC Server
|
||||
|
||||
Start the server. It listens for connections on the default address (usually `localhost:50052`). If your server is on a different machine, ensure the server binds to the correct interface and your firewall allows the connection.
|
||||
|
||||
**On the Server :**
|
||||
If running on the same machine, you can use the default address:
|
||||
|
||||
```bash
|
||||
./rpc-server
|
||||
```
|
||||
|
||||
If you want to allow connections from other machines on the network:
|
||||
|
||||
```bash
|
||||
./rpc-server --host 0.0.0.0
|
||||
```
|
||||
|
||||
> **Security Warning:** The RPC server does not currently support authentication or encryption. **Only run the server on trusted local networks**. Never expose the RPC server directly to the open internet.
|
||||
|
||||
> **Drivers & Hardware:** Ensure the Server machine has the necessary drivers installed and functional (e.g., Nvidia Drivers for CUDA, Vulkan SDK, or Metal). If no devices are found, the server will simply fallback to CPU usage.
|
||||
|
||||
<!-- ### Step B: Check if the client is able to connect to the server and see the available devices
|
||||
|
||||
We're assuming the server is running on your local machine, and listening on the default port `50052`. If it's running on a different machine, you can replace `localhost` with the IP address of the server.
|
||||
|
||||
**On the Client:**
|
||||
|
||||
```bash
|
||||
./sd-cli --rpc-servers localhost:50052 --list-devices
|
||||
```
|
||||
|
||||
If the server is running and the client is able to connect, you should see `RPC0 localhost:50052` in the list of devices.
|
||||
|
||||
Example output:
|
||||
(Client built without GPU acceleration, two GPUs available on the server)
|
||||
|
||||
```
|
||||
List of available GGML devices:
|
||||
Name Description
|
||||
-------------------
|
||||
CPU AMD Ryzen 9 5900X 12-Core Processor
|
||||
RPC0 localhost:50052
|
||||
RPC1 localhost:50052
|
||||
``` -->
|
||||
|
||||
### Step B: Run with RPC device
|
||||
|
||||
If everything is working correctly, you can now run the client while offloading some or all of the work to the RPC server.
|
||||
|
||||
Example: Setting the main backend to the RPC0 device for doing all the work on the server.
|
||||
|
||||
```bash
|
||||
./sd-cli -m models/sd1.5.safetensors -p "A cat" --rpc-servers localhost:50052 --backend RPC0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Scaling: Multiple RPC Servers
|
||||
|
||||
You can connect the client to multiple RPC servers simultaneously to scale out your hardware usage.
|
||||
|
||||
Example: A main machine (192.168.1.10) with 3 GPUs, with one GPU running CUDA and the other two running Vulkan, and a second machine (192.168.1.11) only one GPU.
|
||||
|
||||
**On the first machine (Running two server instances):**
|
||||
|
||||
**Terminal 1 (CUDA):**
|
||||
|
||||
```bash
|
||||
# Linux / WSL
|
||||
export CUDA_VISIBLE_DEVICES=0
|
||||
cd ./build_cuda/bin/Release
|
||||
./rpc-server --host 0.0.0.0
|
||||
|
||||
# Windows PowerShell
|
||||
$env:CUDA_VISIBLE_DEVICES="0"
|
||||
cd .\build_cuda\bin\Release
|
||||
./rpc-server --host 0.0.0.0
|
||||
```
|
||||
|
||||
**Terminal 2 (Vulkan):**
|
||||
|
||||
```bash
|
||||
cd ./build_vulkan/bin/Release
|
||||
# ignore the first GPU (used by CUDA server)
|
||||
./rpc-server --host 0.0.0.0 --port 50053 -d Vulkan1,Vulkan2
|
||||
```
|
||||
|
||||
**On the second machine:**
|
||||
|
||||
```bash
|
||||
cd ./build/bin/Release
|
||||
./rpc-server --host 0.0.0.0
|
||||
```
|
||||
|
||||
**On the Client:**
|
||||
Pass multiple server addresses separated by commas.
|
||||
|
||||
```bash
|
||||
./sd-cli --rpc-servers 192.168.1.10:50052,192.168.1.10:50053,192.168.1.11:50052 [...]
|
||||
```
|
||||
|
||||
The client will map these servers to sequential device IDs (e.g., RPC0 from the first server, RPC2, RPC3 from the second, and RPC4 from the third). With this setup, you could for example use RPC0 for the main backend, RPC1 and RPC2 for the text encoders, and RPC3 for the VAE.
|
||||
|
||||
---
|
||||
|
||||
## 6. Performance Considerations
|
||||
|
||||
RPC performance is heavily dependent on network bandwidth, as large weights and activations must be transferred back and forth over the network, especially for large models, or when using high resolutions. For best results, ensure your network connection is stable and has sufficient bandwidth (>1Gbps recommended). This shoumd not be a concern if you are running the server and client on the same machine, as the data transfer will happen over the loopback interface.
|
||||
@@ -0,0 +1,50 @@
|
||||
# How to Use
|
||||
|
||||
SeFi-Image uses a Flux2-style dual-time transformer (semantic + texture streams), the standard Flux2 VAE, and Qwen3-VL as the LLM text encoder. Tech report: [arXiv:2606.22568](https://arxiv.org/abs/2606.22568).
|
||||
|
||||
## Download weights
|
||||
|
||||
The SeFi-Image family ships in three scales (1B / 2B / 5B) and three families (Base / RL / turbo), all gated on Hugging Face under https://huggingface.co/SeFi-Image.
|
||||
|
||||
- 1B and 2B variants pair with Qwen3-VL-2B-Instruct.
|
||||
- 5B variants pair with Qwen3-VL-4B-Instruct.
|
||||
- All variants use the standard Flux2 VAE (`flux2_ae.safetensors` from https://huggingface.co/black-forest-labs/FLUX.2-dev).
|
||||
|
||||
Convert the transformer and text encoder to sd.cpp safetensors:
|
||||
|
||||
```bash
|
||||
python3 script/convert_sefi.py <hf_repo_dir> <out_dir>/sefi_<scale>_<family>.safetensors
|
||||
python3 script/convert_qwen3_vl.py <hf_repo_dir>/Qwen3-VL-XB-Instruct <out_dir>/qwen3_vl_<X>b.safetensors
|
||||
```
|
||||
|
||||
## Variant defaults
|
||||
|
||||
| Family | timestep_shift_alpha | steps | cfg-scale |
|
||||
|---|---|---|---|
|
||||
| Base | 0.3 | 50 | 4.0 |
|
||||
| RL | 0.3 | 50 | 4.0 |
|
||||
| turbo | 1.0 | 4 | 1.0 |
|
||||
|
||||
The dispatcher picks `alpha` from the filename (`turbo` substring => 1.0, otherwise 0.3). Override via `--extra-sample-args sefi_alpha=<value>` or `sefi_delta_t=<value>`.
|
||||
|
||||
## Examples
|
||||
|
||||
### 1B / 2B turbo
|
||||
|
||||
```
|
||||
./build/bin/sd-cli --diffusion-model /path/to/sefi_1b_turbo.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_2b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 1.0 --steps 4 -W 1024 -H 1024 -s 42 --diffusion-fa --offload-to-cpu -o out.png
|
||||
```
|
||||
|
||||
### 1B / 2B base
|
||||
|
||||
```
|
||||
./build/bin/sd-cli --diffusion-model /path/to/sefi_1b_base.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_2b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 4.0 --steps 50 -W 1024 -H 1024 -s 42 --diffusion-fa --offload-to-cpu -o out.png
|
||||
```
|
||||
|
||||
### 5B (needs streaming on 12 GiB VRAM)
|
||||
|
||||
```
|
||||
./build/bin/sd-cli --diffusion-model /path/to/sefi_5b_turbo.safetensors --vae /path/to/flux2_ae.safetensors --llm /path/to/qwen3_vl_4b.safetensors -p "a photograph of an orange tabby cat sitting on a couch" --cfg-scale 1.0 --steps 4 -W 1024 -H 1024 -s 42 --diffusion-fa --max-vram 8 --stream-layers --offload-to-cpu -o out.png
|
||||
```
|
||||
|
||||
<img alt="SeFi-Image 5B turbo example" src="../assets/sefi_image/example.png" />
|
||||
+4
-199
@@ -1,204 +1,9 @@
|
||||
# Run
|
||||
# Usage
|
||||
|
||||
```
|
||||
usage: ./bin/sd-cli [options]
|
||||
For detailed command-line arguments, run:
|
||||
|
||||
CLI Options:
|
||||
-o, --output <string> path to write result image to. you can use printf-style %d format specifiers for image
|
||||
sequences (default: ./output.png) (eg. output_%03d.png). Single-file video outputs
|
||||
support .avi, .webm, and animated .webp
|
||||
--image <string> path to the image to inspect (for metadata mode)
|
||||
--metadata-format <string> metadata output format, one of [text, json] (default: text)
|
||||
--preview-path <string> path to write preview image to (default: ./preview.png). Multi-frame previews support
|
||||
.avi, .webm, and animated .webp
|
||||
--preview-interval <int> interval in denoising steps between consecutive updates of the image preview file
|
||||
(default is 1, meaning updating at every step)
|
||||
--output-begin-idx <int> starting index for output image sequence, must be non-negative (default 0 if specified
|
||||
%d in output path, 1 otherwise)
|
||||
--canny apply canny preprocessor (edge detection)
|
||||
--convert-name convert tensor name (for convert mode)
|
||||
-v, --verbose print extra info
|
||||
--color colors the logging tags according to level
|
||||
--taesd-preview-only prevents usage of taesd for decoding the final image. (for use with --preview tae)
|
||||
--preview-noisy enables previewing noisy inputs of the models rather than the denoised outputs
|
||||
--metadata-raw include raw hex previews for unparsed metadata payloads
|
||||
--metadata-brief truncate long metadata text values in text output
|
||||
--metadata-all include structural/container entries such as IHDR, IDAT, and non-metadata JPEG segments
|
||||
-M, --mode run mode, one of [img_gen, vid_gen, upscale, convert, metadata], default: img_gen
|
||||
--preview preview method. must be one of the following [none, proj, tae, vae] (default is none)
|
||||
-h, --help show this help message and exit
|
||||
|
||||
Context Options:
|
||||
-m, --model <string> path to full model
|
||||
--clip_l <string> path to the clip-l text encoder
|
||||
--clip_g <string> path to the clip-g text encoder
|
||||
--clip_vision <string> path to the clip-vision encoder
|
||||
--t5xxl <string> path to the t5xxl text encoder
|
||||
--llm <string> path to the llm text encoder. For example: (qwenvl2.5 for qwen-image,
|
||||
mistral-small3.2 for flux2, ...)
|
||||
--llm_vision <string> path to the llm vit
|
||||
--qwen2vl <string> alias of --llm. Deprecated.
|
||||
--qwen2vl_vision <string> alias of --llm_vision. Deprecated.
|
||||
--diffusion-model <string> path to the standalone diffusion model
|
||||
--high-noise-diffusion-model <string> path to the standalone high noise diffusion model
|
||||
--uncond-diffusion-model <string> path to the standalone unconditional diffusion model, currently used by
|
||||
Ideogram4 CFG
|
||||
--vae <string> path to standalone vae model
|
||||
--taesd <string> path to taesd. Using Tiny AutoEncoder for fast decoding (low quality)
|
||||
--tae <string> alias of --taesd
|
||||
--control-net <string> path to control net model
|
||||
--embd-dir <string> embeddings directory
|
||||
--lora-model-dir <string> lora model directory
|
||||
--hires-upscalers-dir <string> highres fix upscaler model directory
|
||||
--tensor-type-rules <string> weight type per tensor pattern (example: "^vae\.=f16,model\.=q8_0")
|
||||
--photo-maker <string> path to PHOTOMAKER model
|
||||
--upscale-model <string> path to esrgan model.
|
||||
-t, --threads <int> number of threads to use during computation (default: -1). If threads <= 0,
|
||||
then threads will be set to the number of CPU physical cores
|
||||
--chroma-t5-mask-pad <int> t5 mask pad size of chroma
|
||||
--max-vram <float> maximum VRAM budget in GiB for graph-cut segmented execution. 0 disables
|
||||
graph splitting; a negative value auto-detects free VRAM, sparing the
|
||||
specified value (e.g. -0.5 will keep at least 0.5 GiB free)
|
||||
--force-sdxl-vae-conv-scale force use of conv scale on sdxl vae
|
||||
--offload-to-cpu place the weights in RAM to save VRAM, and automatically load them into VRAM
|
||||
when needed
|
||||
--mmap whether to memory-map model
|
||||
--control-net-cpu keep controlnet in cpu (for low vram)
|
||||
--clip-on-cpu keep clip in cpu (for low vram)
|
||||
--vae-on-cpu keep vae in cpu (for low vram)
|
||||
--fa use flash attention
|
||||
--diffusion-fa use flash attention in the diffusion model only
|
||||
--diffusion-conv-direct use ggml_conv2d_direct in the diffusion model
|
||||
--vae-conv-direct use ggml_conv2d_direct in the vae model
|
||||
--circular enable circular padding for convolutions
|
||||
--circularx enable circular RoPE wrapping on x-axis (width) only
|
||||
--circulary enable circular RoPE wrapping on y-axis (height) only
|
||||
--chroma-disable-dit-mask disable dit mask for chroma
|
||||
--qwen-image-zero-cond-t enable zero_cond_t for qwen image
|
||||
--chroma-enable-t5-mask enable t5 mask for chroma
|
||||
--type weight type (examples: f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_K, q3_K,
|
||||
q4_K). If not specified, the default is the type of the weight file
|
||||
--rng RNG, one of [std_default, cuda, cpu], default: cuda(sd-webui), cpu(comfyui)
|
||||
--sampler-rng sampler RNG, one of [std_default, cuda, cpu]. If not specified, use --rng
|
||||
--prediction prediction type override, one of [eps, v, edm_v, sd3_flow, flux_flow,
|
||||
flux2_flow]
|
||||
--lora-apply-mode the way to apply LoRA, one of [auto, immediately, at_runtime], default is
|
||||
auto. In auto mode, if the model weights contain any quantized parameters,
|
||||
the at_runtime mode will be used; otherwise, immediately will be used.The
|
||||
immediately mode may have precision and compatibility issues with quantized
|
||||
parameters, but it usually offers faster inference speed and, in some cases,
|
||||
lower memory usage. The at_runtime mode, on the other hand, is exactly the
|
||||
opposite.
|
||||
|
||||
Generation Options:
|
||||
-p, --prompt <string> the prompt to render
|
||||
-n, --negative-prompt <string> the negative prompt (default: "")
|
||||
-i, --init-img <string> path to the init image
|
||||
--end-img <string> path to the end image, required by flf2v
|
||||
--mask <string> path to the mask image
|
||||
--control-image <string> path to control image, control net
|
||||
--control-video <string> path to control video frames, It must be a directory path. The video frames
|
||||
inside should be stored as images in lexicographical (character) order. For
|
||||
example, if the control video path is `frames`, the directory contain images
|
||||
such as 00.png, 01.png, ... etc.
|
||||
--pm-id-images-dir <string> path to PHOTOMAKER input id images dir
|
||||
--pm-id-embed-path <string> path to PHOTOMAKER v2 id embed
|
||||
--hires-upscaler <string> highres fix upscaler, Lanczos, Nearest, Latent, Latent (nearest), Latent
|
||||
(nearest-exact), Latent (antialiased), Latent (bicubic), Latent (bicubic
|
||||
antialiased), or a model name under --hires-upscalers-dir (default: Latent)
|
||||
--extra-sample-args <string> extra sampler/scheduler/guidance args, key=value list. APG supports apg_eta,
|
||||
apg_momentum, apg_norm_threshold, apg_norm_threshold_smoothing; SLG supports
|
||||
slg_uncond; lcm supports noise_clip_std, noise_scale_start, noise_scale_end;
|
||||
ltx2 supports max_shift, base_shift, stretch, terminal; euler_ge supports gamma
|
||||
--extra-tiling-args <string> extra VAE tiling args, key=value list. LTX video VAE supports
|
||||
temporal_tile_frames (default: 4), temporal_tile_overlap (default: 1)
|
||||
-H, --height <int> image height, in pixel space (default: 512)
|
||||
-W, --width <int> image width, in pixel space (default: 512)
|
||||
--steps <int> number of sample steps (default: 20)
|
||||
--high-noise-steps <int> (high noise) number of sample steps (default: -1 = auto)
|
||||
--clip-skip <int> ignore last layers of CLIP network; 1 ignores none, 2 ignores one layer
|
||||
(default: -1). <= 0 represents unspecified, will be 1 for SD1.x, 2 for SD2.x
|
||||
-b, --batch-count <int> batch count
|
||||
--video-frames <int> video frames (default: 1)
|
||||
--fps <int> fps (default: 24)
|
||||
--timestep-shift <int> shift timestep for NitroFusion models (default: 0). recommended N for
|
||||
NitroSD-Realism around 250 and 500 for NitroSD-Vibrant
|
||||
--upscale-repeats <int> Run the ESRGAN upscaler this many times (default: 1)
|
||||
--upscale-tile-size <int> tile size for ESRGAN upscaling (default: 128)
|
||||
--hires-width <int> highres fix target width, 0 to use --hires-scale (default: 0)
|
||||
--hires-height <int> highres fix target height, 0 to use --hires-scale (default: 0)
|
||||
--hires-steps <int> highres fix second pass sample steps, 0 to reuse --steps (default: 0)
|
||||
--hires-upscale-tile-size <int> highres fix upscaler tile size, reserved for model-backed upscalers (default:
|
||||
128)
|
||||
--cfg-scale <float> unconditional guidance scale: (default: 7.0)
|
||||
--img-cfg-scale <float> image guidance scale for inpaint or image edit models: (default: same as
|
||||
--cfg-scale)
|
||||
--guidance <float> distilled guidance scale for models with guidance input (default: 3.5)
|
||||
--slg-scale <float> skip layer guidance (SLG) scale, only for DiT models: (default: 0). 0 means
|
||||
disabled, a value of 2.5 is nice for sd3.5 medium
|
||||
--skip-layer-start <float> SLG enabling point (default: 0.01)
|
||||
--skip-layer-end <float> SLG disabling point (default: 0.2)
|
||||
--eta <float> noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and
|
||||
res_2s; 1 for euler_a, er_sde and dpm++2s_a)
|
||||
--flow-shift <float> shift value for Flow models like SD3.x or WAN (default: auto)
|
||||
--high-noise-cfg-scale <float> (high noise) unconditional guidance scale: (default: 7.0)
|
||||
--high-noise-img-cfg-scale <float> (high noise) image guidance scale for inpaint or image edit models (default:
|
||||
same as --cfg-scale)
|
||||
--high-noise-guidance <float> (high noise) distilled guidance scale for models with guidance input
|
||||
(default: 3.5)
|
||||
--high-noise-slg-scale <float> (high noise) skip layer guidance (SLG) scale, only for DiT models: (default:
|
||||
0)
|
||||
--high-noise-skip-layer-start <float> (high noise) SLG enabling point (default: 0.01)
|
||||
--high-noise-skip-layer-end <float> (high noise) SLG disabling point (default: 0.2)
|
||||
--high-noise-eta <float> (high noise) noise multiplier (default: 0 for ddim_trailing, tcd,
|
||||
res_multistep and res_2s; 1 for euler_a, er_sde and dpm++2s_a)
|
||||
--strength <float> strength for noising/unnoising (default: 0.75)
|
||||
--pm-style-strength <float>
|
||||
--control-strength <float> strength to apply Control Net (default: 0.9). 1.0 corresponds to full
|
||||
destruction of information in init image
|
||||
--moe-boundary <float> timestep boundary for Wan2.2 MoE model. (default: 0.875). Only enabled if
|
||||
`--high-noise-steps` is set to -1
|
||||
--vace-strength <float> wan vace strength
|
||||
--vae-tile-overlap <float> tile overlap for vae tiling, in fraction of tile size (default: 0.5)
|
||||
--hires-scale <float> highres fix scale when target size is not set (default: 2.0)
|
||||
--hires-denoising-strength <float> highres fix second pass denoising strength (default: 0.7)
|
||||
--increase-ref-index automatically increase the indices of references images based on the order
|
||||
they are listed (starting with 1).
|
||||
--disable-auto-resize-ref-image disable auto resize of ref images
|
||||
--disable-image-metadata do not embed generation metadata on image files
|
||||
--vae-tiling process vae in tiles to reduce memory usage
|
||||
--temporal-tiling enable temporal tiling for LTX video VAE decode
|
||||
--hires enable highres fix
|
||||
-s, --seed RNG seed (default: 42, use random seed for < 0)
|
||||
--sampling-method sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m,
|
||||
dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep, res_2s,
|
||||
er_sde, euler_cfg_pp, euler_a_cfg_pp] (default: euler for Flux/SD3/Wan, euler_a otherwise)
|
||||
--high-noise-sampling-method (high noise) sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a,
|
||||
dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep,
|
||||
res_2s, er_sde, euler_cfg_pp, euler_a_cfg_pp] default: euler for Flux/SD3/Wan, euler_a otherwise
|
||||
--scheduler denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits,
|
||||
smoothstep, sgm_uniform, simple, kl_optimal, lcm, bong_tangent, ltx2], default:
|
||||
model-specific
|
||||
--sigmas custom sigma values for the sampler, comma-separated (e.g.,
|
||||
"14.61,7.8,3.5,0.0").
|
||||
--hires-sigmas custom sigma values for the highres fix second pass, comma-separated (e.g.,
|
||||
"0.85,0.725,0.421875,0.0").
|
||||
--skip-layers layers to skip for SLG steps (default: [7,8,9])
|
||||
--high-noise-skip-layers (high noise) layers to skip for SLG steps (default: [7,8,9])
|
||||
-r, --ref-image reference image for Flux Kontext models (can be used multiple times)
|
||||
--cache-mode caching method: 'easycache' (DiT), 'ucache' (UNET),
|
||||
'dbcache'/'taylorseer'/'cache-dit' (DiT block-level), 'spectrum' (UNET/DiT
|
||||
Chebyshev+Taylor forecasting)
|
||||
--cache-option named cache params (key=value format, comma-separated). easycache/ucache:
|
||||
threshold=,start=,end=,decay=,relative=,reset=; dbcache/taylorseer/cache-dit:
|
||||
Fn=,Bn=,threshold=,warmup=; spectrum: w=,m=,lam=,window=,flex=,warmup=,stop=.
|
||||
Examples: "threshold=0.25" or "threshold=1.5,reset=0"
|
||||
--scm-mask SCM steps mask for cache-dit: comma-separated 0/1 (e.g.,
|
||||
"1,1,1,0,0,1,0,0,1,0") - 1=compute, 0=can cache
|
||||
--scm-policy SCM policy: 'dynamic' (default) or 'static'
|
||||
--vae-tile-size tile size for vae tiling, format [X]x[Y] (default: 32x32)
|
||||
--vae-relative-tile-size relative tile size for vae tiling, format [X]x[Y], in fraction of image size
|
||||
if < 1, in number of tiles per dim if >=1 (overrides --vae-tile-size)
|
||||
```bash
|
||||
./bin/sd-cli -h
|
||||
```
|
||||
|
||||
Metadata mode inspects PNG/JPEG container metadata without loading any model:
|
||||
|
||||
+26
-25
@@ -62,18 +62,22 @@ struct SDCliParams {
|
||||
{"-o",
|
||||
"--output",
|
||||
"path to write result image to. you can use printf-style %d format specifiers for image sequences (default: ./output.png) (eg. output_%03d.png). Single-file video outputs support .avi, .webm, and animated .webp",
|
||||
0,
|
||||
&output_path},
|
||||
{"",
|
||||
"--image",
|
||||
"path to the image to inspect (for metadata mode)",
|
||||
0,
|
||||
&image_path},
|
||||
{"",
|
||||
"--metadata-format",
|
||||
"metadata output format, one of [text, json] (default: text)",
|
||||
0,
|
||||
&metadata_format},
|
||||
{"",
|
||||
"--preview-path",
|
||||
"path to write preview image to (default: ./preview.png). Multi-frame previews support .avi, .webm, and animated .webp",
|
||||
0,
|
||||
&preview_path},
|
||||
};
|
||||
|
||||
@@ -623,8 +627,6 @@ int main(int argc, const char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
bool vae_decode_only = true;
|
||||
|
||||
auto load_image_and_update_size = [&](const std::string& path,
|
||||
SDImageOwner& image,
|
||||
bool resize_image = true,
|
||||
@@ -646,21 +648,18 @@ int main(int argc, const char* argv[]) {
|
||||
};
|
||||
|
||||
if (gen_params.init_image_path.size() > 0) {
|
||||
vae_decode_only = false;
|
||||
if (!load_image_and_update_size(gen_params.init_image_path, gen_params.init_image)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (gen_params.end_image_path.size() > 0) {
|
||||
vae_decode_only = false;
|
||||
if (!load_image_and_update_size(gen_params.end_image_path, gen_params.end_image)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (gen_params.ref_image_paths.size() > 0) {
|
||||
vae_decode_only = false;
|
||||
gen_params.ref_images.clear();
|
||||
for (auto& path : gen_params.ref_image_paths) {
|
||||
SDImageOwner ref_image({0, 0, 3, nullptr});
|
||||
@@ -735,18 +734,7 @@ int main(int argc, const char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if (cli_params.mode == VID_GEN) {
|
||||
vae_decode_only = false;
|
||||
}
|
||||
|
||||
if (gen_params.hires_enabled &&
|
||||
(gen_params.resolved_hires_upscaler == SD_HIRES_UPSCALER_MODEL ||
|
||||
gen_params.resolved_hires_upscaler == SD_HIRES_UPSCALER_LANCZOS ||
|
||||
gen_params.resolved_hires_upscaler == SD_HIRES_UPSCALER_NEAREST)) {
|
||||
vae_decode_only = false;
|
||||
}
|
||||
|
||||
sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(vae_decode_only, true, cli_params.taesd_preview);
|
||||
sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(cli_params.taesd_preview);
|
||||
|
||||
SDImageVec results;
|
||||
int num_results = 0;
|
||||
@@ -778,8 +766,12 @@ int main(int argc, const char* argv[]) {
|
||||
if (cli_params.mode == IMG_GEN) {
|
||||
sd_img_gen_params_t img_gen_params = gen_params.to_sd_img_gen_params_t();
|
||||
|
||||
num_results = gen_params.batch_count;
|
||||
results.adopt(generate_image(sd_ctx.get(), &img_gen_params), num_results);
|
||||
sd_image_t* generated_images = nullptr;
|
||||
if (!generate_image(sd_ctx.get(), &img_gen_params, &generated_images, &num_results)) {
|
||||
generated_images = nullptr;
|
||||
num_results = 0;
|
||||
}
|
||||
results.adopt(generated_images, num_results);
|
||||
} else if (cli_params.mode == VID_GEN) {
|
||||
sd_vid_gen_params_t vid_gen_params = gen_params.to_sd_vid_gen_params_t();
|
||||
sd_image_t* generated_video = nullptr;
|
||||
@@ -798,12 +790,11 @@ int main(int argc, const char* argv[]) {
|
||||
int upscale_factor = 4; // unused for RealESRGAN_x4plus_anime_6B.pth
|
||||
if (ctx_params.esrgan_path.size() > 0 && gen_params.upscale_repeats > 0) {
|
||||
UpscalerCtxPtr upscaler_ctx(new_upscaler_ctx(ctx_params.esrgan_path.c_str(),
|
||||
ctx_params.offload_params_to_cpu,
|
||||
ctx_params.diffusion_conv_direct,
|
||||
ctx_params.n_threads,
|
||||
gen_params.upscale_tile_size,
|
||||
ctx_params.backend.c_str(),
|
||||
ctx_params.params_backend.c_str()));
|
||||
sd_ctx_params.backend,
|
||||
sd_ctx_params.params_backend));
|
||||
|
||||
if (upscaler_ctx == nullptr) {
|
||||
LOG_ERROR("new_upscaler_ctx failed");
|
||||
@@ -815,12 +806,22 @@ int main(int argc, const char* argv[]) {
|
||||
SDImageOwner current_image(results[i]);
|
||||
results[i] = {0, 0, 0, nullptr};
|
||||
for (int u = 0; u < gen_params.upscale_repeats; ++u) {
|
||||
SDImageOwner upscaled_image(upscale(upscaler_ctx.get(), current_image.get(), upscale_factor));
|
||||
if (upscaled_image.get().data == nullptr) {
|
||||
sd_image_t* upscaled_images = nullptr;
|
||||
int upscaled_count = 0;
|
||||
bool upscale_ok = upscale(upscaler_ctx.get(),
|
||||
current_image.get(),
|
||||
upscale_factor,
|
||||
&upscaled_images,
|
||||
&upscaled_count);
|
||||
if (!upscale_ok || upscaled_count <= 0 || upscaled_images[0].data == nullptr) {
|
||||
free_sd_images(upscaled_images, upscaled_count);
|
||||
LOG_ERROR("upscale failed");
|
||||
break;
|
||||
}
|
||||
current_image = std::move(upscaled_image);
|
||||
sd_image_t upscaled_image = upscaled_images[0];
|
||||
upscaled_images[0] = {0, 0, 0, nullptr};
|
||||
free_sd_images(upscaled_images, upscaled_count);
|
||||
current_image.reset(upscaled_image);
|
||||
}
|
||||
results[i] = current_image.release(); // Set the final upscaled image as the result
|
||||
}
|
||||
|
||||
+211
-70
@@ -6,6 +6,7 @@
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <regex>
|
||||
@@ -51,6 +52,10 @@ static sd_vae_format_t str_to_vae_format(const std::string& value) {
|
||||
return SD_VAE_FORMAT_COUNT;
|
||||
}
|
||||
|
||||
static void prepend_backend_assignment(std::string& spec, const char* assignment) {
|
||||
spec = spec.empty() ? assignment : std::string(assignment) + "," + spec;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
static std::string utf16_to_utf8(const std::wstring& wstr) {
|
||||
if (wstr.empty())
|
||||
@@ -256,8 +261,15 @@ bool parse_options(int argc, const char** argv, const std::vector<ArgOptions>& o
|
||||
invalid_arg = true;
|
||||
return;
|
||||
}
|
||||
*option.target = argv_to_utf8(i, argv);
|
||||
found_arg = true;
|
||||
if (option.concat && !option.target->empty()) {
|
||||
if (option.concat > 0 && option.concat <= 0xff) {
|
||||
*option.target += static_cast<char>(option.concat);
|
||||
}
|
||||
*option.target += argv_to_utf8(i, argv);
|
||||
} else {
|
||||
*option.target = argv_to_utf8(i, argv);
|
||||
}
|
||||
found_arg = true;
|
||||
}))
|
||||
break;
|
||||
|
||||
@@ -320,109 +332,152 @@ ArgOptions SDContextParams::get_options() {
|
||||
{"-m",
|
||||
"--model",
|
||||
"path to full model",
|
||||
0,
|
||||
&model_path},
|
||||
{"",
|
||||
"--clip_l",
|
||||
"path to the clip-l text encoder", &clip_l_path},
|
||||
"path to the clip-l text encoder",
|
||||
0,
|
||||
&clip_l_path},
|
||||
{"", "--clip_g",
|
||||
"path to the clip-g text encoder",
|
||||
0,
|
||||
&clip_g_path},
|
||||
{"",
|
||||
"--clip_vision",
|
||||
"path to the clip-vision encoder",
|
||||
0,
|
||||
&clip_vision_path},
|
||||
{"",
|
||||
"--t5xxl",
|
||||
"path to the t5xxl text encoder",
|
||||
0,
|
||||
&t5xxl_path},
|
||||
{"",
|
||||
"--llm",
|
||||
"path to the llm text encoder. For example: (qwenvl2.5 for qwen-image, mistral-small3.2 for flux2, ...)",
|
||||
0,
|
||||
&llm_path},
|
||||
{"",
|
||||
"--llm_vision",
|
||||
"path to the llm vit",
|
||||
0,
|
||||
&llm_vision_path},
|
||||
{"",
|
||||
"--qwen2vl",
|
||||
"alias of --llm. Deprecated.",
|
||||
0,
|
||||
&llm_path},
|
||||
{"",
|
||||
"--qwen2vl_vision",
|
||||
"alias of --llm_vision. Deprecated.",
|
||||
0,
|
||||
&llm_vision_path},
|
||||
{"",
|
||||
"--diffusion-model",
|
||||
"path to the standalone diffusion model",
|
||||
0,
|
||||
&diffusion_model_path},
|
||||
{"",
|
||||
"--high-noise-diffusion-model",
|
||||
"path to the standalone high noise diffusion model",
|
||||
0,
|
||||
&high_noise_diffusion_model_path},
|
||||
{"",
|
||||
"--uncond-diffusion-model",
|
||||
"path to the standalone unconditional diffusion model, currently used by Ideogram4 CFG",
|
||||
0,
|
||||
&uncond_diffusion_model_path},
|
||||
{"",
|
||||
"--embeddings-connectors",
|
||||
"path to LTXAV embeddings connectors",
|
||||
0,
|
||||
&embeddings_connectors_path},
|
||||
{"",
|
||||
"--vae",
|
||||
"path to standalone vae model",
|
||||
0,
|
||||
&vae_path},
|
||||
{"",
|
||||
"--vae-format",
|
||||
"VAE latent format override: auto, flux, sd3, or flux2 (default: auto)",
|
||||
0,
|
||||
&vae_format},
|
||||
{"",
|
||||
"--audio-vae",
|
||||
"path to standalone LTX audio vae model",
|
||||
0,
|
||||
&audio_vae_path},
|
||||
{"",
|
||||
"--taesd",
|
||||
"path to taesd. Using Tiny AutoEncoder for fast decoding (low quality)",
|
||||
0,
|
||||
&taesd_path},
|
||||
{"",
|
||||
"--tae",
|
||||
"alias of --taesd",
|
||||
0,
|
||||
&taesd_path},
|
||||
{"",
|
||||
"--control-net",
|
||||
"path to control net model",
|
||||
0,
|
||||
&control_net_path},
|
||||
{"",
|
||||
"--embd-dir",
|
||||
"embeddings directory",
|
||||
0,
|
||||
&embedding_dir},
|
||||
{"",
|
||||
"--lora-model-dir",
|
||||
"lora model directory",
|
||||
0,
|
||||
&lora_model_dir},
|
||||
{"",
|
||||
"--hires-upscalers-dir",
|
||||
"highres fix upscaler model directory",
|
||||
0,
|
||||
&hires_upscalers_dir},
|
||||
{"",
|
||||
"--tensor-type-rules",
|
||||
"weight type per tensor pattern (example: \"^vae\\.=f16,model\\.=q8_0\")",
|
||||
(int)',',
|
||||
&tensor_type_rules},
|
||||
{"",
|
||||
"--photo-maker",
|
||||
"path to PHOTOMAKER model",
|
||||
0,
|
||||
&photo_maker_path},
|
||||
{"",
|
||||
"--pulid-weights",
|
||||
"path to PuLID Flux weights",
|
||||
0,
|
||||
&pulid_weights_path},
|
||||
{"",
|
||||
"--upscale-model",
|
||||
"path to esrgan model.",
|
||||
0,
|
||||
&esrgan_path},
|
||||
{"",
|
||||
"--backend",
|
||||
"runtime backend assignment, e.g. cpu or clip=cpu,vae=cuda0,diffusion=vulkan0",
|
||||
(int)',',
|
||||
&backend},
|
||||
{"",
|
||||
"--params-backend",
|
||||
"parameter backend assignment, e.g. cpu or diffusion=cpu,clip=cpu",
|
||||
"parameter backend assignment, e.g. disk, cpu, or diffusion=disk,clip=cpu",
|
||||
(int)',',
|
||||
¶ms_backend},
|
||||
{"",
|
||||
"--rpc-servers",
|
||||
"comma-separated list of RPC servers to connect to for offloading, in the format host:port, e.g. localhost:50052,192.168.1.3:50052",
|
||||
(int)',',
|
||||
&rpc_servers},
|
||||
{"",
|
||||
"--max-vram",
|
||||
"maximum VRAM budget in GiB for graph-cut segmented execution. Accepts a single value or assignments by backend/device, e.g. 6 or cuda0=6,vulkan0=4. 0 disables graph splitting; a negative value auto-detects free VRAM, sparing the specified value",
|
||||
0,
|
||||
&max_vram},
|
||||
};
|
||||
|
||||
options.int_options = {
|
||||
@@ -437,18 +492,15 @@ ArgOptions SDContextParams::get_options() {
|
||||
&chroma_t5_mask_pad},
|
||||
};
|
||||
|
||||
options.float_options = {
|
||||
{"",
|
||||
"--max-vram",
|
||||
"maximum VRAM budget in GiB for graph-cut segmented execution. 0 disables graph splitting; a negative value auto-detects free VRAM, sparing the specified value (e.g. -0.5 will keep at least 0.5 GiB free)",
|
||||
&max_vram},
|
||||
};
|
||||
|
||||
options.bool_options = {
|
||||
{"",
|
||||
"--stream-layers",
|
||||
"enable residency+prefetch streaming on top of --max-vram (no effect without --max-vram; defaults to false)",
|
||||
true, &stream_layers},
|
||||
{"",
|
||||
"--eager-load",
|
||||
"load all params into the params backend at model-load time instead of lazily on first use (defaults to false)",
|
||||
true, &eager_load},
|
||||
{"",
|
||||
"--force-sdxl-vae-conv-scale",
|
||||
"force use of conv scale on sdxl vae",
|
||||
@@ -463,15 +515,15 @@ ArgOptions SDContextParams::get_options() {
|
||||
true, &enable_mmap},
|
||||
{"",
|
||||
"--control-net-cpu",
|
||||
"keep controlnet in cpu (for low vram)",
|
||||
"deprecated; use --backend controlnet=cpu",
|
||||
true, &control_net_cpu},
|
||||
{"",
|
||||
"--clip-on-cpu",
|
||||
"keep clip in cpu (for low vram)",
|
||||
"deprecated; use --backend te=cpu",
|
||||
true, &clip_on_cpu},
|
||||
{"",
|
||||
"--vae-on-cpu",
|
||||
"keep vae in cpu (for low vram)",
|
||||
"deprecated; use --backend vae=cpu",
|
||||
true, &vae_on_cpu},
|
||||
{"",
|
||||
"--fa",
|
||||
@@ -601,7 +653,7 @@ ArgOptions SDContextParams::get_options() {
|
||||
on_sampler_rng_arg},
|
||||
{"",
|
||||
"--prediction",
|
||||
"prediction type override, one of [eps, v, edm_v, sd3_flow, flux_flow, flux2_flow]",
|
||||
"prediction type override, one of [eps, v, edm_v, sd3_flow, flux_flow, sefi_flow]",
|
||||
on_prediction_arg},
|
||||
{"",
|
||||
"--lora-apply-mode",
|
||||
@@ -688,6 +740,25 @@ bool SDContextParams::resolve_and_validate(SDMode mode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDContextParams::prepare_backend_assignments() {
|
||||
effective_backend = backend;
|
||||
effective_params_backend = params_backend;
|
||||
|
||||
if (offload_params_to_cpu) {
|
||||
prepend_backend_assignment(effective_params_backend, "*=cpu");
|
||||
}
|
||||
|
||||
if (clip_on_cpu) {
|
||||
prepend_backend_assignment(effective_backend, "te=cpu");
|
||||
}
|
||||
if (vae_on_cpu) {
|
||||
prepend_backend_assignment(effective_backend, "vae=cpu");
|
||||
}
|
||||
if (control_net_cpu) {
|
||||
prepend_backend_assignment(effective_backend, "controlnet=cpu");
|
||||
}
|
||||
}
|
||||
|
||||
std::string SDContextParams::to_string() const {
|
||||
std::ostringstream emb_ss;
|
||||
emb_ss << "{\n";
|
||||
@@ -731,8 +802,9 @@ std::string SDContextParams::to_string() const {
|
||||
<< " rng_type: " << sd_rng_type_name(rng_type) << ",\n"
|
||||
<< " sampler_rng_type: " << sd_rng_type_name(sampler_rng_type) << ",\n"
|
||||
<< " offload_params_to_cpu: " << (offload_params_to_cpu ? "true" : "false") << ",\n"
|
||||
<< " max_vram: " << max_vram << ",\n"
|
||||
<< " max_vram: \"" << max_vram << "\",\n"
|
||||
<< " stream_layers: " << (stream_layers ? "true" : "false") << ",\n"
|
||||
<< " eager_load: " << (eager_load ? "true" : "false") << ",\n"
|
||||
<< " backend: \"" << backend << "\",\n"
|
||||
<< " params_backend: \"" << params_backend << "\",\n"
|
||||
<< " enable_mmap: " << (enable_mmap ? "true" : "false") << ",\n"
|
||||
@@ -757,7 +829,8 @@ std::string SDContextParams::to_string() const {
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
sd_ctx_params_t SDContextParams::to_sd_ctx_params_t(bool vae_decode_only, bool free_params_immediately, bool taesd_preview) {
|
||||
sd_ctx_params_t SDContextParams::to_sd_ctx_params_t(bool taesd_preview) {
|
||||
prepare_backend_assignments();
|
||||
embedding_vec.clear();
|
||||
embedding_vec.reserve(embedding_map.size());
|
||||
for (const auto& kv : embedding_map) {
|
||||
@@ -767,57 +840,54 @@ sd_ctx_params_t SDContextParams::to_sd_ctx_params_t(bool vae_decode_only, bool f
|
||||
embedding_vec.emplace_back(item);
|
||||
}
|
||||
|
||||
sd_ctx_params_t sd_ctx_params = {
|
||||
model_path.c_str(),
|
||||
clip_l_path.c_str(),
|
||||
clip_g_path.c_str(),
|
||||
clip_vision_path.c_str(),
|
||||
t5xxl_path.c_str(),
|
||||
llm_path.c_str(),
|
||||
llm_vision_path.c_str(),
|
||||
diffusion_model_path.c_str(),
|
||||
high_noise_diffusion_model_path.c_str(),
|
||||
uncond_diffusion_model_path.c_str(),
|
||||
embeddings_connectors_path.c_str(),
|
||||
vae_path.c_str(),
|
||||
audio_vae_path.c_str(),
|
||||
taesd_path.c_str(),
|
||||
control_net_path.c_str(),
|
||||
embedding_vec.data(),
|
||||
static_cast<uint32_t>(embedding_vec.size()),
|
||||
photo_maker_path.c_str(),
|
||||
tensor_type_rules.c_str(),
|
||||
vae_decode_only,
|
||||
free_params_immediately,
|
||||
n_threads,
|
||||
wtype,
|
||||
rng_type,
|
||||
sampler_rng_type,
|
||||
prediction,
|
||||
lora_apply_mode,
|
||||
offload_params_to_cpu,
|
||||
enable_mmap,
|
||||
clip_on_cpu,
|
||||
control_net_cpu,
|
||||
vae_on_cpu,
|
||||
flash_attn,
|
||||
diffusion_flash_attn,
|
||||
taesd_preview,
|
||||
diffusion_conv_direct,
|
||||
vae_conv_direct,
|
||||
circular || circular_x,
|
||||
circular || circular_y,
|
||||
force_sdxl_vae_conv_scale,
|
||||
chroma_use_dit_mask,
|
||||
chroma_use_t5_mask,
|
||||
chroma_t5_mask_pad,
|
||||
qwen_image_zero_cond_t,
|
||||
str_to_vae_format(vae_format),
|
||||
max_vram,
|
||||
stream_layers,
|
||||
backend.c_str(),
|
||||
params_backend.c_str(),
|
||||
};
|
||||
sd_ctx_params_t sd_ctx_params;
|
||||
sd_ctx_params_init(&sd_ctx_params);
|
||||
sd_ctx_params.model_path = model_path.c_str();
|
||||
sd_ctx_params.clip_l_path = clip_l_path.c_str();
|
||||
sd_ctx_params.clip_g_path = clip_g_path.c_str();
|
||||
sd_ctx_params.clip_vision_path = clip_vision_path.c_str();
|
||||
sd_ctx_params.t5xxl_path = t5xxl_path.c_str();
|
||||
sd_ctx_params.llm_path = llm_path.c_str();
|
||||
sd_ctx_params.llm_vision_path = llm_vision_path.c_str();
|
||||
sd_ctx_params.diffusion_model_path = diffusion_model_path.c_str();
|
||||
sd_ctx_params.high_noise_diffusion_model_path = high_noise_diffusion_model_path.c_str();
|
||||
sd_ctx_params.uncond_diffusion_model_path = uncond_diffusion_model_path.c_str();
|
||||
sd_ctx_params.embeddings_connectors_path = embeddings_connectors_path.c_str();
|
||||
sd_ctx_params.vae_path = vae_path.c_str();
|
||||
sd_ctx_params.audio_vae_path = audio_vae_path.c_str();
|
||||
sd_ctx_params.taesd_path = taesd_path.c_str();
|
||||
sd_ctx_params.control_net_path = control_net_path.c_str();
|
||||
sd_ctx_params.embeddings = embedding_vec.data();
|
||||
sd_ctx_params.embedding_count = static_cast<uint32_t>(embedding_vec.size());
|
||||
sd_ctx_params.photo_maker_path = photo_maker_path.c_str();
|
||||
sd_ctx_params.pulid_weights_path = pulid_weights_path.c_str();
|
||||
sd_ctx_params.tensor_type_rules = tensor_type_rules.c_str();
|
||||
sd_ctx_params.n_threads = n_threads;
|
||||
sd_ctx_params.wtype = wtype;
|
||||
sd_ctx_params.rng_type = rng_type;
|
||||
sd_ctx_params.sampler_rng_type = sampler_rng_type;
|
||||
sd_ctx_params.prediction = prediction;
|
||||
sd_ctx_params.lora_apply_mode = lora_apply_mode;
|
||||
sd_ctx_params.enable_mmap = enable_mmap;
|
||||
sd_ctx_params.flash_attn = flash_attn;
|
||||
sd_ctx_params.diffusion_flash_attn = diffusion_flash_attn;
|
||||
sd_ctx_params.tae_preview_only = taesd_preview;
|
||||
sd_ctx_params.diffusion_conv_direct = diffusion_conv_direct;
|
||||
sd_ctx_params.vae_conv_direct = vae_conv_direct;
|
||||
sd_ctx_params.circular_x = circular || circular_x;
|
||||
sd_ctx_params.circular_y = circular || circular_y;
|
||||
sd_ctx_params.force_sdxl_vae_conv_scale = force_sdxl_vae_conv_scale;
|
||||
sd_ctx_params.chroma_use_dit_mask = chroma_use_dit_mask;
|
||||
sd_ctx_params.chroma_use_t5_mask = chroma_use_t5_mask;
|
||||
sd_ctx_params.chroma_t5_mask_pad = chroma_t5_mask_pad;
|
||||
sd_ctx_params.qwen_image_zero_cond_t = qwen_image_zero_cond_t;
|
||||
sd_ctx_params.vae_format = str_to_vae_format(vae_format);
|
||||
sd_ctx_params.max_vram = max_vram.c_str();
|
||||
sd_ctx_params.stream_layers = stream_layers;
|
||||
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();
|
||||
sd_ctx_params.rpc_servers = rpc_servers.c_str();
|
||||
return sd_ctx_params;
|
||||
}
|
||||
|
||||
@@ -832,54 +902,71 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
{"-p",
|
||||
"--prompt",
|
||||
"the prompt to render",
|
||||
0,
|
||||
&prompt},
|
||||
{"-n",
|
||||
"--negative-prompt",
|
||||
"the negative prompt (default: \"\")",
|
||||
0,
|
||||
&negative_prompt},
|
||||
{"-i",
|
||||
"--init-img",
|
||||
"path to the init image",
|
||||
0,
|
||||
&init_image_path},
|
||||
{"",
|
||||
"--end-img",
|
||||
"path to the end image, required by flf2v",
|
||||
0,
|
||||
&end_image_path},
|
||||
{"",
|
||||
"--mask",
|
||||
"path to the mask image",
|
||||
0,
|
||||
&mask_image_path},
|
||||
{"",
|
||||
"--control-image",
|
||||
"path to control image, control net",
|
||||
0,
|
||||
&control_image_path},
|
||||
{"",
|
||||
"--control-video",
|
||||
"path to control video frames, It must be a directory path. The video frames inside should be stored as images in "
|
||||
"lexicographical (character) order. For example, if the control video path is `frames`, the directory contain images "
|
||||
"such as 00.png, 01.png, ... etc.",
|
||||
0,
|
||||
&control_video_path},
|
||||
{"",
|
||||
"--pm-id-images-dir",
|
||||
"path to PHOTOMAKER input id images dir",
|
||||
0,
|
||||
&pm_id_images_dir},
|
||||
{"",
|
||||
"--pm-id-embed-path",
|
||||
"path to PHOTOMAKER v2 id embed",
|
||||
0,
|
||||
&pm_id_embed_path},
|
||||
{"",
|
||||
"--pulid-id-embedding",
|
||||
"path to PuLID id embedding",
|
||||
0,
|
||||
&pulid_id_embedding_path},
|
||||
{"",
|
||||
"--hires-upscaler",
|
||||
"highres fix upscaler, Lanczos, Nearest, Latent, Latent (nearest), Latent (nearest-exact), "
|
||||
"Latent (antialiased), Latent (bicubic), Latent (bicubic antialiased), or a model name "
|
||||
"under --hires-upscalers-dir (default: Latent)",
|
||||
0,
|
||||
&hires_upscaler},
|
||||
{"",
|
||||
"--extra-sample-args",
|
||||
"extra sampler/scheduler/guidance args, key=value list. APG supports apg_eta, apg_momentum, apg_norm_threshold, apg_norm_threshold_smoothing; SLG supports slg_uncond; lcm supports noise_clip_std, noise_scale_start, noise_scale_end; ltx2 supports max_shift, base_shift, stretch, terminal; euler_ge supports gamma",
|
||||
"extra sampler/scheduler/guidance args, key=value list. CFG supports guidance_schedule; APG supports apg_eta, apg_momentum, apg_norm_threshold, apg_norm_threshold_smoothing; SLG supports slg_uncond; lcm supports noise_clip_std, noise_scale_start, noise_scale_end; flux supports base_shift, max_shift; ltx2 supports max_shift, base_shift, stretch, terminal; euler_ge supports gamma;; logit_normal supports mu, std, logsnr_min, logsnr_max, resolution_aware",
|
||||
(int)',',
|
||||
&extra_sample_args},
|
||||
{"",
|
||||
"--extra-tiling-args",
|
||||
"extra VAE tiling args, key=value list. LTX video VAE supports temporal_tile_frames (default: 4), temporal_tile_overlap (default: 1)",
|
||||
(int)',',
|
||||
&extra_tiling_args},
|
||||
};
|
||||
|
||||
@@ -1017,6 +1104,10 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
"--pm-style-strength",
|
||||
"",
|
||||
&pm_style_strength},
|
||||
{"",
|
||||
"--pulid-id-weight",
|
||||
"strength of PuLID identity injection",
|
||||
&pulid_id_weight},
|
||||
{"",
|
||||
"--control-strength",
|
||||
"strength to apply Control Net (default: 0.9). 1.0 corresponds to full destruction of information in init image",
|
||||
@@ -1331,6 +1422,42 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
return 1;
|
||||
};
|
||||
|
||||
auto on_prompt_file_arg = [&](int argc, const char** argv, int index) {
|
||||
if (++index >= argc) {
|
||||
return -1;
|
||||
}
|
||||
const char* arg = argv[index];
|
||||
std::ifstream f(arg, std::ios::binary);
|
||||
try {
|
||||
prompt = std::string(std::istreambuf_iterator<char>{f}, {});
|
||||
} catch (const std::ios_base::failure&) {
|
||||
f.setstate(std::ios_base::failbit);
|
||||
}
|
||||
if (f.fail()) {
|
||||
LOG_ERROR("error: failed to read prompt file '%s'\n", arg);
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
|
||||
auto on_negative_prompt_file_arg = [&](int argc, const char** argv, int index) {
|
||||
if (++index >= argc) {
|
||||
return -1;
|
||||
}
|
||||
const char* arg = argv[index];
|
||||
std::ifstream f(arg, std::ios::binary);
|
||||
try {
|
||||
negative_prompt = std::string(std::istreambuf_iterator<char>{f}, {});
|
||||
} catch (const std::ios_base::failure&) {
|
||||
f.setstate(std::ios_base::failbit);
|
||||
}
|
||||
if (f.fail()) {
|
||||
LOG_ERROR("error: failed to read negative prompt file '%s'\n", arg);
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
|
||||
options.manual_options = {
|
||||
{"-s",
|
||||
"--seed",
|
||||
@@ -1348,7 +1475,7 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
on_high_noise_sample_method_arg},
|
||||
{"",
|
||||
"--scheduler",
|
||||
"denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits, smoothstep, sgm_uniform, simple, kl_optimal, lcm, bong_tangent, ltx2], default: model-specific",
|
||||
"denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits, smoothstep, sgm_uniform, simple, kl_optimal, lcm, bong_tangent, ltx2, logit_normal, flux2, flux, beta], alias: normal=discrete, default: model-specific",
|
||||
on_scheduler_arg},
|
||||
{"",
|
||||
"--sigmas",
|
||||
@@ -1394,6 +1521,14 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
"--vae-relative-tile-size",
|
||||
"relative tile size for vae tiling, format [X]x[Y], in fraction of image size if < 1, in number of tiles per dim if >=1 (overrides --vae-tile-size)",
|
||||
on_relative_tile_size_arg},
|
||||
{"",
|
||||
"--prompt-file",
|
||||
"path to the file containing the prompt to render",
|
||||
on_prompt_file_arg},
|
||||
{"",
|
||||
"--negative-prompt-file",
|
||||
"path to the file containing the negative prompt",
|
||||
on_negative_prompt_file_arg},
|
||||
|
||||
};
|
||||
|
||||
@@ -2249,6 +2384,11 @@ sd_img_gen_params_t SDGenerationParams::to_sd_img_gen_params_t() {
|
||||
pm_style_strength,
|
||||
};
|
||||
|
||||
sd_pulid_params_t pulid_params = {
|
||||
pulid_id_embedding_path.empty() ? nullptr : pulid_id_embedding_path.c_str(),
|
||||
pulid_id_weight,
|
||||
};
|
||||
|
||||
params.loras = lora_vec.empty() ? nullptr : lora_vec.data();
|
||||
params.lora_count = static_cast<uint32_t>(lora_vec.size());
|
||||
params.prompt = prompt.c_str();
|
||||
@@ -2269,6 +2409,7 @@ sd_img_gen_params_t SDGenerationParams::to_sd_img_gen_params_t() {
|
||||
params.control_image = control_image.get();
|
||||
params.control_strength = control_strength;
|
||||
params.pm_params = pm_params;
|
||||
params.pulid_params = pulid_params;
|
||||
params.vae_tiling_params = vae_tiling_params;
|
||||
params.cache = cache_params;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ struct StringOption {
|
||||
std::string short_name;
|
||||
std::string long_name;
|
||||
std::string desc;
|
||||
int concat;
|
||||
std::string* target;
|
||||
};
|
||||
|
||||
@@ -133,6 +134,7 @@ struct SDContextParams {
|
||||
std::string control_net_path;
|
||||
std::string embedding_dir;
|
||||
std::string photo_maker_path;
|
||||
std::string pulid_weights_path;
|
||||
sd_type_t wtype = SD_TYPE_COUNT;
|
||||
std::string tensor_type_rules;
|
||||
std::string lora_model_dir = ".";
|
||||
@@ -144,10 +146,14 @@ struct SDContextParams {
|
||||
rng_type_t rng_type = CUDA_RNG;
|
||||
rng_type_t sampler_rng_type = RNG_TYPE_COUNT;
|
||||
bool offload_params_to_cpu = false;
|
||||
float max_vram = 0.f;
|
||||
std::string max_vram = "0";
|
||||
bool stream_layers = false;
|
||||
bool eager_load = false;
|
||||
std::string backend;
|
||||
std::string params_backend;
|
||||
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;
|
||||
@@ -175,11 +181,12 @@ struct SDContextParams {
|
||||
float flow_shift = INFINITY;
|
||||
ArgOptions get_options();
|
||||
void build_embedding_map();
|
||||
void prepare_backend_assignments();
|
||||
bool resolve(SDMode mode);
|
||||
bool validate(SDMode mode);
|
||||
bool resolve_and_validate(SDMode mode);
|
||||
std::string to_string() const;
|
||||
sd_ctx_params_t to_sd_ctx_params_t(bool vae_decode_only, bool free_params_immediately, bool taesd_preview);
|
||||
sd_ctx_params_t to_sd_ctx_params_t(bool taesd_preview);
|
||||
};
|
||||
|
||||
struct SDGenerationParams {
|
||||
@@ -230,6 +237,9 @@ struct SDGenerationParams {
|
||||
std::string pm_id_embed_path;
|
||||
float pm_style_strength = 20.f;
|
||||
|
||||
std::string pulid_id_embedding_path;
|
||||
float pulid_id_weight = 1.0f;
|
||||
|
||||
int upscale_repeats = 1;
|
||||
int upscale_tile_size = 128;
|
||||
|
||||
|
||||
+4
-182
@@ -117,188 +117,10 @@ In this case, the server will load and serve the specified `index.html` file ins
|
||||
* using a custom UI
|
||||
* avoiding rebuilding the binary after frontend modifications
|
||||
|
||||
# Run
|
||||
# Usage
|
||||
|
||||
```
|
||||
usage: ./bin/sd-server [options]
|
||||
For detailed command-line arguments, run:
|
||||
|
||||
Svr Options:
|
||||
-l, --listen-ip <string> server listen ip (default: 127.0.0.1)
|
||||
--serve-html-path <string> path to HTML file to serve at root (optional)
|
||||
--listen-port <int> server listen port (default: 1234)
|
||||
-v, --verbose print extra info
|
||||
--color colors the logging tags according to level
|
||||
-h, --help show this help message and exit
|
||||
|
||||
Context Options:
|
||||
-m, --model <string> path to full model
|
||||
--clip_l <string> path to the clip-l text encoder
|
||||
--clip_g <string> path to the clip-g text encoder
|
||||
--clip_vision <string> path to the clip-vision encoder
|
||||
--t5xxl <string> path to the t5xxl text encoder
|
||||
--llm <string> path to the llm text encoder. For example: (qwenvl2.5 for qwen-image,
|
||||
mistral-small3.2 for flux2, ...)
|
||||
--llm_vision <string> path to the llm vit
|
||||
--qwen2vl <string> alias of --llm. Deprecated.
|
||||
--qwen2vl_vision <string> alias of --llm_vision. Deprecated.
|
||||
--diffusion-model <string> path to the standalone diffusion model
|
||||
--high-noise-diffusion-model <string> path to the standalone high noise diffusion model
|
||||
--uncond-diffusion-model <string> path to the standalone unconditional diffusion model, currently used by
|
||||
Ideogram4 CFG
|
||||
--vae <string> path to standalone vae model
|
||||
--taesd <string> path to taesd. Using Tiny AutoEncoder for fast decoding (low quality)
|
||||
--tae <string> alias of --taesd
|
||||
--control-net <string> path to control net model
|
||||
--embd-dir <string> embeddings directory
|
||||
--lora-model-dir <string> lora model directory
|
||||
--hires-upscalers-dir <string> highres fix upscaler model directory
|
||||
--tensor-type-rules <string> weight type per tensor pattern (example: "^vae\.=f16,model\.=q8_0")
|
||||
--photo-maker <string> path to PHOTOMAKER model
|
||||
--upscale-model <string> path to esrgan model.
|
||||
-t, --threads <int> number of threads to use during computation (default: -1). If threads <= 0,
|
||||
then threads will be set to the number of CPU physical cores
|
||||
--chroma-t5-mask-pad <int> t5 mask pad size of chroma
|
||||
--max-vram <float> maximum VRAM budget in GiB for graph-cut segmented execution. 0 disables
|
||||
graph splitting; a negative value auto-detects free VRAM, sparing the
|
||||
specified value (e.g. -0.5 will keep at least 0.5 GiB free)
|
||||
--force-sdxl-vae-conv-scale force use of conv scale on sdxl vae
|
||||
--offload-to-cpu place the weights in RAM to save VRAM, and automatically load them into VRAM
|
||||
when needed
|
||||
--mmap whether to memory-map model
|
||||
--control-net-cpu keep controlnet in cpu (for low vram)
|
||||
--clip-on-cpu keep clip in cpu (for low vram)
|
||||
--vae-on-cpu keep vae in cpu (for low vram)
|
||||
--fa use flash attention
|
||||
--diffusion-fa use flash attention in the diffusion model only
|
||||
--diffusion-conv-direct use ggml_conv2d_direct in the diffusion model
|
||||
--vae-conv-direct use ggml_conv2d_direct in the vae model
|
||||
--circular enable circular padding for convolutions
|
||||
--circularx enable circular RoPE wrapping on x-axis (width) only
|
||||
--circulary enable circular RoPE wrapping on y-axis (height) only
|
||||
--chroma-disable-dit-mask disable dit mask for chroma
|
||||
--qwen-image-zero-cond-t enable zero_cond_t for qwen image
|
||||
--chroma-enable-t5-mask enable t5 mask for chroma
|
||||
--type weight type (examples: f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_K, q3_K,
|
||||
q4_K). If not specified, the default is the type of the weight file
|
||||
--rng RNG, one of [std_default, cuda, cpu], default: cuda(sd-webui), cpu(comfyui)
|
||||
--sampler-rng sampler RNG, one of [std_default, cuda, cpu]. If not specified, use --rng
|
||||
--prediction prediction type override, one of [eps, v, edm_v, sd3_flow, flux_flow,
|
||||
flux2_flow]
|
||||
--lora-apply-mode the way to apply LoRA, one of [auto, immediately, at_runtime], default is
|
||||
auto. In auto mode, if the model weights contain any quantized parameters,
|
||||
the at_runtime mode will be used; otherwise, immediately will be used.The
|
||||
immediately mode may have precision and compatibility issues with quantized
|
||||
parameters, but it usually offers faster inference speed and, in some cases,
|
||||
lower memory usage. The at_runtime mode, on the other hand, is exactly the
|
||||
opposite.
|
||||
|
||||
Default Generation Options:
|
||||
-p, --prompt <string> the prompt to render
|
||||
-n, --negative-prompt <string> the negative prompt (default: "")
|
||||
-i, --init-img <string> path to the init image
|
||||
--end-img <string> path to the end image, required by flf2v
|
||||
--mask <string> path to the mask image
|
||||
--control-image <string> path to control image, control net
|
||||
--control-video <string> path to control video frames, It must be a directory path. The video frames
|
||||
inside should be stored as images in lexicographical (character) order. For
|
||||
example, if the control video path is `frames`, the directory contain images
|
||||
such as 00.png, 01.png, ... etc.
|
||||
--pm-id-images-dir <string> path to PHOTOMAKER input id images dir
|
||||
--pm-id-embed-path <string> path to PHOTOMAKER v2 id embed
|
||||
--hires-upscaler <string> highres fix upscaler, Lanczos, Nearest, Latent, Latent (nearest), Latent
|
||||
(nearest-exact), Latent (antialiased), Latent (bicubic), Latent (bicubic
|
||||
antialiased), or a model name under --hires-upscalers-dir (default: Latent)
|
||||
--extra-sample-args <string> extra sampler/scheduler/guidance args, key=value list. APG supports apg_eta,
|
||||
apg_momentum, apg_norm_threshold, apg_norm_threshold_smoothing; SLG supports
|
||||
slg_uncond; lcm supports noise_clip_std, noise_scale_start, noise_scale_end;
|
||||
ltx2 supports max_shift, base_shift, stretch, terminal; euler_ge supports gamma
|
||||
--extra-tiling-args <string> extra VAE tiling args, key=value list. LTX video VAE supports
|
||||
temporal_tile_frames (default: 4), temporal_tile_overlap (default: 1)
|
||||
-H, --height <int> image height, in pixel space (default: 512)
|
||||
-W, --width <int> image width, in pixel space (default: 512)
|
||||
--steps <int> number of sample steps (default: 20)
|
||||
--high-noise-steps <int> (high noise) number of sample steps (default: -1 = auto)
|
||||
--clip-skip <int> ignore last layers of CLIP network; 1 ignores none, 2 ignores one layer
|
||||
(default: -1). <= 0 represents unspecified, will be 1 for SD1.x, 2 for SD2.x
|
||||
-b, --batch-count <int> batch count
|
||||
--video-frames <int> video frames (default: 1)
|
||||
--fps <int> fps (default: 24)
|
||||
--timestep-shift <int> shift timestep for NitroFusion models (default: 0). recommended N for
|
||||
NitroSD-Realism around 250 and 500 for NitroSD-Vibrant
|
||||
--upscale-repeats <int> Run the ESRGAN upscaler this many times (default: 1)
|
||||
--upscale-tile-size <int> tile size for ESRGAN upscaling (default: 128)
|
||||
--hires-width <int> highres fix target width, 0 to use --hires-scale (default: 0)
|
||||
--hires-height <int> highres fix target height, 0 to use --hires-scale (default: 0)
|
||||
--hires-steps <int> highres fix second pass sample steps, 0 to reuse --steps (default: 0)
|
||||
--hires-upscale-tile-size <int> highres fix upscaler tile size, reserved for model-backed upscalers (default:
|
||||
128)
|
||||
--cfg-scale <float> unconditional guidance scale: (default: 7.0)
|
||||
--img-cfg-scale <float> image guidance scale for inpaint or image edit models: (default: same as
|
||||
--cfg-scale)
|
||||
--guidance <float> distilled guidance scale for models with guidance input (default: 3.5)
|
||||
--slg-scale <float> skip layer guidance (SLG) scale, only for DiT models: (default: 0). 0 means
|
||||
disabled, a value of 2.5 is nice for sd3.5 medium
|
||||
--skip-layer-start <float> SLG enabling point (default: 0.01)
|
||||
--skip-layer-end <float> SLG disabling point (default: 0.2)
|
||||
--eta <float> noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and
|
||||
res_2s; 1 for euler_a, er_sde and dpm++2s_a)
|
||||
--flow-shift <float> shift value for Flow models like SD3.x or WAN (default: auto)
|
||||
--high-noise-cfg-scale <float> (high noise) unconditional guidance scale: (default: 7.0)
|
||||
--high-noise-img-cfg-scale <float> (high noise) image guidance scale for inpaint or image edit models (default:
|
||||
same as --cfg-scale)
|
||||
--high-noise-guidance <float> (high noise) distilled guidance scale for models with guidance input
|
||||
(default: 3.5)
|
||||
--high-noise-slg-scale <float> (high noise) skip layer guidance (SLG) scale, only for DiT models: (default:
|
||||
0)
|
||||
--high-noise-skip-layer-start <float> (high noise) SLG enabling point (default: 0.01)
|
||||
--high-noise-skip-layer-end <float> (high noise) SLG disabling point (default: 0.2)
|
||||
--high-noise-eta <float> (high noise) noise multiplier (default: 0 for ddim_trailing, tcd,
|
||||
res_multistep and res_2s; 1 for euler_a, er_sde and dpm++2s_a)
|
||||
--strength <float> strength for noising/unnoising (default: 0.75)
|
||||
--pm-style-strength <float>
|
||||
--control-strength <float> strength to apply Control Net (default: 0.9). 1.0 corresponds to full
|
||||
destruction of information in init image
|
||||
--moe-boundary <float> timestep boundary for Wan2.2 MoE model. (default: 0.875). Only enabled if
|
||||
`--high-noise-steps` is set to -1
|
||||
--vace-strength <float> wan vace strength
|
||||
--vae-tile-overlap <float> tile overlap for vae tiling, in fraction of tile size (default: 0.5)
|
||||
--hires-scale <float> highres fix scale when target size is not set (default: 2.0)
|
||||
--hires-denoising-strength <float> highres fix second pass denoising strength (default: 0.7)
|
||||
--increase-ref-index automatically increase the indices of references images based on the order
|
||||
they are listed (starting with 1).
|
||||
--disable-auto-resize-ref-image disable auto resize of ref images
|
||||
--disable-image-metadata do not embed generation metadata on image files
|
||||
--vae-tiling process vae in tiles to reduce memory usage
|
||||
--temporal-tiling enable temporal tiling for LTX video VAE decode
|
||||
--hires enable highres fix
|
||||
-s, --seed RNG seed (default: 42, use random seed for < 0)
|
||||
--sampling-method sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m,
|
||||
dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep, res_2s,
|
||||
er_sde, euler_cfg_pp, euler_a_cfg_pp] (default: euler for Flux/SD3/Wan, euler_a otherwise)
|
||||
--high-noise-sampling-method (high noise) sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a,
|
||||
dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep,
|
||||
res_2s, er_sde, euler_cfg_pp, euler_a_cfg_pp] default: euler for Flux/SD3/Wan, euler_a otherwise
|
||||
--scheduler denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits,
|
||||
smoothstep, sgm_uniform, simple, kl_optimal, lcm, bong_tangent, ltx2], default:
|
||||
model-specific
|
||||
--sigmas custom sigma values for the sampler, comma-separated (e.g.,
|
||||
"14.61,7.8,3.5,0.0").
|
||||
--hires-sigmas custom sigma values for the highres fix second pass, comma-separated (e.g.,
|
||||
"0.85,0.725,0.421875,0.0").
|
||||
--skip-layers layers to skip for SLG steps (default: [7,8,9])
|
||||
--high-noise-skip-layers (high noise) layers to skip for SLG steps (default: [7,8,9])
|
||||
-r, --ref-image reference image for Flux Kontext models (can be used multiple times)
|
||||
--cache-mode caching method: 'easycache' (DiT), 'ucache' (UNET),
|
||||
'dbcache'/'taylorseer'/'cache-dit' (DiT block-level), 'spectrum' (UNET/DiT
|
||||
Chebyshev+Taylor forecasting)
|
||||
--cache-option named cache params (key=value format, comma-separated). easycache/ucache:
|
||||
threshold=,start=,end=,decay=,relative=,reset=; dbcache/taylorseer/cache-dit:
|
||||
Fn=,Bn=,threshold=,warmup=; spectrum: w=,m=,lam=,window=,flex=,warmup=,stop=.
|
||||
Examples: "threshold=0.25" or "threshold=1.5,reset=0"
|
||||
--scm-mask SCM steps mask for cache-dit: comma-separated 0/1 (e.g.,
|
||||
"1,1,1,0,0,1,0,0,1,0") - 1=compute, 0=can cache
|
||||
--scm-policy SCM policy: 'dynamic' (default) or 'static'
|
||||
--vae-tile-size tile size for vae tiling, format [X]x[Y] (default: 32x32)
|
||||
--vae-relative-tile-size relative tile size for vae tiling, format [X]x[Y], in fraction of image size
|
||||
if < 1, in number of tiles per dim if >=1 (overrides --vae-tile-size)
|
||||
```bash
|
||||
./bin/sd-server -h
|
||||
```
|
||||
|
||||
@@ -173,8 +173,13 @@ bool execute_img_gen_job(ServerRuntime& runtime,
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
|
||||
sd_image_t* raw_results = generate_image(runtime.sd_ctx, ¶ms);
|
||||
results.adopt(raw_results, params.batch_count);
|
||||
sd_image_t* raw_results = nullptr;
|
||||
int num_results = 0;
|
||||
if (!generate_image(runtime.sd_ctx, ¶ms, &raw_results, &num_results)) {
|
||||
raw_results = nullptr;
|
||||
num_results = 0;
|
||||
}
|
||||
results.adopt(raw_results, num_results);
|
||||
}
|
||||
|
||||
const int num_results = results.count();
|
||||
|
||||
+1
-1
Submodule examples/server/frontend updated: 797ccf8082...c4bce3d6b3
@@ -85,7 +85,7 @@ int main(int argc, const char** argv) {
|
||||
LOG_DEBUG("%s", ctx_params.to_string().c_str());
|
||||
LOG_DEBUG("%s", default_gen_params.to_string().c_str());
|
||||
|
||||
sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(false, false, false);
|
||||
sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(false);
|
||||
SDCtxPtr sd_ctx(new_sd_ctx(&sd_ctx_params));
|
||||
|
||||
if (sd_ctx == nullptr) {
|
||||
|
||||
@@ -229,8 +229,11 @@ static bool execute_sync_img_gen_request(ServerRuntime& runtime,
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
|
||||
sd_image_t* raw_results = generate_image(runtime.sd_ctx, &img_gen_params);
|
||||
num_results = request.gen_params.batch_count;
|
||||
sd_image_t* raw_results = nullptr;
|
||||
if (!generate_image(runtime.sd_ctx, &img_gen_params, &raw_results, &num_results)) {
|
||||
raw_results = nullptr;
|
||||
num_results = 0;
|
||||
}
|
||||
results.adopt(raw_results, num_results);
|
||||
}
|
||||
|
||||
|
||||
@@ -292,8 +292,11 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime->sd_ctx_mutex);
|
||||
sd_image_t* raw_results = generate_image(runtime->sd_ctx, &img_gen_params);
|
||||
num_results = request.gen_params.batch_count;
|
||||
sd_image_t* raw_results = nullptr;
|
||||
if (!generate_image(runtime->sd_ctx, &img_gen_params, &raw_results, &num_results)) {
|
||||
raw_results = nullptr;
|
||||
num_results = 0;
|
||||
}
|
||||
results.adopt(raw_results, num_results);
|
||||
}
|
||||
|
||||
@@ -438,6 +441,9 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
scheduler_names.push_back("default");
|
||||
for (int i = 0; i < SCHEDULER_COUNT; i++) {
|
||||
scheduler_names.push_back(sd_scheduler_name((scheduler_t)i));
|
||||
if (i == DISCRETE_SCHEDULER) {
|
||||
scheduler_names.push_back("normal");
|
||||
}
|
||||
}
|
||||
json r = json::array();
|
||||
for (auto name : scheduler_names) {
|
||||
|
||||
@@ -219,6 +219,9 @@ static json make_capabilities_json(ServerRuntime& runtime) {
|
||||
|
||||
for (int i = 0; i < SCHEDULER_COUNT; ++i) {
|
||||
schedulers.push_back(sd_scheduler_name((scheduler_t)i));
|
||||
if (i == DISCRETE_SCHEDULER) {
|
||||
schedulers.push_back("normal");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -190,8 +190,8 @@ ArgOptions SDSvrParams::get_options() {
|
||||
ArgOptions options;
|
||||
|
||||
options.string_options = {
|
||||
{"-l", "--listen-ip", "server listen ip (default: 127.0.0.1)", &listen_ip},
|
||||
{"", "--serve-html-path", "path to HTML file to serve at root (optional)", &serve_html_path},
|
||||
{"-l", "--listen-ip", "server listen ip (default: 127.0.0.1)", 0, &listen_ip},
|
||||
{"", "--serve-html-path", "path to HTML file to serve at root (optional)", 0, &serve_html_path},
|
||||
};
|
||||
|
||||
options.int_options = {
|
||||
|
||||
+1
-1
Submodule ggml updated: 0ce7ad348a...eced84c86f
+35
-13
@@ -70,6 +70,10 @@ enum scheduler_t {
|
||||
LCM_SCHEDULER,
|
||||
BONG_TANGENT_SCHEDULER,
|
||||
LTX2_SCHEDULER,
|
||||
LOGIT_NORMAL_SCHEDULER,
|
||||
FLUX2_SCHEDULER,
|
||||
FLUX_SCHEDULER,
|
||||
BETA_SCHEDULER,
|
||||
SCHEDULER_COUNT
|
||||
};
|
||||
|
||||
@@ -79,7 +83,7 @@ enum prediction_t {
|
||||
EDM_V_PRED,
|
||||
FLOW_PRED,
|
||||
FLUX_FLOW_PRED,
|
||||
FLUX2_FLOW_PRED,
|
||||
SEFI_FLOW_PRED,
|
||||
PREDICTION_COUNT
|
||||
};
|
||||
|
||||
@@ -195,20 +199,15 @@ typedef struct {
|
||||
const sd_embedding_t* embeddings;
|
||||
uint32_t embedding_count;
|
||||
const char* photo_maker_path;
|
||||
const char* pulid_weights_path;
|
||||
const char* tensor_type_rules;
|
||||
bool vae_decode_only;
|
||||
bool free_params_immediately;
|
||||
int n_threads;
|
||||
enum sd_type_t wtype;
|
||||
enum rng_type_t rng_type;
|
||||
enum rng_type_t sampler_rng_type;
|
||||
enum prediction_t prediction;
|
||||
enum lora_apply_mode_t lora_apply_mode;
|
||||
bool offload_params_to_cpu;
|
||||
bool enable_mmap;
|
||||
bool keep_clip_on_cpu;
|
||||
bool keep_control_net_on_cpu;
|
||||
bool keep_vae_on_cpu;
|
||||
bool flash_attn;
|
||||
bool diffusion_flash_attn;
|
||||
bool tae_preview_only;
|
||||
@@ -222,10 +221,12 @@ typedef struct {
|
||||
int chroma_t5_mask_pad;
|
||||
bool qwen_image_zero_cond_t;
|
||||
enum sd_vae_format_t vae_format;
|
||||
float max_vram; // GiB budget for graph-cut segmented param offload (0 = disabled, -1 = auto free VRAM minus 1 GiB)
|
||||
const char* max_vram; // GiB budget or backend assignment spec for graph-cut segmented param offload (0 = disabled, -1 = auto)
|
||||
bool stream_layers; // Enable residency+prefetch streaming on top of --max-vram (no effect without --max-vram)
|
||||
bool eager_load; // Load all params into the params backend at model-load time instead of lazily on first use
|
||||
const char* backend;
|
||||
const char* params_backend;
|
||||
const char* rpc_servers;
|
||||
} sd_ctx_params_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -277,6 +278,11 @@ typedef struct {
|
||||
float style_strength;
|
||||
} sd_pm_params_t; // photo maker
|
||||
|
||||
typedef struct {
|
||||
const char* id_embedding_path;
|
||||
float id_weight;
|
||||
} sd_pulid_params_t;
|
||||
|
||||
enum sd_cache_mode_t {
|
||||
SD_CACHE_DISABLED = 0,
|
||||
SD_CACHE_EASYCACHE,
|
||||
@@ -369,6 +375,7 @@ typedef struct {
|
||||
sd_image_t control_image;
|
||||
float control_strength;
|
||||
sd_pm_params_t pm_params;
|
||||
sd_pulid_params_t pulid_params;
|
||||
sd_tiling_params_t vae_tiling_params;
|
||||
sd_cache_params_t cache;
|
||||
sd_hires_params_t hires;
|
||||
@@ -448,7 +455,21 @@ SD_API enum scheduler_t sd_get_default_scheduler(const sd_ctx_t* sd_ctx, enum sa
|
||||
|
||||
SD_API void sd_img_gen_params_init(sd_img_gen_params_t* sd_img_gen_params);
|
||||
SD_API char* sd_img_gen_params_to_str(const sd_img_gen_params_t* sd_img_gen_params);
|
||||
SD_API sd_image_t* generate_image(sd_ctx_t* sd_ctx, const sd_img_gen_params_t* sd_img_gen_params);
|
||||
SD_API bool generate_image(sd_ctx_t* sd_ctx,
|
||||
const sd_img_gen_params_t* sd_img_gen_params,
|
||||
sd_image_t** images_out,
|
||||
int* num_images_out);
|
||||
|
||||
enum sd_cancel_mode_t {
|
||||
// Stop the current generation as soon as possible.
|
||||
SD_CANCEL_ALL,
|
||||
// Finish the current image sample, then skip additional batch latents and return completed images.
|
||||
SD_CANCEL_NEW_LATENTS,
|
||||
// Clear a pending cancellation request.
|
||||
SD_CANCEL_RESET
|
||||
};
|
||||
|
||||
SD_API void sd_cancel_generation(sd_ctx_t* sd_ctx, enum sd_cancel_mode_t mode);
|
||||
|
||||
SD_API void sd_vid_gen_params_init(sd_vid_gen_params_t* sd_vid_gen_params);
|
||||
SD_API bool generate_video(sd_ctx_t* sd_ctx,
|
||||
@@ -460,7 +481,6 @@ SD_API bool generate_video(sd_ctx_t* sd_ctx,
|
||||
typedef struct upscaler_ctx_t upscaler_ctx_t;
|
||||
|
||||
SD_API upscaler_ctx_t* new_upscaler_ctx(const char* esrgan_path,
|
||||
bool offload_params_to_cpu,
|
||||
bool direct,
|
||||
int n_threads,
|
||||
int tile_size,
|
||||
@@ -468,9 +488,11 @@ SD_API upscaler_ctx_t* new_upscaler_ctx(const char* esrgan_path,
|
||||
const char* params_backend);
|
||||
SD_API void free_upscaler_ctx(upscaler_ctx_t* upscaler_ctx);
|
||||
|
||||
SD_API sd_image_t upscale(upscaler_ctx_t* upscaler_ctx,
|
||||
sd_image_t input_image,
|
||||
uint32_t upscale_factor);
|
||||
SD_API bool upscale(upscaler_ctx_t* upscaler_ctx,
|
||||
sd_image_t input_image,
|
||||
uint32_t upscale_factor,
|
||||
sd_image_t** images_out,
|
||||
int* num_images_out);
|
||||
|
||||
SD_API int get_upscale_factor(upscaler_ctx_t* upscaler_ctx);
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert a Qwen3-VL HF safetensors checkpoint into a sd.cpp-loadable form.
|
||||
|
||||
The HF dump prefixes text-tower keys with ``model.language_model.`` and
|
||||
vision-tower keys with ``model.visual.``. sd.cpp expects ``model.<rest>`` for
|
||||
the text side; the vision side is converted by sd.cpp's own
|
||||
``convert_qwen3_vl_vision_name`` and is left as-is here.
|
||||
|
||||
Operates on raw safetensors bytes so any dtype (BF16/F16/F32) is preserved.
|
||||
|
||||
Usage:
|
||||
python3 script/convert_qwen3_vl.py <hf_qwen3_vl_dir_or_safetensors> <output.safetensors>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def rewrite_key(key: str) -> str:
|
||||
if key.startswith("model.language_model."):
|
||||
return "model." + key[len("model.language_model."):]
|
||||
return key
|
||||
|
||||
|
||||
def read_safetensors_header(path: str):
|
||||
with open(path, "rb") as f:
|
||||
hdr_len = struct.unpack("<Q", f.read(8))[0]
|
||||
hdr_bytes = f.read(hdr_len)
|
||||
return json.loads(hdr_bytes), 8 + hdr_len
|
||||
|
||||
|
||||
def collect_shard_paths(path: str):
|
||||
if os.path.isdir(path):
|
||||
index_path = os.path.join(path, "model.safetensors.index.json")
|
||||
if os.path.isfile(index_path):
|
||||
with open(index_path) as f:
|
||||
idx = json.load(f)
|
||||
return sorted({os.path.join(path, n) for n in idx["weight_map"].values()})
|
||||
single = os.path.join(path, "model.safetensors")
|
||||
if os.path.isfile(single):
|
||||
return [single]
|
||||
raise FileNotFoundError(f"No Qwen3-VL safetensors in {path}")
|
||||
if os.path.isfile(path):
|
||||
return [path]
|
||||
raise FileNotFoundError(path)
|
||||
|
||||
|
||||
def stage_tensors(input_path: str):
|
||||
entries = []
|
||||
for shard_path in collect_shard_paths(input_path):
|
||||
hdr, data_off = read_safetensors_header(shard_path)
|
||||
for key, info in hdr.items():
|
||||
if key == "__metadata__":
|
||||
continue
|
||||
entries.append((rewrite_key(key), shard_path, data_off, info))
|
||||
return entries
|
||||
|
||||
|
||||
def write_consolidated(out_path: str, entries):
|
||||
entries = sorted(entries, key=lambda e: e[0])
|
||||
|
||||
new_header = {}
|
||||
cur_offset = 0
|
||||
for new_key, shard_path, data_off, info in entries:
|
||||
start, end = info["data_offsets"]
|
||||
size = end - start
|
||||
new_header[new_key] = {
|
||||
"dtype": info["dtype"],
|
||||
"shape": info["shape"],
|
||||
"data_offsets": [cur_offset, cur_offset + size],
|
||||
}
|
||||
cur_offset += size
|
||||
|
||||
header_json = json.dumps(new_header, separators=(",", ":")).encode("utf-8")
|
||||
pad = (-len(header_json)) % 8
|
||||
header_json = header_json + (b" " * pad)
|
||||
|
||||
with open(out_path, "wb") as out:
|
||||
out.write(struct.pack("<Q", len(header_json)))
|
||||
out.write(header_json)
|
||||
for new_key, shard_path, data_off, info in entries:
|
||||
start, end = info["data_offsets"]
|
||||
with open(shard_path, "rb") as src:
|
||||
src.seek(data_off + start)
|
||||
remaining = end - start
|
||||
while remaining > 0:
|
||||
chunk = src.read(min(8 * 1024 * 1024, remaining))
|
||||
if not chunk:
|
||||
raise IOError(f"Truncated tensor in {shard_path}")
|
||||
out.write(chunk)
|
||||
remaining -= len(chunk)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("input", help="HF Qwen3-VL directory or single safetensors file")
|
||||
parser.add_argument("output", help="Output single safetensors path")
|
||||
args = parser.parse_args()
|
||||
|
||||
entries = stage_tensors(args.input)
|
||||
print(f"Tensors: {len(entries)}")
|
||||
print(f"Writing -> {args.output}")
|
||||
os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True)
|
||||
write_consolidated(args.output, entries)
|
||||
print(f"Done. Output size: {os.path.getsize(args.output) / 1e9:.2f} GB")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,279 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert a SeFi-Image diffusers checkpoint into a single sd.cpp-compatible safetensors.
|
||||
|
||||
Operates on raw safetensors bytes so any dtype (BF16, F32, ...) is preserved exactly.
|
||||
No numpy or torch dependency required.
|
||||
|
||||
Usage:
|
||||
python3 script/convert_sefi.py <sefi_diffusers_dir> <output.safetensors>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
_LINEAR_TO_LIN = re.compile(r"\.linear\.")
|
||||
_SHARED_MOD_PREFIXES = (
|
||||
"double_stream_modulation_img",
|
||||
"double_stream_modulation_txt",
|
||||
"single_stream_modulation",
|
||||
)
|
||||
|
||||
|
||||
def rewrite_transformer_key(key: str) -> str:
|
||||
if key.startswith("backbone."):
|
||||
key = key[len("backbone."):]
|
||||
elif key.startswith("dual_time_embed."):
|
||||
return key
|
||||
|
||||
if any(key.startswith(prefix + ".") for prefix in _SHARED_MOD_PREFIXES):
|
||||
key = _LINEAR_TO_LIN.sub(".lin.", key, count=1)
|
||||
|
||||
if key == "context_embedder.weight":
|
||||
return "txt_in.weight"
|
||||
if key == "context_embedder.bias":
|
||||
return "txt_in.bias"
|
||||
if key == "x_embedder.weight":
|
||||
return "img_in.weight"
|
||||
if key == "x_embedder.bias":
|
||||
return "img_in.bias"
|
||||
|
||||
if key == "proj_out.weight":
|
||||
return "final_layer.linear.weight"
|
||||
if key == "proj_out.bias":
|
||||
return "final_layer.linear.bias"
|
||||
if key == "norm_out.linear.weight":
|
||||
return "final_layer.adaLN_modulation.1.weight"
|
||||
if key == "norm_out.linear.bias":
|
||||
return "final_layer.adaLN_modulation.1.bias"
|
||||
|
||||
m = re.match(r"transformer_blocks\.(\d+)\.(.*)$", key)
|
||||
if m:
|
||||
return _rewrite_double_stream(m.group(1), m.group(2))
|
||||
m = re.match(r"single_transformer_blocks\.(\d+)\.(.*)$", key)
|
||||
if m:
|
||||
return _rewrite_single_stream(m.group(1), m.group(2))
|
||||
|
||||
return key
|
||||
|
||||
|
||||
def _rewrite_double_stream(idx: str, tail: str) -> str:
|
||||
dst = f"double_blocks.{idx}."
|
||||
mapping = {
|
||||
"norm1.linear.weight": "img_mod.lin.weight",
|
||||
"norm1_context.linear.weight": "txt_mod.lin.weight",
|
||||
"attn.norm_q.weight": "img_attn.norm.query_norm.scale",
|
||||
"attn.norm_k.weight": "img_attn.norm.key_norm.scale",
|
||||
"attn.norm_added_q.weight": "txt_attn.norm.query_norm.scale",
|
||||
"attn.norm_added_k.weight": "txt_attn.norm.key_norm.scale",
|
||||
"attn.to_out.0.weight": "img_attn.proj.weight",
|
||||
"attn.to_add_out.weight": "txt_attn.proj.weight",
|
||||
"ff.net.0.proj.weight": "img_mlp.0.weight",
|
||||
"ff.net.2.weight": "img_mlp.2.weight",
|
||||
"ff_context.net.0.proj.weight": "txt_mlp.0.weight",
|
||||
"ff_context.net.2.weight": "txt_mlp.2.weight",
|
||||
"ff.linear_in.weight": "img_mlp.0.weight",
|
||||
"ff.linear_out.weight": "img_mlp.2.weight",
|
||||
"ff_context.linear_in.weight": "txt_mlp.0.weight",
|
||||
"ff_context.linear_out.weight": "txt_mlp.2.weight",
|
||||
}
|
||||
return dst + mapping.get(tail, tail)
|
||||
|
||||
|
||||
# QKV triplets to fuse on output: source tails -> target fused tail.
|
||||
# Each tuple is (q_tail, k_tail, v_tail, fused_target_tail).
|
||||
QKV_DOUBLE_TRIPLETS = [
|
||||
("attn.to_q.weight", "attn.to_k.weight", "attn.to_v.weight", "img_attn.qkv.weight"),
|
||||
("attn.add_q_proj.weight", "attn.add_k_proj.weight", "attn.add_v_proj.weight", "txt_attn.qkv.weight"),
|
||||
]
|
||||
|
||||
|
||||
def _rewrite_single_stream(idx: str, tail: str) -> str:
|
||||
dst = f"single_blocks.{idx}."
|
||||
mapping = {
|
||||
"norm.linear.weight": "modulation.lin.weight",
|
||||
"attn.norm_q.weight": "norm.query_norm.scale",
|
||||
"attn.norm_k.weight": "norm.key_norm.scale",
|
||||
"attn.to_qkv_mlp_proj.weight": "linear1.weight",
|
||||
"attn.to_out.weight": "linear2.weight",
|
||||
}
|
||||
return dst + mapping.get(tail, tail)
|
||||
|
||||
|
||||
|
||||
|
||||
def read_safetensors_header(path: str):
|
||||
"""Return (header dict, data start byte offset)."""
|
||||
with open(path, "rb") as f:
|
||||
hdr_len = struct.unpack("<Q", f.read(8))[0]
|
||||
hdr_bytes = f.read(hdr_len)
|
||||
return json.loads(hdr_bytes), 8 + hdr_len
|
||||
|
||||
|
||||
def collect_shard_paths(directory: str, weight_pattern: str):
|
||||
index_path = os.path.join(directory, f"{weight_pattern}.safetensors.index.json")
|
||||
if os.path.isfile(index_path):
|
||||
with open(index_path) as f:
|
||||
idx = json.load(f)
|
||||
return sorted({os.path.join(directory, n) for n in idx["weight_map"].values()})
|
||||
single = os.path.join(directory, f"{weight_pattern}.safetensors")
|
||||
if not os.path.isfile(single):
|
||||
raise FileNotFoundError(f"No checkpoint at {directory}: missing {weight_pattern}")
|
||||
return [single]
|
||||
|
||||
|
||||
def stage_tensors_for_section(section_dir: str, rewrite_fn):
|
||||
"""Return a list of (new_key, shard_path, data_start_offset, info_dict) entries.
|
||||
|
||||
A "qkv_fuse" pseudo-entry with three source descriptors is emitted when a
|
||||
transformer_blocks.* split q/k/v triplet is found, so the writer can fuse
|
||||
them into a single output tensor.
|
||||
"""
|
||||
entries = []
|
||||
# First, index all raw keys per shard so we can detect qkv triplets.
|
||||
raw_by_block = {} # block_idx -> {tail: (key, shard_path, data_off, info)}
|
||||
raw_others = []
|
||||
for shard_path in collect_shard_paths(section_dir, "diffusion_pytorch_model"):
|
||||
hdr, data_off = read_safetensors_header(shard_path)
|
||||
for key, info in hdr.items():
|
||||
if key == "__metadata__":
|
||||
continue
|
||||
m = re.match(r"backbone\.transformer_blocks\.(\d+)\.(.*)$", key)
|
||||
if m and any(m.group(2) in trip[:3] for trip in QKV_DOUBLE_TRIPLETS):
|
||||
idx = m.group(1)
|
||||
raw_by_block.setdefault(idx, {})[m.group(2)] = (key, shard_path, data_off, info)
|
||||
else:
|
||||
raw_others.append((key, shard_path, data_off, info))
|
||||
|
||||
for key, shard_path, data_off, info in raw_others:
|
||||
new_key = rewrite_fn(key)
|
||||
# Swap the (scale, shift) halves to (shift, scale) at conversion time so
|
||||
# the on-disk weight matches BFL flux ordering and the runtime stays
|
||||
# version-agnostic. norm_out.linear weight shape is [2*dim, dim] and bias
|
||||
# is [2*dim]; both split along axis 0 (outermost == row-major outer).
|
||||
if new_key in ("final_layer.adaLN_modulation.1.weight",
|
||||
"final_layer.adaLN_modulation.1.bias"):
|
||||
info = dict(info)
|
||||
info["_chunk_swap_halves"] = True
|
||||
entries.append((new_key, shard_path, data_off, info))
|
||||
|
||||
for block_idx, tails in raw_by_block.items():
|
||||
for q_tail, k_tail, v_tail, fused_tail in QKV_DOUBLE_TRIPLETS:
|
||||
if q_tail in tails and k_tail in tails and v_tail in tails:
|
||||
q = tails[q_tail]; k = tails[k_tail]; v = tails[v_tail]
|
||||
# Validate shapes match.
|
||||
q_shape = q[3]["shape"]; k_shape = k[3]["shape"]; v_shape = v[3]["shape"]
|
||||
if q_shape != k_shape or q_shape != v_shape:
|
||||
raise ValueError(f"qkv shape mismatch at block {block_idx} {q_tail}: q={q_shape} k={k_shape} v={v_shape}")
|
||||
fused_shape = [q_shape[0] * 3] + list(q_shape[1:])
|
||||
fused_info = {
|
||||
"dtype": q[3]["dtype"],
|
||||
"shape": fused_shape,
|
||||
"_qkv_sources": [q, k, v], # pseudo field consumed by writer
|
||||
}
|
||||
entries.append((f"double_blocks.{block_idx}.{fused_tail}",
|
||||
None, None, fused_info))
|
||||
del tails[q_tail]; del tails[k_tail]; del tails[v_tail]
|
||||
# Anything left in tails was an unmatched single - pass through.
|
||||
for tail, payload in tails.items():
|
||||
entries.append((rewrite_fn(payload[0]),) + payload[1:])
|
||||
return entries
|
||||
|
||||
|
||||
_DTYPE_BYTES = {
|
||||
"BF16": 2, "F16": 2, "F32": 4, "F64": 8,
|
||||
"U8": 1, "I8": 1, "I16": 2, "I32": 4, "I64": 8,
|
||||
"BOOL": 1,
|
||||
}
|
||||
|
||||
|
||||
def _total_bytes(info: dict) -> int:
|
||||
if "_qkv_sources" in info:
|
||||
elems = 1
|
||||
for d in info["shape"]:
|
||||
elems *= d
|
||||
return elems * _DTYPE_BYTES[info["dtype"]]
|
||||
start, end = info["data_offsets"]
|
||||
return end - start
|
||||
|
||||
|
||||
def write_consolidated(out_path: str, entries):
|
||||
"""Write a single safetensors file by streaming raw bytes from each shard.
|
||||
|
||||
For qkv-fused entries, q/k/v are concatenated along axis 0 (row-major), so a
|
||||
simple byte-level concatenation produces the correct fused layout for any
|
||||
standard dtype.
|
||||
"""
|
||||
entries = sorted(entries, key=lambda e: e[0])
|
||||
|
||||
new_header = {}
|
||||
cur_offset = 0
|
||||
for new_key, shard_path, data_off, info in entries:
|
||||
size = _total_bytes(info)
|
||||
new_header[new_key] = {
|
||||
"dtype": info["dtype"],
|
||||
"shape": info["shape"],
|
||||
"data_offsets": [cur_offset, cur_offset + size],
|
||||
}
|
||||
cur_offset += size
|
||||
|
||||
header_json = json.dumps(new_header, separators=(",", ":")).encode("utf-8")
|
||||
pad = (-len(header_json)) % 8
|
||||
header_json = header_json + (b" " * pad)
|
||||
|
||||
def copy_range(src_path, src_data_off, src_info, out, byte_range=None):
|
||||
start, end = src_info["data_offsets"]
|
||||
if byte_range is not None:
|
||||
sub_start, sub_end = byte_range
|
||||
start, end = start + sub_start, start + sub_end
|
||||
with open(src_path, "rb") as src:
|
||||
src.seek(src_data_off + start)
|
||||
remaining = end - start
|
||||
while remaining > 0:
|
||||
chunk = src.read(min(8 * 1024 * 1024, remaining))
|
||||
if not chunk:
|
||||
raise IOError(f"Truncated tensor in {src_path}")
|
||||
out.write(chunk)
|
||||
remaining -= len(chunk)
|
||||
|
||||
with open(out_path, "wb") as out:
|
||||
out.write(struct.pack("<Q", len(header_json)))
|
||||
out.write(header_json)
|
||||
for new_key, shard_path, data_off, info in entries:
|
||||
if "_qkv_sources" in info:
|
||||
for q_entry in info["_qkv_sources"]:
|
||||
_, src_path, src_data_off, src_info = q_entry
|
||||
copy_range(src_path, src_data_off, src_info, out)
|
||||
elif info.get("_chunk_swap_halves"):
|
||||
size = _total_bytes(info)
|
||||
half = size // 2
|
||||
if size != half * 2:
|
||||
raise ValueError(f"{new_key}: odd byte size {size} cannot be split into halves")
|
||||
copy_range(shard_path, data_off, info, out, byte_range=(half, size))
|
||||
copy_range(shard_path, data_off, info, out, byte_range=(0, half))
|
||||
else:
|
||||
copy_range(shard_path, data_off, info, out)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("input_dir", help="SeFi diffusers checkpoint directory")
|
||||
parser.add_argument("output", help="Output transformer safetensors path (load via --diffusion-model)")
|
||||
args = parser.parse_args()
|
||||
|
||||
transformer_entries = stage_tensors_for_section(
|
||||
os.path.join(args.input_dir, "transformer"), rewrite_transformer_key)
|
||||
|
||||
print(f"Transformer tensors: {len(transformer_entries)}")
|
||||
print(f"Writing {len(transformer_entries)} tensors -> {args.output}")
|
||||
os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True)
|
||||
write_consolidated(args.output, transformer_entries)
|
||||
print(f"Done. Output size: {os.path.getsize(args.output) / 1e9:.2f} GB")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
Precompute a PuLID-Flux identity embedding from a single source portrait.
|
||||
|
||||
Writes a gguf file (a single tensor `pulid_id`) that stable-diffusion.cpp's
|
||||
`--pulid-id-embedding` flag consumes.
|
||||
|
||||
Dependencies (recommended: vendor rather than pip-install due to upstream
|
||||
packaging quirks):
|
||||
- torch + safetensors
|
||||
- The ToTheBeginning/PuLID repository's `pulid/` package and `eva_clip/`.
|
||||
Put them on PYTHONPATH or sys.path before running this script.
|
||||
- insightface, facexlib, torchvision, opencv-python, huggingface_hub, gguf
|
||||
- numpy, Pillow
|
||||
|
||||
Usage:
|
||||
python script/pulid_extract_id.py \\
|
||||
--portrait /path/to/source-photo.jpg \\
|
||||
--pulid-weights /path/to/pulid_flux_v0.9.1.safetensors \\
|
||||
--out /path/to/source.pulidembd
|
||||
|
||||
The portrait must contain a clearly visible face. insightface's antelopev2
|
||||
detector will be auto-downloaded on first run.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from types import SimpleNamespace
|
||||
|
||||
|
||||
def extract(portrait_path: str, pulid_weights: str) -> "torch.Tensor":
|
||||
import numpy as np
|
||||
import torch
|
||||
from PIL import Image
|
||||
from pulid.pipeline_flux import PuLIDPipeline
|
||||
|
||||
if torch.cuda.is_available():
|
||||
device, onnx_provider = "cuda", "gpu"
|
||||
else:
|
||||
device, onnx_provider = "cpu", "cpu"
|
||||
|
||||
print(f"device={device}", flush=True)
|
||||
|
||||
# PuLIDPipeline only attaches pulid_ca attributes to `dit` during
|
||||
# construction; get_id_embedding() never runs Flux, so a dummy object is
|
||||
# enough and avoids importing/building a Flux skeleton.
|
||||
print("instantiating PuLIDPipeline with a dummy Flux object", flush=True)
|
||||
dit = SimpleNamespace()
|
||||
pulid = PuLIDPipeline(dit=dit,
|
||||
device=device,
|
||||
weight_dtype=torch.bfloat16,
|
||||
onnx_provider=onnx_provider)
|
||||
|
||||
print(f"loading PuLID weights from {pulid_weights}", flush=True)
|
||||
pulid.load_pretrain(pretrain_path=pulid_weights, version="v0.9.1")
|
||||
|
||||
print(f"extracting ID embedding from {portrait_path}", flush=True)
|
||||
face_img = np.array(Image.open(portrait_path).convert("RGB"))
|
||||
id_embedding, _ = pulid.get_id_embedding(face_img)
|
||||
print(f"id embedding shape={tuple(id_embedding.shape)} dtype={id_embedding.dtype}",
|
||||
flush=True)
|
||||
|
||||
if id_embedding.ndim == 3 and id_embedding.shape[0] == 1:
|
||||
id_embedding = id_embedding[0]
|
||||
return id_embedding
|
||||
|
||||
|
||||
def write_embd(tensor, out_path: str, dtype_choice: str) -> None:
|
||||
import gguf
|
||||
import torch
|
||||
|
||||
if tensor.ndim != 2:
|
||||
raise ValueError(f"expected (num_tokens, token_dim); got {tuple(tensor.shape)}")
|
||||
num_tokens, token_dim = tensor.shape
|
||||
|
||||
os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
|
||||
|
||||
writer = gguf.GGUFWriter(out_path, arch="pulid")
|
||||
writer.add_uint32("pulid.version", 1)
|
||||
|
||||
if dtype_choice == "fp16":
|
||||
arr = tensor.to(torch.float16).contiguous().cpu().numpy()
|
||||
writer.add_tensor("pulid_id", arr)
|
||||
elif dtype_choice == "fp32":
|
||||
arr = tensor.to(torch.float32).contiguous().cpu().numpy()
|
||||
writer.add_tensor("pulid_id", arr)
|
||||
elif dtype_choice == "bf16":
|
||||
raw = tensor.to(torch.bfloat16).contiguous().view(torch.uint16).cpu().numpy()
|
||||
writer.add_tensor("pulid_id", raw,
|
||||
raw_shape=(int(num_tokens), int(token_dim)),
|
||||
raw_dtype=gguf.GGMLQuantizationType.BF16)
|
||||
else:
|
||||
raise ValueError(f"unknown --dtype {dtype_choice}")
|
||||
|
||||
writer.write_header_to_file()
|
||||
writer.write_kv_data_to_file()
|
||||
writer.write_tensors_to_file()
|
||||
writer.close()
|
||||
|
||||
print(f"wrote {out_path}: gguf, tensor pulid_id [{token_dim}, {num_tokens}] {dtype_choice}",
|
||||
flush=True)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(
|
||||
description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument("--portrait", required=True,
|
||||
help="Path to the source portrait image (JPG/PNG).")
|
||||
ap.add_argument("--pulid-weights", required=True,
|
||||
help="Path to pulid_flux_v0.9.x.safetensors.")
|
||||
ap.add_argument("--out", required=True,
|
||||
help="Output path for the .pulidembd binary.")
|
||||
ap.add_argument("--dtype", default="fp16",
|
||||
choices=["fp16", "bf16", "fp32"],
|
||||
help="Storage dtype (default fp16; produces ~131 KB).")
|
||||
args = ap.parse_args()
|
||||
|
||||
if not os.path.exists(args.portrait):
|
||||
print(f"ERROR: portrait not found at {args.portrait}", file=sys.stderr)
|
||||
return 2
|
||||
if not os.path.exists(args.pulid_weights):
|
||||
print(f"ERROR: PuLID weights not found at {args.pulid_weights}", file=sys.stderr)
|
||||
return 3
|
||||
|
||||
embedding = extract(args.portrait, args.pulid_weights)
|
||||
write_embd(embedding, args.out, args.dtype)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Remove UTF-8 BOMs from files under a directory.
|
||||
|
||||
By default this scans the current working directory recursively and skips
|
||||
repository areas that should not be touched by ordinary maintenance scripts.
|
||||
Only files whose first three bytes are the UTF-8 BOM are rewritten.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
UTF8_BOM = b"\xef\xbb\xbf"
|
||||
|
||||
DEFAULT_EXCLUDED_DIR_NAMES = {
|
||||
".git",
|
||||
".hg",
|
||||
".svn",
|
||||
".mypy_cache",
|
||||
".pytest_cache",
|
||||
"__pycache__",
|
||||
"test",
|
||||
}
|
||||
|
||||
DEFAULT_EXCLUDED_DIR_PREFIXES = {
|
||||
"build",
|
||||
}
|
||||
|
||||
DEFAULT_EXCLUDED_REL_DIRS = {
|
||||
"examples/server/frontend",
|
||||
"ggml",
|
||||
"models",
|
||||
"src/vocab",
|
||||
"thirdparty",
|
||||
}
|
||||
|
||||
|
||||
def rel_posix(path: Path, root: Path) -> str:
|
||||
try:
|
||||
return path.relative_to(root).as_posix()
|
||||
except ValueError:
|
||||
return path.as_posix()
|
||||
|
||||
|
||||
def should_skip_dir(
|
||||
path: Path,
|
||||
root: Path,
|
||||
excluded_rel_dirs: set[str],
|
||||
excluded_names: set[str],
|
||||
excluded_prefixes: set[str],
|
||||
) -> bool:
|
||||
rel = rel_posix(path, root)
|
||||
return (
|
||||
path.name in excluded_names
|
||||
or rel in excluded_rel_dirs
|
||||
or any(path.name.startswith(prefix) for prefix in excluded_prefixes)
|
||||
)
|
||||
|
||||
|
||||
def iter_files(
|
||||
root: Path,
|
||||
recursive: bool,
|
||||
excluded_rel_dirs: set[str],
|
||||
excluded_names: set[str],
|
||||
excluded_prefixes: set[str],
|
||||
follow_symlinks: bool,
|
||||
):
|
||||
if recursive:
|
||||
for dirpath, dirnames, filenames in os.walk(root, followlinks=follow_symlinks):
|
||||
current_dir = Path(dirpath)
|
||||
dirnames[:] = [
|
||||
name
|
||||
for name in dirnames
|
||||
if not should_skip_dir(
|
||||
current_dir / name,
|
||||
root,
|
||||
excluded_rel_dirs,
|
||||
excluded_names,
|
||||
excluded_prefixes,
|
||||
)
|
||||
]
|
||||
for filename in filenames:
|
||||
path = current_dir / filename
|
||||
if path.is_symlink() and not follow_symlinks:
|
||||
continue
|
||||
yield path
|
||||
else:
|
||||
for path in root.iterdir():
|
||||
if path.is_file() and (follow_symlinks or not path.is_symlink()):
|
||||
yield path
|
||||
|
||||
|
||||
def has_utf8_bom(path: Path) -> bool:
|
||||
with path.open("rb") as f:
|
||||
return f.read(len(UTF8_BOM)) == UTF8_BOM
|
||||
|
||||
|
||||
def strip_utf8_bom(path: Path) -> None:
|
||||
tmp_path = None
|
||||
try:
|
||||
with path.open("rb") as src:
|
||||
if src.read(len(UTF8_BOM)) != UTF8_BOM:
|
||||
return
|
||||
|
||||
fd, tmp_name = tempfile.mkstemp(
|
||||
prefix=f".{path.name}.",
|
||||
suffix=".tmp",
|
||||
dir=str(path.parent),
|
||||
)
|
||||
tmp_path = Path(tmp_name)
|
||||
with os.fdopen(fd, "wb") as dst:
|
||||
shutil.copyfileobj(src, dst, length=1024 * 1024)
|
||||
|
||||
shutil.copystat(path, tmp_path, follow_symlinks=False)
|
||||
os.replace(tmp_path, path)
|
||||
tmp_path = None
|
||||
finally:
|
||||
if tmp_path is not None:
|
||||
try:
|
||||
tmp_path.unlink()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Scan files and convert UTF-8 BOM files to UTF-8 without BOM.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"root",
|
||||
nargs="?",
|
||||
default=".",
|
||||
help="Directory to scan. Defaults to the current directory.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Only list files that would be converted.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-recursive",
|
||||
action="store_true",
|
||||
help="Only scan files directly under root.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-repo-excluded",
|
||||
action="store_true",
|
||||
help="Do not skip default repository excluded directories.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--exclude-dir",
|
||||
action="append",
|
||||
default=[],
|
||||
metavar="DIR",
|
||||
help="Additional directory name or root-relative path to skip. Can be used multiple times.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--follow-symlinks",
|
||||
action="store_true",
|
||||
help="Follow symlinked directories and files.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q",
|
||||
"--quiet",
|
||||
action="store_true",
|
||||
help="Only print the final summary.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
root = Path(args.root).resolve()
|
||||
|
||||
if not root.is_dir():
|
||||
print(f"error: not a directory: {root}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
excluded_names = set()
|
||||
excluded_rel_dirs = set()
|
||||
excluded_prefixes = set()
|
||||
if not args.include_repo_excluded:
|
||||
excluded_names.update(DEFAULT_EXCLUDED_DIR_NAMES)
|
||||
excluded_rel_dirs.update(DEFAULT_EXCLUDED_REL_DIRS)
|
||||
excluded_prefixes.update(DEFAULT_EXCLUDED_DIR_PREFIXES)
|
||||
|
||||
for item in args.exclude_dir:
|
||||
normalized = Path(item).as_posix().strip("/")
|
||||
if "/" in normalized:
|
||||
excluded_rel_dirs.add(normalized)
|
||||
else:
|
||||
excluded_names.add(normalized)
|
||||
|
||||
scanned = 0
|
||||
converted = 0
|
||||
errors = 0
|
||||
|
||||
for path in iter_files(
|
||||
root=root,
|
||||
recursive=not args.no_recursive,
|
||||
excluded_rel_dirs=excluded_rel_dirs,
|
||||
excluded_names=excluded_names,
|
||||
excluded_prefixes=excluded_prefixes,
|
||||
follow_symlinks=args.follow_symlinks,
|
||||
):
|
||||
scanned += 1
|
||||
try:
|
||||
if not has_utf8_bom(path):
|
||||
continue
|
||||
converted += 1
|
||||
rel = rel_posix(path, root)
|
||||
if args.dry_run:
|
||||
if not args.quiet:
|
||||
print(f"would convert: {rel}")
|
||||
else:
|
||||
strip_utf8_bom(path)
|
||||
if not args.quiet:
|
||||
print(f"converted: {rel}")
|
||||
except OSError as exc:
|
||||
errors += 1
|
||||
print(f"error: {rel_posix(path, root)}: {exc}", file=sys.stderr)
|
||||
|
||||
action = "would convert" if args.dry_run else "converted"
|
||||
print(f"scanned {scanned} file(s), {action} {converted}, errors {errors}")
|
||||
return 1 if errors else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef __SD_CONDITIONING_CONDITIONER_HPP__
|
||||
#ifndef __SD_CONDITIONING_CONDITIONER_HPP__
|
||||
#define __SD_CONDITIONING_CONDITIONER_HPP__
|
||||
|
||||
#include <cmath>
|
||||
@@ -142,8 +142,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner {
|
||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
: version(version), tokenizer(sd_version_is_sd2(version) ? 0 : 49407) {
|
||||
for (const auto& kv : orig_embedding_map) {
|
||||
std::string name = kv.first;
|
||||
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
std::string name = normalize_embedding_name(kv.first);
|
||||
embedding_map[name] = kv.second;
|
||||
tokenizer.add_special_token(name);
|
||||
}
|
||||
@@ -278,17 +277,23 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner {
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string normalize_embedding_name(std::string name) {
|
||||
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
return name;
|
||||
}
|
||||
|
||||
bool append_embedding_tokens(std::string str, std::vector<int32_t>& bpe_tokens) {
|
||||
std::string name = normalize_embedding_name(std::move(str));
|
||||
auto iter = embedding_map.find(name);
|
||||
if (iter == embedding_map.end()) {
|
||||
return false;
|
||||
}
|
||||
return load_embedding(name, iter->second, bpe_tokens);
|
||||
}
|
||||
|
||||
std::vector<int> convert_token_to_id(std::string text) {
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
auto iter = embedding_map.find(str);
|
||||
if (iter == embedding_map.end()) {
|
||||
return false;
|
||||
}
|
||||
std::string embedding_path = iter->second;
|
||||
if (load_embedding(str, embedding_path, bpe_tokens)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return append_embedding_tokens(str, bpe_tokens);
|
||||
};
|
||||
std::vector<int> curr_tokens = tokenizer.encode(text, on_new_token_cb);
|
||||
return curr_tokens;
|
||||
@@ -315,15 +320,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner {
|
||||
}
|
||||
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
auto iter = embedding_map.find(str);
|
||||
if (iter == embedding_map.end()) {
|
||||
return false;
|
||||
}
|
||||
std::string embedding_path = iter->second;
|
||||
if (load_embedding(str, embedding_path, bpe_tokens)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return append_embedding_tokens(str, bpe_tokens);
|
||||
};
|
||||
|
||||
std::vector<int> tokens;
|
||||
@@ -1521,7 +1518,7 @@ struct LLMEmbedder : public Conditioner {
|
||||
arch = LLM::LLMArch::GPT_OSS_20B;
|
||||
} else if (sd_version_is_pid(version)) {
|
||||
arch = LLM::LLMArch::GEMMA2_2B;
|
||||
} else if (sd_version_is_ideogram4(version)) {
|
||||
} else if (sd_version_is_ideogram4(version) || sd_version_is_boogu_image(version) || sd_version_is_sefi_image(version) || sd_version_is_krea2(version)) {
|
||||
arch = LLM::LLMArch::QWEN3_VL;
|
||||
} else if (sd_version_is_z_image(version) || version == VERSION_OVIS_IMAGE || version == VERSION_FLUX2_KLEIN) {
|
||||
arch = LLM::LLMArch::QWEN3;
|
||||
@@ -1781,6 +1778,76 @@ struct LLMEmbedder : public Conditioner {
|
||||
|
||||
prompt += "<|im_end|>\n<|im_start|>assistant\n";
|
||||
}
|
||||
} else if (sd_version_is_boogu_image(version)) {
|
||||
prompt_template_encode_start_idx = 0;
|
||||
|
||||
const std::string t2i_system_prompt =
|
||||
"You are a helpful assistant that generates high-quality images based on user instructions. The instructions are as follows.";
|
||||
const std::string edit_system_prompt =
|
||||
"Describe the key features of the input image (color, shape, size, texture, objects, background), then explain how the user's text instruction should alter or modify the image. Generate a new image that meets the user's requirements while maintaining consistency with the original input where appropriate.";
|
||||
const bool has_ref_images = llm->enable_vision && conditioner_params.ref_images != nullptr && !conditioner_params.ref_images->empty();
|
||||
const bool text_empty = conditioner_params.text.find_first_not_of(" \t\r\n") == std::string::npos;
|
||||
|
||||
if (has_ref_images) {
|
||||
LOG_INFO("BooguImageEditPipeline");
|
||||
const std::string prompt_prefix = "<|im_start|>system\n" + edit_system_prompt + "<|im_end|>\n<|im_start|>user\n";
|
||||
std::string img_prompt;
|
||||
const std::string placeholder = "<|image_pad|>";
|
||||
|
||||
for (int i = 0; i < conditioner_params.ref_images->size(); i++) {
|
||||
const auto& image = (*conditioner_params.ref_images)[i];
|
||||
double factor = llm->config.vision.patch_size * llm->config.vision.spatial_merge_size;
|
||||
int height = static_cast<int>(image.shape()[1]);
|
||||
int width = static_cast<int>(image.shape()[0]);
|
||||
double beta = std::sqrt((384.0 * 384.0) / (static_cast<double>(height) * static_cast<double>(width)));
|
||||
int h_bar = std::max(static_cast<int>(factor),
|
||||
static_cast<int>(std::round(height * beta / factor)) * static_cast<int>(factor));
|
||||
int w_bar = std::max(static_cast<int>(factor),
|
||||
static_cast<int>(std::round(width * beta / factor)) * static_cast<int>(factor));
|
||||
|
||||
LOG_DEBUG("resize conditioner ref image %d from %dx%d to %dx%d", i, height, width, h_bar, w_bar);
|
||||
|
||||
auto resized_image = clip_preprocess(image, w_bar, h_bar);
|
||||
auto image_embed = llm->encode_image(n_threads, resized_image, false, true, true);
|
||||
GGML_ASSERT(!image_embed.empty());
|
||||
|
||||
std::string image_prefix = prompt_prefix + img_prompt + "<|vision_start|>";
|
||||
int image_embed_idx = static_cast<int>(tokenizer->encode(image_prefix, nullptr).size());
|
||||
image_embeds.emplace_back(image_embed_idx, image_embed);
|
||||
|
||||
img_prompt += "<|vision_start|>";
|
||||
int64_t num_image_tokens = image_embed.shape()[1];
|
||||
img_prompt.reserve(img_prompt.size() + static_cast<size_t>(num_image_tokens) * placeholder.size() + 32);
|
||||
for (int j = 0; j < num_image_tokens; j++) {
|
||||
img_prompt += placeholder;
|
||||
}
|
||||
img_prompt += "<|vision_end|>";
|
||||
}
|
||||
|
||||
prompt = prompt_prefix + img_prompt;
|
||||
prompt_attn_range.first = static_cast<int>(prompt.size());
|
||||
prompt += conditioner_params.text;
|
||||
prompt_attn_range.second = static_cast<int>(prompt.size());
|
||||
prompt += "<|im_end|>\n";
|
||||
} else {
|
||||
const std::string& system_prompt = text_empty ? edit_system_prompt : t2i_system_prompt;
|
||||
prompt = "<|im_start|>system\n" + system_prompt + "<|im_end|>\n<|im_start|>user\n";
|
||||
prompt_attn_range.first = static_cast<int>(prompt.size());
|
||||
prompt += conditioner_params.text;
|
||||
prompt_attn_range.second = static_cast<int>(prompt.size());
|
||||
prompt += "<|im_end|>\n";
|
||||
}
|
||||
} else if (sd_version_is_krea2(version)) {
|
||||
prompt_template_encode_start_idx = 34;
|
||||
out_layers = {2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35};
|
||||
|
||||
prompt = "<|im_start|>system\nDescribe 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";
|
||||
|
||||
prompt_attn_range.first = static_cast<int>(prompt.size());
|
||||
prompt += conditioner_params.text;
|
||||
prompt_attn_range.second = static_cast<int>(prompt.size());
|
||||
|
||||
prompt += "<|im_end|>\n<|im_start|>assistant\n";
|
||||
} else if (sd_version_is_longcat(version)) {
|
||||
spell_quotes = true;
|
||||
|
||||
@@ -1930,6 +1997,18 @@ struct LLMEmbedder : public Conditioner {
|
||||
prompt_attn_range.second = static_cast<int>(prompt.size());
|
||||
|
||||
prompt += "<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n";
|
||||
} else if (sd_version_is_sefi_image(version)) {
|
||||
prompt_template_encode_start_idx = 0;
|
||||
min_length = 1024;
|
||||
out_layers = {9, 18, 27};
|
||||
|
||||
prompt = "<|im_start|>user\n";
|
||||
|
||||
prompt_attn_range.first = static_cast<int>(prompt.size());
|
||||
prompt += conditioner_params.text;
|
||||
prompt_attn_range.second = static_cast<int>(prompt.size());
|
||||
|
||||
prompt += "<|im_end|>\n<|im_start|>assistant\n";
|
||||
} else if (version == VERSION_OVIS_IMAGE) {
|
||||
prompt_template_encode_start_idx = 28;
|
||||
min_length = prompt_template_encode_start_idx + 256;
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ bool convert(const char* input_path,
|
||||
model_loader.convert_tensors_name();
|
||||
}
|
||||
|
||||
ggml_type type = (ggml_type)output_type;
|
||||
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);
|
||||
|
||||
|
||||
@@ -1382,7 +1382,16 @@ __STATIC_INLINE__ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
if (!ggml_backend_supports_op(backend, kqv)) {
|
||||
kqv = nullptr;
|
||||
} else {
|
||||
kqv = ggml_view_3d(ctx, kqv, d_head, n_head, L_q, kqv->nb[1], kqv->nb[2], 0);
|
||||
kqv = ggml_view_4d(ctx,
|
||||
kqv,
|
||||
d_head,
|
||||
n_head,
|
||||
L_q,
|
||||
N,
|
||||
kqv->nb[1],
|
||||
kqv->nb[2],
|
||||
kqv->nb[1] * n_head,
|
||||
0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2007,6 +2016,10 @@ protected:
|
||||
}
|
||||
|
||||
bool copy_cache_tensors_to_cache_buffer(const std::unordered_set<std::string>* cache_keep_names = nullptr) {
|
||||
if (cache_tensor_map.empty() && cache_keep_names == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ggml_context* old_cache_ctx = cache_ctx;
|
||||
ggml_backend_buffer_t old_cache_buffer = cache_buffer;
|
||||
cache_ctx = nullptr;
|
||||
|
||||
@@ -45,6 +45,10 @@ static bool is_default_backend_token(const std::string& name) {
|
||||
return lower.empty() || lower == "default" || lower == "auto";
|
||||
}
|
||||
|
||||
static bool is_disk_backend_token(const std::string& name) {
|
||||
return lower_copy(trim_copy(name)) == "disk";
|
||||
}
|
||||
|
||||
static bool parse_backend_module(const std::string& raw_name, SDBackendModule* module) {
|
||||
std::string name = lower_copy(trim_copy(raw_name));
|
||||
name.erase(std::remove(name.begin(), name.end(), '-'), name.end());
|
||||
@@ -200,6 +204,36 @@ void ggml_ext_im_set_f32_1d(const struct ggml_tensor* tensor, int i, float value
|
||||
}
|
||||
}
|
||||
|
||||
bool add_rpc_devices(const std::string& servers) {
|
||||
const std::string in = trim_copy(servers);
|
||||
if (in.empty()) {
|
||||
return true;
|
||||
}
|
||||
auto rpc_servers = split_copy(in, ',');
|
||||
if (rpc_servers.empty()) {
|
||||
LOG_ERROR("invalid RPC servers specification: '%s'", servers.c_str());
|
||||
return false;
|
||||
}
|
||||
ggml_backend_reg_t rpc_reg = ggml_backend_reg_by_name("RPC");
|
||||
if (!rpc_reg) {
|
||||
LOG_ERROR("RPC backend not found, cannot add RPC servers");
|
||||
return false;
|
||||
}
|
||||
typedef ggml_backend_reg_t (*ggml_backend_rpc_add_server_t)(const char* endpoint);
|
||||
ggml_backend_rpc_add_server_t ggml_backend_rpc_add_server_fn = (ggml_backend_rpc_add_server_t)ggml_backend_reg_get_proc_address(rpc_reg, "ggml_backend_rpc_add_server");
|
||||
if (!ggml_backend_rpc_add_server_fn) {
|
||||
LOG_ERROR("RPC backend does not have ggml_backend_rpc_add_server function, cannot add RPC servers");
|
||||
return false;
|
||||
}
|
||||
for (const auto& server : rpc_servers) {
|
||||
LOG_INFO("Adding RPC server: %s", server.c_str());
|
||||
auto reg = ggml_backend_rpc_add_server_fn(server.c_str());
|
||||
// no return value to check for success but should print errors from the RPC backend if it fails to add the server
|
||||
ggml_backend_register(reg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ggml_backend_load_all_once() {
|
||||
// If the registry already has devices and the CPU backend is present,
|
||||
// assume either static registration or explicit host-side preloading has
|
||||
@@ -246,7 +280,7 @@ static std::string get_default_backend_name() {
|
||||
return resolve_first_device_by_type(GGML_BACKEND_DEVICE_TYPE_CPU);
|
||||
}
|
||||
|
||||
static std::string sd_resolve_backend_name(const std::string& name) {
|
||||
std::string sd_backend_resolve_name(const std::string& name) {
|
||||
ggml_backend_load_all_once();
|
||||
std::string requested = trim_copy(name);
|
||||
std::string lower = lower_copy(requested);
|
||||
@@ -284,7 +318,7 @@ static std::string sd_resolve_backend_name(const std::string& name) {
|
||||
}
|
||||
|
||||
static bool backend_name_exists(const std::string& name) {
|
||||
return !sd_resolve_backend_name(name).empty();
|
||||
return !sd_backend_resolve_name(name).empty();
|
||||
}
|
||||
|
||||
static ggml_backend_t init_named_backend(const std::string& name) {
|
||||
@@ -294,7 +328,7 @@ static ggml_backend_t init_named_backend(const std::string& name) {
|
||||
return ggml_backend_init_best();
|
||||
}
|
||||
|
||||
std::string resolved = sd_resolve_backend_name(name);
|
||||
std::string resolved = sd_backend_resolve_name(name);
|
||||
if (resolved.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -504,6 +538,9 @@ ggml_backend_t SDBackendManager::params_backend(SDBackendModule module) {
|
||||
if (name.empty()) {
|
||||
return runtime_backend(module);
|
||||
}
|
||||
if (is_disk_backend_token(name)) {
|
||||
return runtime_backend(module);
|
||||
}
|
||||
return init_cached_backend(name);
|
||||
}
|
||||
|
||||
@@ -515,6 +552,10 @@ bool SDBackendManager::params_backend_is_cpu(SDBackendModule module) {
|
||||
return sd_backend_is_cpu(params_backend(module));
|
||||
}
|
||||
|
||||
bool SDBackendManager::params_backend_is_disk(SDBackendModule module) const {
|
||||
return is_disk_backend_token(params_assignment_.get(module));
|
||||
}
|
||||
|
||||
bool SDBackendManager::runtime_backend_supports_host_buffer(SDBackendModule module) {
|
||||
ggml_backend_t backend = runtime_backend(module);
|
||||
if (backend == nullptr) {
|
||||
@@ -534,10 +575,6 @@ bool SDBackendManager::runtime_backend_supports_host_buffer(SDBackendModule modu
|
||||
|
||||
bool SDBackendManager::init(const char* backend_spec,
|
||||
const char* params_backend_spec,
|
||||
bool offload_params_to_cpu,
|
||||
bool keep_clip_on_cpu,
|
||||
bool keep_vae_on_cpu,
|
||||
bool keep_control_net_on_cpu,
|
||||
std::string* error) {
|
||||
reset();
|
||||
|
||||
@@ -548,31 +585,21 @@ bool SDBackendManager::init(const char* backend_spec,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (runtime_assignment_.empty()) {
|
||||
if (keep_clip_on_cpu) {
|
||||
runtime_assignment_.set_module(SDBackendModule::TE, "cpu");
|
||||
}
|
||||
if (keep_vae_on_cpu) {
|
||||
runtime_assignment_.set_module(SDBackendModule::VAE, "cpu");
|
||||
}
|
||||
if (keep_control_net_on_cpu) {
|
||||
runtime_assignment_.set_module(SDBackendModule::CONTROL_NET, "cpu");
|
||||
}
|
||||
}
|
||||
|
||||
if (params_assignment_.empty() && offload_params_to_cpu) {
|
||||
params_assignment_.set_default("cpu");
|
||||
}
|
||||
|
||||
return validate(error);
|
||||
}
|
||||
|
||||
bool SDBackendManager::validate(std::string* error) const {
|
||||
auto validate_name = [&](const std::string& name) -> bool {
|
||||
auto validate_runtime_name = [&](const std::string& name) -> bool {
|
||||
if (is_default_backend_token(name)) {
|
||||
return true;
|
||||
}
|
||||
if (!sd_resolve_backend_name(name).empty()) {
|
||||
if (is_disk_backend_token(name)) {
|
||||
if (error != nullptr) {
|
||||
*error = "backend 'disk' is only supported by params_backend";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!sd_backend_resolve_name(name).empty()) {
|
||||
return true;
|
||||
}
|
||||
if (error != nullptr) {
|
||||
@@ -580,18 +607,24 @@ bool SDBackendManager::validate(std::string* error) const {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
auto validate_params_name = [&](const std::string& name) -> bool {
|
||||
if (is_disk_backend_token(name)) {
|
||||
return true;
|
||||
}
|
||||
return validate_runtime_name(name);
|
||||
};
|
||||
|
||||
if (!validate_name(runtime_assignment_.default_name) ||
|
||||
!validate_name(params_assignment_.default_name)) {
|
||||
if (!validate_runtime_name(runtime_assignment_.default_name) ||
|
||||
!validate_params_name(params_assignment_.default_name)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& kv : runtime_assignment_.module_names) {
|
||||
if (!validate_name(kv.second)) {
|
||||
if (!validate_runtime_name(kv.second)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& kv : params_assignment_.module_names) {
|
||||
if (!validate_name(kv.second)) {
|
||||
if (!validate_params_name(kv.second)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -599,7 +632,7 @@ bool SDBackendManager::validate(std::string* error) const {
|
||||
}
|
||||
|
||||
ggml_backend_t SDBackendManager::init_cached_backend(const std::string& name) {
|
||||
std::string resolved = sd_resolve_backend_name(name);
|
||||
std::string resolved = sd_backend_resolve_name(name);
|
||||
std::string key = lower_copy(resolved);
|
||||
ggml_backend_t backend = nullptr;
|
||||
|
||||
|
||||
@@ -51,10 +51,6 @@ public:
|
||||
|
||||
bool init(const char* backend_spec,
|
||||
const char* params_backend_spec,
|
||||
bool offload_params_to_cpu,
|
||||
bool keep_clip_on_cpu,
|
||||
bool keep_vae_on_cpu,
|
||||
bool keep_control_net_on_cpu,
|
||||
std::string* error);
|
||||
void reset();
|
||||
|
||||
@@ -63,6 +59,7 @@ public:
|
||||
|
||||
bool runtime_backend_is_cpu(SDBackendModule module);
|
||||
bool params_backend_is_cpu(SDBackendModule module);
|
||||
bool params_backend_is_disk(SDBackendModule module) const;
|
||||
bool runtime_backend_supports_host_buffer(SDBackendModule module);
|
||||
|
||||
private:
|
||||
@@ -74,6 +71,8 @@ bool sd_backend_is(ggml_backend_t backend, const std::string& name);
|
||||
bool sd_backend_is_cpu(ggml_backend_t backend);
|
||||
ggml_backend_t sd_backend_cpu_init();
|
||||
bool sd_backend_cpu_set_n_threads(ggml_backend_t backend_cpu, int n_threads);
|
||||
std::string sd_backend_resolve_name(const std::string& name);
|
||||
const char* sd_backend_module_name(SDBackendModule module);
|
||||
void ggml_ext_im_set_f32_1d(const struct ggml_tensor* tensor, int i, float value);
|
||||
bool add_rpc_devices(const std::string& servers);
|
||||
#endif // __SD_CORE_GGML_EXTEND_BACKEND_H__
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "core/ggml_graph_cut.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <set>
|
||||
@@ -8,6 +10,7 @@
|
||||
#include <stack>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "core/ggml_extend_backend.h"
|
||||
#include "core/util.h"
|
||||
#include "ggml-alloc.h"
|
||||
#include "ggml-backend.h"
|
||||
@@ -83,6 +86,157 @@ namespace sd::ggml_graph_cut {
|
||||
segment.output_bytes;
|
||||
}
|
||||
|
||||
static std::string lower_ascii_copy(std::string value) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) {
|
||||
return static_cast<char>(std::tolower(c));
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
static std::string normalize_backend_budget_key(const std::string& value) {
|
||||
return lower_ascii_copy(trim(value));
|
||||
}
|
||||
|
||||
static bool is_default_max_vram_key(const std::string& key) {
|
||||
std::string normalized = normalize_backend_budget_key(key);
|
||||
return normalized == "all" || normalized == "default" || normalized == "*";
|
||||
}
|
||||
|
||||
static bool parse_max_vram_budget_value(const std::string& text, float* value, std::string* error) {
|
||||
float parsed = 0.f;
|
||||
if (!parse_strict_float(text, parsed) || !std::isfinite(parsed)) {
|
||||
if (error != nullptr) {
|
||||
*error = "invalid --max-vram value '" + text + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
*value = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<std::string> backend_budget_keys(ggml_backend_t backend) {
|
||||
std::vector<std::string> keys;
|
||||
if (backend == nullptr) {
|
||||
return keys;
|
||||
}
|
||||
|
||||
ggml_backend_dev_t dev = ggml_backend_get_device(backend);
|
||||
if (dev != nullptr) {
|
||||
keys.push_back(normalize_backend_budget_key(ggml_backend_dev_name(dev)));
|
||||
}
|
||||
const char* backend_name = ggml_backend_name(backend);
|
||||
if (backend_name != nullptr) {
|
||||
keys.push_back(normalize_backend_budget_key(backend_name));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
void MaxVramAssignment::reset(float fallback_gib) {
|
||||
default_gib = fallback_gib;
|
||||
backend_gib.clear();
|
||||
resolved_backend_bytes.clear();
|
||||
}
|
||||
|
||||
bool MaxVramAssignment::parse(const std::string& raw_spec, std::string* error) {
|
||||
const std::string in = trim(raw_spec);
|
||||
if (in.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const std::string& raw_part : split_string(in, ',')) {
|
||||
const std::string part = trim(raw_part);
|
||||
if (part.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t eq = part.find('=');
|
||||
if (eq == std::string::npos) {
|
||||
float value = 0.f;
|
||||
if (!parse_max_vram_budget_value(part, &value, error)) {
|
||||
return false;
|
||||
}
|
||||
default_gib = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string key = trim(part.substr(0, eq));
|
||||
const std::string value_text = trim(part.substr(eq + 1));
|
||||
if (key.empty() || value_text.empty()) {
|
||||
if (error != nullptr) {
|
||||
*error = "invalid --max-vram assignment '" + part + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float value = 0.f;
|
||||
if (!parse_max_vram_budget_value(value_text, &value, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_default_max_vram_key(key)) {
|
||||
default_gib = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string backend_key = trim(key);
|
||||
if (backend_key.empty()) {
|
||||
if (error != nullptr) {
|
||||
*error = "invalid --max-vram backend key in '" + part + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
backend_gib[backend_key] = value;
|
||||
}
|
||||
resolved_backend_bytes.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MaxVramAssignment::canonicalize_backend_keys(std::string* error) {
|
||||
if (backend_gib.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, float> normalized;
|
||||
for (const auto& kv : backend_gib) {
|
||||
std::string resolved = sd_backend_resolve_name(kv.first);
|
||||
if (resolved.empty()) {
|
||||
if (error != nullptr) {
|
||||
*error = "unknown --max-vram backend '" + kv.first + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
normalized[normalize_backend_budget_key(resolved)] = kv.second;
|
||||
}
|
||||
backend_gib = std::move(normalized);
|
||||
resolved_backend_bytes.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t MaxVramAssignment::bytes_for_backend(ggml_backend_t backend) {
|
||||
std::vector<std::string> keys = backend_budget_keys(backend);
|
||||
const std::string cache_key = keys.empty() ? std::string("<none>") : keys.front();
|
||||
auto cached = resolved_backend_bytes.find(cache_key);
|
||||
if (cached != resolved_backend_bytes.end()) {
|
||||
return cached->second;
|
||||
}
|
||||
|
||||
float budget_gib = default_gib;
|
||||
if (!backend_gib.empty()) {
|
||||
for (const std::string& key : keys) {
|
||||
auto backend_it = backend_gib.find(key);
|
||||
if (backend_it != backend_gib.end()) {
|
||||
budget_gib = backend_it->second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const float resolved_gib = resolve_max_vram_gib(budget_gib, backend);
|
||||
const size_t bytes = max_vram_gib_to_bytes(resolved_gib);
|
||||
resolved_backend_bytes[cache_key] = bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
size_t max_vram_gib_to_bytes(float max_vram) {
|
||||
if (max_vram <= 0.f) {
|
||||
return 0;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
@@ -68,6 +69,17 @@ namespace sd::ggml_graph_cut {
|
||||
|
||||
static constexpr const char* GGML_RUNNER_CUT_PREFIX = "ggml_runner_cut:";
|
||||
|
||||
struct MaxVramAssignment {
|
||||
float default_gib = 0.f;
|
||||
std::unordered_map<std::string, float> backend_gib;
|
||||
std::unordered_map<std::string, size_t> resolved_backend_bytes;
|
||||
|
||||
void reset(float fallback_gib);
|
||||
bool parse(const std::string& raw_spec, std::string* error);
|
||||
bool canonicalize_backend_keys(std::string* error);
|
||||
size_t bytes_for_backend(ggml_backend_t backend);
|
||||
};
|
||||
|
||||
bool is_graph_cut_tensor(const ggml_tensor* tensor);
|
||||
std::string make_graph_cut_name(const std::string& group, const std::string& output);
|
||||
void mark_graph_cut(ggml_tensor* tensor, const std::string& group, const std::string& output);
|
||||
|
||||
@@ -406,6 +406,15 @@ std::vector<std::string> split_string(const std::string& str, char delimiter) {
|
||||
return result;
|
||||
}
|
||||
|
||||
ggml_type sd_type_to_ggml_type(sd_type_t sdtype) {
|
||||
const int type_value = static_cast<int>(sdtype);
|
||||
if (type_value < std::min<int>(SD_TYPE_COUNT, GGML_TYPE_COUNT)) {
|
||||
return static_cast<ggml_type>(type_value);
|
||||
} else {
|
||||
return GGML_TYPE_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
KeyValueArgs parse_key_value_args(const char* args, const char* context) {
|
||||
KeyValueArgs pairs;
|
||||
|
||||
|
||||
@@ -80,6 +80,8 @@ void pretty_bytes_progress(int step, int steps, uint64_t bytes_processed, float
|
||||
|
||||
void log_printf(sd_log_level_t level, const char* file, int line, const char* format, ...);
|
||||
|
||||
ggml_type sd_type_to_ggml_type(sd_type_t sdtype);
|
||||
|
||||
std::string trim(const std::string& s);
|
||||
|
||||
std::vector<std::pair<std::string, float>> parse_prompt_attention(const std::string& text);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "conditioning/conditioner.hpp"
|
||||
#include "core/ggml_extend_backend.h"
|
||||
#include "model/diffusion/model.hpp"
|
||||
#include "model_loader.h"
|
||||
#include "model_manager.h"
|
||||
#include "stable-diffusion.h"
|
||||
@@ -30,6 +31,7 @@ struct GenerationExtensionConditionContext {
|
||||
Conditioner* conditioner;
|
||||
ConditionerParams& condition_params;
|
||||
const sd_pm_params_t& pm_params;
|
||||
const sd_pulid_params_t& pulid_params;
|
||||
int n_threads;
|
||||
int total_steps;
|
||||
};
|
||||
@@ -56,8 +58,20 @@ struct GenerationExtension {
|
||||
const SDCondition& condition) const {
|
||||
return condition;
|
||||
}
|
||||
|
||||
// Called in the denoise loop for each enabled extension, after the per-step
|
||||
// DiffusionParams (including its version-specific `extra`) has been built,
|
||||
// but before diffusion_model->compute(). Lets an extension feed data into
|
||||
// the diffusion forward that the conditioning-side hooks can't reach -- it
|
||||
// can set/override fields on `params` (typically the architecture-specific
|
||||
// `params.extra`, e.g. a guidance tensor, control payload, or an identity
|
||||
// embedding for an adapter that injects inside the model's blocks). The
|
||||
// extension targets whichever `extra` variant matches the active model.
|
||||
// Mutates `params` only, never the extension. Default no-op.
|
||||
virtual void before_diffusion(DiffusionParams& /*params*/, int /*step*/) const {}
|
||||
};
|
||||
|
||||
std::shared_ptr<GenerationExtension> create_photomaker_extension();
|
||||
std::shared_ptr<GenerationExtension> create_pulid_extension();
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "extensions/generation_extension.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <variant>
|
||||
|
||||
#include "core/tensor_ggml.hpp"
|
||||
#include "core/util.h"
|
||||
#include "gguf.h"
|
||||
|
||||
static sd::Tensor<float> load_pulid_id_embedding(const char* path) {
|
||||
sd::Tensor<float> empty;
|
||||
if (path == nullptr || strlen(path) == 0) {
|
||||
return empty;
|
||||
}
|
||||
|
||||
struct ggml_context* ctx_data = nullptr;
|
||||
struct gguf_init_params gp = {/*.no_alloc =*/false, /*.ctx =*/&ctx_data};
|
||||
struct gguf_context* gguf_ctx = gguf_init_from_file(path, gp);
|
||||
if (gguf_ctx == nullptr || ctx_data == nullptr) {
|
||||
LOG_WARN("PuLID id-embedding: cannot read gguf '%s'", path);
|
||||
if (gguf_ctx != nullptr)
|
||||
gguf_free(gguf_ctx);
|
||||
if (ctx_data != nullptr)
|
||||
ggml_free(ctx_data);
|
||||
return empty;
|
||||
}
|
||||
|
||||
struct ggml_tensor* t = ggml_get_tensor(ctx_data, "pulid_id");
|
||||
if (t == nullptr) {
|
||||
LOG_WARN("PuLID id-embedding: no 'pulid_id' tensor in '%s'", path);
|
||||
gguf_free(gguf_ctx);
|
||||
ggml_free(ctx_data);
|
||||
return empty;
|
||||
}
|
||||
|
||||
const int64_t token_dim = t->ne[0];
|
||||
const int64_t num_tokens = t->ne[1];
|
||||
if (token_dim <= 0 || num_tokens <= 0 || token_dim > 65536 || num_tokens > 1024 ||
|
||||
t->ne[2] != 1 || t->ne[3] != 1) {
|
||||
LOG_WARN("PuLID id-embedding: implausible shape [%lld, %lld] in '%s'",
|
||||
(long long)token_dim, (long long)num_tokens, path);
|
||||
gguf_free(gguf_ctx);
|
||||
ggml_free(ctx_data);
|
||||
return empty;
|
||||
}
|
||||
|
||||
const size_t n_elem = (size_t)token_dim * (size_t)num_tokens;
|
||||
sd::Tensor<float> out({token_dim, num_tokens, 1});
|
||||
float* dst = out.data();
|
||||
if (t->type == GGML_TYPE_F32) {
|
||||
memcpy(dst, t->data, n_elem * sizeof(float));
|
||||
} else if (t->type == GGML_TYPE_F16) {
|
||||
const ggml_fp16_t* src = reinterpret_cast<const ggml_fp16_t*>(t->data);
|
||||
for (size_t i = 0; i < n_elem; i++) {
|
||||
dst[i] = ggml_fp16_to_fp32(src[i]);
|
||||
}
|
||||
} else if (t->type == GGML_TYPE_BF16) {
|
||||
const ggml_bf16_t* src = reinterpret_cast<const ggml_bf16_t*>(t->data);
|
||||
for (size_t i = 0; i < n_elem; i++) {
|
||||
dst[i] = ggml_bf16_to_fp32(src[i]);
|
||||
}
|
||||
} else {
|
||||
LOG_WARN("PuLID id-embedding: unsupported tensor type %s in '%s'",
|
||||
ggml_type_name(t->type), path);
|
||||
gguf_free(gguf_ctx);
|
||||
ggml_free(ctx_data);
|
||||
return empty;
|
||||
}
|
||||
|
||||
LOG_INFO("PuLID id-embedding: loaded [%lld, %lld] type=%s from '%s'",
|
||||
(long long)token_dim, (long long)num_tokens, ggml_type_name(t->type), path);
|
||||
gguf_free(gguf_ctx);
|
||||
ggml_free(ctx_data);
|
||||
return out;
|
||||
}
|
||||
|
||||
struct PuLIDExtension : public GenerationExtension {
|
||||
bool enabled = false;
|
||||
sd::Tensor<float> id_embedding;
|
||||
float id_weight = 1.0f;
|
||||
|
||||
const char* name() const override {
|
||||
return "pulid";
|
||||
}
|
||||
|
||||
bool is_enabled() const override {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
bool init(const GenerationExtensionInitContext& ctx) override {
|
||||
enabled = strlen(SAFE_STR(ctx.params->pulid_weights_path)) > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void reset_runtime_condition() override {
|
||||
id_embedding = {};
|
||||
id_weight = 1.0f;
|
||||
}
|
||||
|
||||
bool prepare_condition(GenerationExtensionConditionContext& ctx) override {
|
||||
reset_runtime_condition();
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
id_embedding = load_pulid_id_embedding(ctx.pulid_params.id_embedding_path);
|
||||
id_weight = ctx.pulid_params.id_weight;
|
||||
return false; // PuLID does not modify the conditioning
|
||||
}
|
||||
|
||||
void before_diffusion(DiffusionParams& params, int /*step*/) const override {
|
||||
if (!enabled || id_embedding.empty()) {
|
||||
return;
|
||||
}
|
||||
if (auto* flux_extra = std::get_if<FluxDiffusionExtra>(¶ms.extra)) {
|
||||
flux_extra->pulid_id = &id_embedding;
|
||||
flux_extra->pulid_id_weight = id_weight;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<GenerationExtension> create_pulid_extension() {
|
||||
return std::make_shared<PuLIDExtension>();
|
||||
}
|
||||
+43
-2
@@ -42,12 +42,15 @@ enum SDVersion {
|
||||
VERSION_LTXAV,
|
||||
VERSION_HIDREAM_O1,
|
||||
VERSION_Z_IMAGE,
|
||||
VERSION_BOOGU_IMAGE,
|
||||
VERSION_OVIS_IMAGE,
|
||||
VERSION_ERNIE_IMAGE,
|
||||
VERSION_LENS,
|
||||
VERSION_LONGCAT,
|
||||
VERSION_PID,
|
||||
VERSION_IDEOGRAM4,
|
||||
VERSION_SEFI_IMAGE,
|
||||
VERSION_KREA2,
|
||||
VERSION_ESRGAN,
|
||||
VERSION_COUNT,
|
||||
};
|
||||
@@ -143,6 +146,13 @@ static inline bool sd_version_is_z_image(SDVersion version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool sd_version_is_boogu_image(SDVersion version) {
|
||||
if (version == VERSION_BOOGU_IMAGE) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool sd_version_is_longcat(SDVersion version) {
|
||||
if (version == VERSION_LONGCAT) {
|
||||
return true;
|
||||
@@ -178,8 +188,36 @@ static inline bool sd_version_is_ideogram4(SDVersion version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool sd_version_is_sefi_image(SDVersion version) {
|
||||
if (version == VERSION_SEFI_IMAGE) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool sd_version_is_krea2(SDVersion version) {
|
||||
if (version == VERSION_KREA2) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool sd_version_uses_flux_vae(SDVersion version) {
|
||||
if (sd_version_is_flux(version) || sd_version_is_z_image(version) || sd_version_is_boogu_image(version) || sd_version_is_longcat(version)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool sd_version_uses_flux2_vae(SDVersion version) {
|
||||
if (sd_version_is_flux2(version) || sd_version_is_ernie_image(version) || sd_version_is_lens(version) || sd_version_is_ideogram4(version)) {
|
||||
if (sd_version_is_flux2(version) || sd_version_is_ernie_image(version) || sd_version_is_lens(version) || sd_version_is_ideogram4(version) || sd_version_is_sefi_image(version)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool sd_version_uses_wan_vae(SDVersion version) {
|
||||
if (sd_version_is_wan(version) || sd_version_is_qwen_image(version) || sd_version_is_krea2(version) || sd_version_is_anima(version)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -206,11 +244,14 @@ 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_boogu_image(version) ||
|
||||
sd_version_is_ernie_image(version) ||
|
||||
sd_version_is_lens(version) ||
|
||||
sd_version_is_longcat(version) ||
|
||||
sd_version_is_pid(version) ||
|
||||
sd_version_is_ideogram4(version)) {
|
||||
sd_version_is_ideogram4(version) ||
|
||||
sd_version_is_sefi_image(version) ||
|
||||
sd_version_is_krea2(version)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -101,7 +101,7 @@ struct LoraModel : public GGMLRunner {
|
||||
if (model_manager == nullptr ||
|
||||
!model_manager->register_param_tensors("LoRA",
|
||||
std::move(tensors),
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
runtime_backend,
|
||||
params_backend) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
|
||||
@@ -622,7 +622,7 @@ struct PhotoMakerIDEmbed : public GGMLRunner {
|
||||
model_loader.load_tensors(on_new_tensor_cb);
|
||||
if (!model_manager->register_param_tensors("PhotoMaker ID embeds",
|
||||
tensors,
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
runtime_backend,
|
||||
params_backend) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#ifndef __PULID_HPP__
|
||||
#define __PULID_HPP__
|
||||
|
||||
#include "core/ggml_extend.hpp"
|
||||
#include "model/common/block.hpp"
|
||||
|
||||
class PuLIDPerceiverAttentionCA : public GGMLBlock {
|
||||
public:
|
||||
static constexpr int64_t DEFAULT_DIM = 3072; // Flux hidden size
|
||||
static constexpr int64_t DEFAULT_DIM_HEAD = 128;
|
||||
static constexpr int64_t DEFAULT_HEADS = 16;
|
||||
static constexpr int64_t DEFAULT_KV_DIM = 2048; // PuLID ID-embedding dim
|
||||
|
||||
protected:
|
||||
int64_t dim;
|
||||
int64_t dim_head;
|
||||
int64_t heads;
|
||||
int64_t kv_dim;
|
||||
int64_t inner_dim;
|
||||
|
||||
public:
|
||||
PuLIDPerceiverAttentionCA(int64_t dim = DEFAULT_DIM,
|
||||
int64_t dim_head = DEFAULT_DIM_HEAD,
|
||||
int64_t heads = DEFAULT_HEADS,
|
||||
int64_t kv_dim = DEFAULT_KV_DIM)
|
||||
: dim(dim),
|
||||
dim_head(dim_head),
|
||||
heads(heads),
|
||||
kv_dim(kv_dim),
|
||||
inner_dim(dim_head * heads) {
|
||||
blocks["norm1"] = std::shared_ptr<GGMLBlock>(new LayerNorm(kv_dim));
|
||||
blocks["norm2"] = std::shared_ptr<GGMLBlock>(new LayerNorm(dim));
|
||||
blocks["to_q"] = std::shared_ptr<GGMLBlock>(new Linear(dim, inner_dim, /*bias=*/false));
|
||||
blocks["to_kv"] = std::shared_ptr<GGMLBlock>(new Linear(kv_dim, inner_dim * 2, /*bias=*/false));
|
||||
blocks["to_out"] = std::shared_ptr<GGMLBlock>(new Linear(inner_dim, dim, /*bias=*/false));
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* id_embedding,
|
||||
ggml_tensor* image_tokens) {
|
||||
auto norm1 = std::dynamic_pointer_cast<LayerNorm>(blocks["norm1"]);
|
||||
auto norm2 = std::dynamic_pointer_cast<LayerNorm>(blocks["norm2"]);
|
||||
auto to_q = std::dynamic_pointer_cast<Linear>(blocks["to_q"]);
|
||||
auto to_kv = std::dynamic_pointer_cast<Linear>(blocks["to_kv"]);
|
||||
auto to_out = std::dynamic_pointer_cast<Linear>(blocks["to_out"]);
|
||||
|
||||
ggml_tensor* x_normed = norm1->forward(ctx, id_embedding);
|
||||
ggml_tensor* lat_normed = norm2->forward(ctx, image_tokens);
|
||||
|
||||
ggml_tensor* q = to_q->forward(ctx, lat_normed); // [N, T_img, 2048]
|
||||
ggml_tensor* kv = to_kv->forward(ctx, x_normed); // [N, T_img, 3072]
|
||||
|
||||
ggml_tensor* k = ggml_view_3d(ctx->ggml_ctx, kv,
|
||||
inner_dim, kv->ne[1], kv->ne[2],
|
||||
kv->nb[1], kv->nb[2],
|
||||
/*offset=*/0);
|
||||
ggml_tensor* v = ggml_view_3d(ctx->ggml_ctx, kv,
|
||||
inner_dim, kv->ne[1], kv->ne[2],
|
||||
kv->nb[1], kv->nb[2],
|
||||
/*offset=*/inner_dim * ggml_element_size(kv));
|
||||
k = ggml_cont(ctx->ggml_ctx, k);
|
||||
v = ggml_cont(ctx->ggml_ctx, v);
|
||||
|
||||
ggml_tensor* attn_out = ggml_ext_attention_ext(
|
||||
ctx->ggml_ctx, ctx->backend,
|
||||
q, k, v,
|
||||
heads,
|
||||
/*mask=*/nullptr,
|
||||
/*diag_mask_inf=*/false);
|
||||
|
||||
ggml_tensor* out = to_out->forward(ctx, attn_out);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __PULID_HPP__
|
||||
@@ -899,10 +899,12 @@ namespace Rope {
|
||||
// q,k,v: [N, L, n_head, d_head]
|
||||
// pe: [L, d_head/2, 2, 2]
|
||||
// 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]
|
||||
|
||||
auto x = ggml_ext_attention_ext(ctx->ggml_ctx, ctx->backend, q, k, v, v->ne[1], mask, true, ctx->flash_attn_enabled, kv_scale); // [N, L, n_head*d_head]
|
||||
auto x = ggml_ext_attention_ext(ctx->ggml_ctx, ctx->backend, q, k, v, n_head, mask, true, ctx->flash_attn_enabled, kv_scale); // [N, L, n_head*d_head]
|
||||
return x;
|
||||
}
|
||||
}; // namespace Rope
|
||||
|
||||
@@ -0,0 +1,835 @@
|
||||
#ifndef __SD_MODEL_DIFFUSION_BOOGU_HPP__
|
||||
#define __SD_MODEL_DIFFUSION_BOOGU_HPP__
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "core/ggml_extend.hpp"
|
||||
#include "model/common/rope.hpp"
|
||||
#include "model/diffusion/dit.hpp"
|
||||
#include "model/diffusion/model.hpp"
|
||||
#include "model/diffusion/qwen_image.hpp"
|
||||
#include "model_loader.h"
|
||||
|
||||
namespace Boogu {
|
||||
constexpr int BOOGU_GRAPH_SIZE = 65536;
|
||||
|
||||
struct BooguConfig {
|
||||
int patch_size = 2;
|
||||
int64_t in_channels = 16;
|
||||
int64_t out_channels = 16;
|
||||
int64_t hidden_size = 3360;
|
||||
int64_t num_layers = 32;
|
||||
int64_t num_double_stream_layers = 8;
|
||||
int64_t num_refiner_layers = 2;
|
||||
int64_t num_attention_heads = 28;
|
||||
int64_t num_kv_heads = 7;
|
||||
int64_t head_dim = 120;
|
||||
int64_t multiple_of = 256;
|
||||
int64_t instruction_feat_dim = 4096;
|
||||
int64_t timestep_embed_dim = 1024;
|
||||
int theta = 10000;
|
||||
float timestep_scale = 1000.0f;
|
||||
float norm_eps = 1e-5f;
|
||||
std::vector<int> axes_dim = {40, 40, 40};
|
||||
int64_t axes_dim_sum = 120;
|
||||
|
||||
static int64_t count_blocks(const String2TensorStorage& tensor_storage_map,
|
||||
const std::string& prefix,
|
||||
const std::string& block_prefix) {
|
||||
int64_t count = 0;
|
||||
for (const auto& [name, _] : tensor_storage_map) {
|
||||
if (!starts_with(name, prefix)) {
|
||||
continue;
|
||||
}
|
||||
size_t pos = name.find(block_prefix);
|
||||
if (pos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
auto items = split_string(name.substr(pos), '.');
|
||||
if (items.size() > 1) {
|
||||
count = std::max<int64_t>(count, atoi(items[1].c_str()) + 1);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static BooguConfig detect_from_weights(const String2TensorStorage& tensor_storage_map, const std::string& prefix) {
|
||||
BooguConfig config;
|
||||
int64_t detected_head_dim = 0;
|
||||
int64_t detected_kv_dim = 0;
|
||||
|
||||
for (const auto& [name, tensor_storage] : tensor_storage_map) {
|
||||
if (!starts_with(name, prefix)) {
|
||||
continue;
|
||||
}
|
||||
if (ends_with(name, "x_embedder.weight") && tensor_storage.n_dims == 2) {
|
||||
int64_t patch_area = config.patch_size * config.patch_size;
|
||||
config.in_channels = tensor_storage.ne[0] / patch_area;
|
||||
config.hidden_size = tensor_storage.ne[1];
|
||||
} else if (ends_with(name, "time_caption_embed.caption_embedder.1.weight") && tensor_storage.n_dims == 2) {
|
||||
config.instruction_feat_dim = tensor_storage.ne[0];
|
||||
config.hidden_size = tensor_storage.ne[1];
|
||||
} else if (ends_with(name, "single_stream_layers.0.attn.norm_q.weight") && tensor_storage.n_dims == 1) {
|
||||
detected_head_dim = tensor_storage.ne[0];
|
||||
} else if (ends_with(name, "double_stream_layers.0.img_self_attn.norm_q.weight") && tensor_storage.n_dims == 1) {
|
||||
detected_head_dim = tensor_storage.ne[0];
|
||||
} else if (ends_with(name, "single_stream_layers.0.attn.to_k.weight") && tensor_storage.n_dims == 2) {
|
||||
detected_kv_dim = tensor_storage.ne[1];
|
||||
} else if (ends_with(name, "double_stream_layers.0.img_instruct_attn.processor.img_to_k.weight") && tensor_storage.n_dims == 2) {
|
||||
detected_kv_dim = tensor_storage.ne[1];
|
||||
} else if (ends_with(name, "norm_out.linear_2.weight") && tensor_storage.n_dims == 2) {
|
||||
int64_t patch_area = config.patch_size * config.patch_size;
|
||||
config.out_channels = tensor_storage.ne[1] / patch_area;
|
||||
}
|
||||
}
|
||||
|
||||
config.num_layers = std::max<int64_t>(1, count_blocks(tensor_storage_map, prefix, "single_stream_layers."));
|
||||
config.num_double_stream_layers = std::max<int64_t>(0, count_blocks(tensor_storage_map, prefix, "double_stream_layers."));
|
||||
int64_t noise_refiner_layers = count_blocks(tensor_storage_map, prefix, "noise_refiner.");
|
||||
int64_t ref_refiner_layers = count_blocks(tensor_storage_map, prefix, "ref_image_refiner.");
|
||||
int64_t context_refiner_layers = count_blocks(tensor_storage_map, prefix, "context_refiner.");
|
||||
config.num_refiner_layers = std::max<int64_t>(1, std::max(noise_refiner_layers, std::max(ref_refiner_layers, context_refiner_layers)));
|
||||
|
||||
if (detected_head_dim > 0) {
|
||||
config.head_dim = detected_head_dim;
|
||||
config.num_attention_heads = config.hidden_size / config.head_dim;
|
||||
config.axes_dim_sum = config.head_dim;
|
||||
if (detected_kv_dim > 0) {
|
||||
config.num_kv_heads = detected_kv_dim / config.head_dim;
|
||||
}
|
||||
if (config.axes_dim_sum == 120) {
|
||||
config.axes_dim = {40, 40, 40};
|
||||
} else if (config.axes_dim_sum % 3 == 0) {
|
||||
int axis = static_cast<int>(config.axes_dim_sum / 3);
|
||||
config.axes_dim = {axis, axis, axis};
|
||||
}
|
||||
}
|
||||
config.timestep_embed_dim = std::min<int64_t>(config.hidden_size, 1024);
|
||||
|
||||
LOG_DEBUG("boogu_image: layers=%" PRId64 ", double_stream_layers=%" PRId64 ", refiner_layers=%" PRId64 ", hidden=%" PRId64 ", heads=%" PRId64 ", kv_heads=%" PRId64 ", head_dim=%" PRId64 ", in_channels=%" PRId64 ", out_channels=%" PRId64,
|
||||
config.num_layers,
|
||||
config.num_double_stream_layers,
|
||||
config.num_refiner_layers,
|
||||
config.hidden_size,
|
||||
config.num_attention_heads,
|
||||
config.num_kv_heads,
|
||||
config.head_dim,
|
||||
config.in_channels,
|
||||
config.out_channels);
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
__STATIC_INLINE__ ggml_tensor* scale_modulate(ggml_context* ctx, ggml_tensor* x, ggml_tensor* scale) {
|
||||
scale = ggml_reshape_3d(ctx, scale, scale->ne[0], 1, scale->ne[1]);
|
||||
return ggml_add(ctx, x, ggml_mul(ctx, x, scale));
|
||||
}
|
||||
|
||||
__STATIC_INLINE__ ggml_tensor* gate_residual(ggml_context* ctx, ggml_tensor* residual, ggml_tensor* x, ggml_tensor* gate) {
|
||||
gate = ggml_tanh(ctx, gate);
|
||||
gate = ggml_reshape_3d(ctx, gate, gate->ne[0], 1, gate->ne[1]);
|
||||
x = ggml_mul(ctx, x, gate);
|
||||
return ggml_add(ctx, residual, x);
|
||||
}
|
||||
|
||||
struct LuminaCombinedTimestepCaptionEmbedding : public GGMLBlock {
|
||||
int64_t frequency_embedding_size;
|
||||
float timestep_scale;
|
||||
|
||||
LuminaCombinedTimestepCaptionEmbedding(int64_t hidden_size,
|
||||
int64_t instruction_feat_dim,
|
||||
int64_t frequency_embedding_size,
|
||||
float norm_eps,
|
||||
float timestep_scale)
|
||||
: frequency_embedding_size(frequency_embedding_size),
|
||||
timestep_scale(timestep_scale) {
|
||||
blocks["timestep_embedder"] = std::make_shared<Qwen::TimestepEmbedding>(frequency_embedding_size, std::min<int64_t>(hidden_size, 1024));
|
||||
blocks["caption_embedder.0"] = std::make_shared<RMSNorm>(instruction_feat_dim, norm_eps);
|
||||
blocks["caption_embedder.1"] = std::make_shared<Linear>(instruction_feat_dim, hidden_size, true);
|
||||
}
|
||||
|
||||
std::pair<ggml_tensor*, ggml_tensor*> forward(GGMLRunnerContext* ctx, ggml_tensor* timestep, ggml_tensor* text_hidden_states) {
|
||||
auto timestep_embedder = std::dynamic_pointer_cast<Qwen::TimestepEmbedding>(blocks["timestep_embedder"]);
|
||||
auto caption_embedder_0 = std::dynamic_pointer_cast<RMSNorm>(blocks["caption_embedder.0"]);
|
||||
auto caption_embedder_1 = std::dynamic_pointer_cast<Linear>(blocks["caption_embedder.1"]);
|
||||
|
||||
auto timestep_proj = ggml_ext_timestep_embedding(ctx->ggml_ctx, timestep, static_cast<int>(frequency_embedding_size), 10000, timestep_scale);
|
||||
auto time_embed = timestep_embedder->forward(ctx, timestep_proj);
|
||||
auto caption_embed = caption_embedder_1->forward(ctx, caption_embedder_0->forward(ctx, text_hidden_states));
|
||||
return {time_embed, caption_embed};
|
||||
}
|
||||
};
|
||||
|
||||
struct LuminaRMSNormZero : public GGMLBlock {
|
||||
LuminaRMSNormZero(int64_t embedding_dim, int64_t conditioning_embedding_dim, float norm_eps) {
|
||||
blocks["linear"] = std::make_shared<Linear>(conditioning_embedding_dim, 4 * embedding_dim, true);
|
||||
blocks["norm"] = std::make_shared<RMSNorm>(embedding_dim, norm_eps);
|
||||
}
|
||||
|
||||
std::tuple<ggml_tensor*, ggml_tensor*, ggml_tensor*, ggml_tensor*> forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* emb) {
|
||||
auto linear = std::dynamic_pointer_cast<Linear>(blocks["linear"]);
|
||||
auto norm = std::dynamic_pointer_cast<RMSNorm>(blocks["norm"]);
|
||||
|
||||
emb = linear->forward(ctx, ggml_silu(ctx->ggml_ctx, emb));
|
||||
auto mods = ggml_ext_chunk(ctx->ggml_ctx, emb, 4, 0);
|
||||
|
||||
auto scale_msa = mods[0];
|
||||
auto gate_msa = mods[1];
|
||||
auto scale_mlp = mods[2];
|
||||
auto gate_mlp = mods[3];
|
||||
|
||||
x = scale_modulate(ctx->ggml_ctx, norm->forward(ctx, x), scale_msa);
|
||||
return {x, gate_msa, scale_mlp, gate_mlp};
|
||||
}
|
||||
};
|
||||
|
||||
struct LuminaFeedForward : public GGMLBlock {
|
||||
LuminaFeedForward(int64_t dim, int64_t inner_dim, int64_t multiple_of) {
|
||||
inner_dim = multiple_of * ((inner_dim + multiple_of - 1) / multiple_of);
|
||||
blocks["linear_1"] = std::make_shared<Linear>(dim, inner_dim, false);
|
||||
blocks["linear_2"] = std::make_shared<Linear>(inner_dim, dim, false);
|
||||
blocks["linear_3"] = std::make_shared<Linear>(dim, inner_dim, false);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
auto linear_1 = std::dynamic_pointer_cast<Linear>(blocks["linear_1"]);
|
||||
auto linear_2 = std::dynamic_pointer_cast<Linear>(blocks["linear_2"]);
|
||||
auto linear_3 = std::dynamic_pointer_cast<Linear>(blocks["linear_3"]);
|
||||
|
||||
if (sd_backend_is(ctx->backend, "Vulkan")) {
|
||||
linear_2->set_force_prec_f32(true);
|
||||
}
|
||||
|
||||
auto h1 = linear_1->forward(ctx, x);
|
||||
auto h2 = linear_3->forward(ctx, x);
|
||||
x = ggml_swiglu_split(ctx->ggml_ctx, h1, h2);
|
||||
x = linear_2->forward(ctx, x);
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
struct LuminaLayerNormContinuous : public GGMLBlock {
|
||||
LuminaLayerNormContinuous(int64_t embedding_dim,
|
||||
int64_t conditioning_embedding_dim,
|
||||
int64_t out_dim) {
|
||||
blocks["linear_1"] = std::make_shared<Linear>(conditioning_embedding_dim, embedding_dim, true);
|
||||
blocks["norm"] = std::make_shared<LayerNorm>(embedding_dim, 1e-6f, false);
|
||||
blocks["linear_2"] = std::make_shared<Linear>(embedding_dim, out_dim, true);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* conditioning_embedding) {
|
||||
auto linear_1 = std::dynamic_pointer_cast<Linear>(blocks["linear_1"]);
|
||||
auto norm = std::dynamic_pointer_cast<LayerNorm>(blocks["norm"]);
|
||||
auto linear_2 = std::dynamic_pointer_cast<Linear>(blocks["linear_2"]);
|
||||
|
||||
auto emb = linear_1->forward(ctx, ggml_silu(ctx->ggml_ctx, conditioning_embedding));
|
||||
x = scale_modulate(ctx->ggml_ctx, norm->forward(ctx, x), emb);
|
||||
x = linear_2->forward(ctx, x);
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
struct Attention : public GGMLBlock {
|
||||
int64_t dim_head;
|
||||
int64_t heads;
|
||||
int64_t kv_heads;
|
||||
|
||||
Attention(int64_t query_dim, int64_t dim_head, int64_t heads, int64_t kv_heads, float eps = 1e-5f)
|
||||
: dim_head(dim_head), heads(heads), kv_heads(kv_heads) {
|
||||
blocks["to_q"] = std::make_shared<Linear>(query_dim, heads * dim_head, false);
|
||||
blocks["to_k"] = std::make_shared<Linear>(query_dim, kv_heads * dim_head, false);
|
||||
blocks["to_v"] = std::make_shared<Linear>(query_dim, kv_heads * dim_head, false);
|
||||
blocks["norm_q"] = std::make_shared<RMSNorm>(dim_head, eps);
|
||||
blocks["norm_k"] = std::make_shared<RMSNorm>(dim_head, eps);
|
||||
blocks["to_out.0"] = std::make_shared<Linear>(heads * dim_head, query_dim, false);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* hidden_states,
|
||||
ggml_tensor* encoder_hidden_states,
|
||||
ggml_tensor* rotary_emb,
|
||||
ggml_tensor* attention_mask = nullptr) {
|
||||
auto to_q = std::dynamic_pointer_cast<Linear>(blocks["to_q"]);
|
||||
auto to_k = std::dynamic_pointer_cast<Linear>(blocks["to_k"]);
|
||||
auto to_v = std::dynamic_pointer_cast<Linear>(blocks["to_v"]);
|
||||
auto norm_q = std::dynamic_pointer_cast<RMSNorm>(blocks["norm_q"]);
|
||||
auto norm_k = std::dynamic_pointer_cast<RMSNorm>(blocks["norm_k"]);
|
||||
auto to_out_0 = std::dynamic_pointer_cast<Linear>(blocks["to_out.0"]);
|
||||
|
||||
if (sd_backend_is(ctx->backend, "Vulkan")) {
|
||||
to_out_0->set_force_prec_f32(true);
|
||||
}
|
||||
|
||||
int64_t N = hidden_states->ne[2];
|
||||
int64_t Lq = hidden_states->ne[1];
|
||||
int64_t Lk = encoder_hidden_states->ne[1];
|
||||
|
||||
auto q = to_q->forward(ctx, hidden_states);
|
||||
q = ggml_reshape_4d(ctx->ggml_ctx, q, dim_head, heads, Lq, N);
|
||||
auto k = to_k->forward(ctx, encoder_hidden_states);
|
||||
k = ggml_reshape_4d(ctx->ggml_ctx, k, dim_head, kv_heads, Lk, N);
|
||||
auto v = to_v->forward(ctx, encoder_hidden_states);
|
||||
v = ggml_reshape_4d(ctx->ggml_ctx, v, dim_head, kv_heads, Lk, N);
|
||||
|
||||
q = norm_q->forward(ctx, q);
|
||||
k = norm_k->forward(ctx, k);
|
||||
|
||||
auto out = Rope::attention(ctx, q, k, v, rotary_emb, attention_mask);
|
||||
out = to_out_0->forward(ctx, out);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
struct BooguImageTransformerBlock : public GGMLBlock {
|
||||
bool modulation;
|
||||
|
||||
BooguImageTransformerBlock(int64_t dim,
|
||||
int64_t num_attention_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t multiple_of,
|
||||
float norm_eps,
|
||||
bool modulation)
|
||||
: modulation(modulation) {
|
||||
int64_t head_dim = dim / num_attention_heads;
|
||||
blocks["attn"] = std::make_shared<Attention>(dim, head_dim, num_attention_heads, num_kv_heads, 1e-5f);
|
||||
blocks["feed_forward"] = std::make_shared<LuminaFeedForward>(dim, 4 * dim, multiple_of);
|
||||
if (modulation) {
|
||||
blocks["norm1"] = std::make_shared<LuminaRMSNormZero>(dim, std::min<int64_t>(dim, 1024), norm_eps);
|
||||
} else {
|
||||
blocks["norm1"] = std::make_shared<RMSNorm>(dim, norm_eps);
|
||||
}
|
||||
blocks["ffn_norm1"] = std::make_shared<RMSNorm>(dim, norm_eps);
|
||||
blocks["norm2"] = std::make_shared<RMSNorm>(dim, norm_eps);
|
||||
blocks["ffn_norm2"] = std::make_shared<RMSNorm>(dim, norm_eps);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* hidden_states,
|
||||
ggml_tensor* rotary_emb,
|
||||
ggml_tensor* temb = nullptr,
|
||||
ggml_tensor* attention_mask = nullptr) {
|
||||
auto attn = std::dynamic_pointer_cast<Attention>(blocks["attn"]);
|
||||
auto feed_forward = std::dynamic_pointer_cast<LuminaFeedForward>(blocks["feed_forward"]);
|
||||
auto ffn_norm1 = std::dynamic_pointer_cast<RMSNorm>(blocks["ffn_norm1"]);
|
||||
auto norm2 = std::dynamic_pointer_cast<RMSNorm>(blocks["norm2"]);
|
||||
auto ffn_norm2 = std::dynamic_pointer_cast<RMSNorm>(blocks["ffn_norm2"]);
|
||||
|
||||
if (modulation) {
|
||||
auto norm1 = std::dynamic_pointer_cast<LuminaRMSNormZero>(blocks["norm1"]);
|
||||
auto mods = norm1->forward(ctx, hidden_states, temb);
|
||||
|
||||
auto norm_hidden_states = std::get<0>(mods);
|
||||
auto gate_msa = std::get<1>(mods);
|
||||
auto scale_mlp = std::get<2>(mods);
|
||||
auto gate_mlp = std::get<3>(mods);
|
||||
|
||||
auto attn_output = attn->forward(ctx, norm_hidden_states, norm_hidden_states, rotary_emb, attention_mask);
|
||||
hidden_states = gate_residual(ctx->ggml_ctx, hidden_states, norm2->forward(ctx, attn_output), gate_msa);
|
||||
|
||||
auto mlp_input = scale_modulate(ctx->ggml_ctx, ffn_norm1->forward(ctx, hidden_states), scale_mlp);
|
||||
auto mlp_output = feed_forward->forward(ctx, mlp_input);
|
||||
hidden_states = gate_residual(ctx->ggml_ctx, hidden_states, ffn_norm2->forward(ctx, mlp_output), gate_mlp);
|
||||
} else {
|
||||
auto norm1 = std::dynamic_pointer_cast<RMSNorm>(blocks["norm1"]);
|
||||
|
||||
auto norm_hidden_states = norm1->forward(ctx, hidden_states);
|
||||
auto attn_output = attn->forward(ctx, norm_hidden_states, norm_hidden_states, rotary_emb, attention_mask);
|
||||
hidden_states = ggml_add(ctx->ggml_ctx, hidden_states, norm2->forward(ctx, attn_output));
|
||||
|
||||
auto mlp_output = feed_forward->forward(ctx, ffn_norm1->forward(ctx, hidden_states));
|
||||
hidden_states = ggml_add(ctx->ggml_ctx, hidden_states, ffn_norm2->forward(ctx, mlp_output));
|
||||
}
|
||||
return hidden_states;
|
||||
}
|
||||
};
|
||||
|
||||
struct BooguImageJointAttention : public GGMLBlock {
|
||||
int64_t dim_head;
|
||||
int64_t heads;
|
||||
int64_t kv_heads;
|
||||
|
||||
BooguImageJointAttention(int64_t dim, int64_t dim_head, int64_t heads, int64_t kv_heads)
|
||||
: dim_head(dim_head), heads(heads), kv_heads(kv_heads) {
|
||||
blocks["norm_q"] = std::make_shared<RMSNorm>(dim_head, 1e-5f);
|
||||
blocks["norm_k"] = std::make_shared<RMSNorm>(dim_head, 1e-5f);
|
||||
blocks["to_out.0"] = std::make_shared<Linear>(heads * dim_head, dim, false);
|
||||
blocks["processor.img_to_q"] = std::make_shared<Linear>(dim, heads * dim_head, false);
|
||||
blocks["processor.img_to_k"] = std::make_shared<Linear>(dim, kv_heads * dim_head, false);
|
||||
blocks["processor.img_to_v"] = std::make_shared<Linear>(dim, kv_heads * dim_head, false);
|
||||
blocks["processor.instruct_to_q"] = std::make_shared<Linear>(dim, heads * dim_head, false);
|
||||
blocks["processor.instruct_to_k"] = std::make_shared<Linear>(dim, kv_heads * dim_head, false);
|
||||
blocks["processor.instruct_to_v"] = std::make_shared<Linear>(dim, kv_heads * dim_head, false);
|
||||
blocks["processor.instruct_out"] = std::make_shared<Linear>(heads * dim_head, dim, false);
|
||||
blocks["processor.img_out"] = std::make_shared<Linear>(heads * dim_head, dim, false);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* img_hidden_states,
|
||||
ggml_tensor* instruct_hidden_states,
|
||||
ggml_tensor* rotary_emb,
|
||||
ggml_tensor* attention_mask = nullptr) {
|
||||
auto norm_q = std::dynamic_pointer_cast<RMSNorm>(blocks["norm_q"]);
|
||||
auto norm_k = std::dynamic_pointer_cast<RMSNorm>(blocks["norm_k"]);
|
||||
auto to_out_0 = std::dynamic_pointer_cast<Linear>(blocks["to_out.0"]);
|
||||
auto img_to_q = std::dynamic_pointer_cast<Linear>(blocks["processor.img_to_q"]);
|
||||
auto img_to_k = std::dynamic_pointer_cast<Linear>(blocks["processor.img_to_k"]);
|
||||
auto img_to_v = std::dynamic_pointer_cast<Linear>(blocks["processor.img_to_v"]);
|
||||
auto instruct_to_q = std::dynamic_pointer_cast<Linear>(blocks["processor.instruct_to_q"]);
|
||||
auto instruct_to_k = std::dynamic_pointer_cast<Linear>(blocks["processor.instruct_to_k"]);
|
||||
auto instruct_to_v = std::dynamic_pointer_cast<Linear>(blocks["processor.instruct_to_v"]);
|
||||
auto instruct_out = std::dynamic_pointer_cast<Linear>(blocks["processor.instruct_out"]);
|
||||
auto img_out = std::dynamic_pointer_cast<Linear>(blocks["processor.img_out"]);
|
||||
|
||||
if (sd_backend_is(ctx->backend, "Vulkan")) {
|
||||
to_out_0->set_force_prec_f32(true);
|
||||
}
|
||||
|
||||
int64_t N = img_hidden_states->ne[2];
|
||||
int64_t L_img = img_hidden_states->ne[1];
|
||||
int64_t L_instruct = instruct_hidden_states->ne[1];
|
||||
|
||||
auto img_q = img_to_q->forward(ctx, img_hidden_states);
|
||||
img_q = ggml_reshape_4d(ctx->ggml_ctx, img_q, dim_head, heads, L_img, N);
|
||||
auto img_k = img_to_k->forward(ctx, img_hidden_states);
|
||||
img_k = ggml_reshape_4d(ctx->ggml_ctx, img_k, dim_head, kv_heads, L_img, N);
|
||||
auto img_v = img_to_v->forward(ctx, img_hidden_states);
|
||||
img_v = ggml_reshape_4d(ctx->ggml_ctx, img_v, dim_head, kv_heads, L_img, N);
|
||||
|
||||
auto instruct_q = instruct_to_q->forward(ctx, instruct_hidden_states);
|
||||
instruct_q = ggml_reshape_4d(ctx->ggml_ctx, instruct_q, dim_head, heads, L_instruct, N);
|
||||
auto instruct_k = instruct_to_k->forward(ctx, instruct_hidden_states);
|
||||
instruct_k = ggml_reshape_4d(ctx->ggml_ctx, instruct_k, dim_head, kv_heads, L_instruct, N);
|
||||
auto instruct_v = instruct_to_v->forward(ctx, instruct_hidden_states);
|
||||
instruct_v = ggml_reshape_4d(ctx->ggml_ctx, instruct_v, dim_head, kv_heads, L_instruct, N);
|
||||
|
||||
auto q = ggml_concat(ctx->ggml_ctx, instruct_q, img_q, 2);
|
||||
auto k = ggml_concat(ctx->ggml_ctx, instruct_k, img_k, 2);
|
||||
auto v = ggml_concat(ctx->ggml_ctx, instruct_v, img_v, 2);
|
||||
q = norm_q->forward(ctx, q);
|
||||
k = norm_k->forward(ctx, k);
|
||||
|
||||
auto hidden_states = Rope::attention(ctx, q, k, v, rotary_emb, attention_mask);
|
||||
auto instruct_attn = ggml_ext_slice(ctx->ggml_ctx, hidden_states, 1, 0, L_instruct);
|
||||
auto img_attn = ggml_ext_slice(ctx->ggml_ctx, hidden_states, 1, L_instruct, L_instruct + L_img);
|
||||
|
||||
instruct_attn = instruct_out->forward(ctx, instruct_attn);
|
||||
img_attn = img_out->forward(ctx, img_attn);
|
||||
hidden_states = ggml_concat(ctx->ggml_ctx, instruct_attn, img_attn, 1);
|
||||
hidden_states = to_out_0->forward(ctx, hidden_states);
|
||||
return hidden_states;
|
||||
}
|
||||
};
|
||||
|
||||
struct BooguImageDoubleStreamBlock : public GGMLBlock {
|
||||
BooguImageDoubleStreamBlock(int64_t dim,
|
||||
int64_t num_attention_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t multiple_of,
|
||||
float norm_eps) {
|
||||
int64_t head_dim = dim / num_attention_heads;
|
||||
blocks["img_instruct_attn"] = std::make_shared<BooguImageJointAttention>(dim, head_dim, num_attention_heads, num_kv_heads);
|
||||
blocks["img_self_attn"] = std::make_shared<Attention>(dim, head_dim, num_attention_heads, num_kv_heads, 1e-5f);
|
||||
blocks["img_feed_forward"] = std::make_shared<LuminaFeedForward>(dim, 4 * dim, multiple_of);
|
||||
blocks["instruct_feed_forward"] = std::make_shared<LuminaFeedForward>(dim, 4 * dim, multiple_of);
|
||||
blocks["img_norm1"] = std::make_shared<LuminaRMSNormZero>(dim, std::min<int64_t>(dim, 1024), norm_eps);
|
||||
blocks["img_norm2"] = std::make_shared<LuminaRMSNormZero>(dim, std::min<int64_t>(dim, 1024), norm_eps);
|
||||
blocks["img_norm3"] = std::make_shared<LuminaRMSNormZero>(dim, std::min<int64_t>(dim, 1024), norm_eps);
|
||||
blocks["instruct_norm1"] = std::make_shared<LuminaRMSNormZero>(dim, std::min<int64_t>(dim, 1024), norm_eps);
|
||||
blocks["instruct_norm2"] = std::make_shared<LuminaRMSNormZero>(dim, std::min<int64_t>(dim, 1024), norm_eps);
|
||||
blocks["img_attn_norm"] = std::make_shared<RMSNorm>(dim, norm_eps);
|
||||
blocks["img_self_attn_norm"] = std::make_shared<RMSNorm>(dim, norm_eps);
|
||||
blocks["img_ffn_norm1"] = std::make_shared<RMSNorm>(dim, norm_eps);
|
||||
blocks["img_ffn_norm2"] = std::make_shared<RMSNorm>(dim, norm_eps);
|
||||
blocks["instruct_attn_norm"] = std::make_shared<RMSNorm>(dim, norm_eps);
|
||||
blocks["instruct_ffn_norm1"] = std::make_shared<RMSNorm>(dim, norm_eps);
|
||||
blocks["instruct_ffn_norm2"] = std::make_shared<RMSNorm>(dim, norm_eps);
|
||||
}
|
||||
|
||||
std::pair<ggml_tensor*, ggml_tensor*> forward(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* img_hidden_states,
|
||||
ggml_tensor* instruct_hidden_states,
|
||||
ggml_tensor* joint_rotary_emb,
|
||||
ggml_tensor* img_rotary_emb,
|
||||
ggml_tensor* temb) {
|
||||
auto img_instruct_attn = std::dynamic_pointer_cast<BooguImageJointAttention>(blocks["img_instruct_attn"]);
|
||||
auto img_self_attn = std::dynamic_pointer_cast<Attention>(blocks["img_self_attn"]);
|
||||
auto img_feed_forward = std::dynamic_pointer_cast<LuminaFeedForward>(blocks["img_feed_forward"]);
|
||||
auto instruct_feed_forward = std::dynamic_pointer_cast<LuminaFeedForward>(blocks["instruct_feed_forward"]);
|
||||
auto img_norm1 = std::dynamic_pointer_cast<LuminaRMSNormZero>(blocks["img_norm1"]);
|
||||
auto img_norm2 = std::dynamic_pointer_cast<LuminaRMSNormZero>(blocks["img_norm2"]);
|
||||
auto img_norm3 = std::dynamic_pointer_cast<LuminaRMSNormZero>(blocks["img_norm3"]);
|
||||
auto instruct_norm1 = std::dynamic_pointer_cast<LuminaRMSNormZero>(blocks["instruct_norm1"]);
|
||||
auto instruct_norm2 = std::dynamic_pointer_cast<LuminaRMSNormZero>(blocks["instruct_norm2"]);
|
||||
auto img_attn_norm = std::dynamic_pointer_cast<RMSNorm>(blocks["img_attn_norm"]);
|
||||
auto img_self_attn_norm = std::dynamic_pointer_cast<RMSNorm>(blocks["img_self_attn_norm"]);
|
||||
auto img_ffn_norm1 = std::dynamic_pointer_cast<RMSNorm>(blocks["img_ffn_norm1"]);
|
||||
auto img_ffn_norm2 = std::dynamic_pointer_cast<RMSNorm>(blocks["img_ffn_norm2"]);
|
||||
auto instruct_attn_norm = std::dynamic_pointer_cast<RMSNorm>(blocks["instruct_attn_norm"]);
|
||||
auto instruct_ffn_norm1 = std::dynamic_pointer_cast<RMSNorm>(blocks["instruct_ffn_norm1"]);
|
||||
auto instruct_ffn_norm2 = std::dynamic_pointer_cast<RMSNorm>(blocks["instruct_ffn_norm2"]);
|
||||
|
||||
int64_t L_instruct = instruct_hidden_states->ne[1];
|
||||
|
||||
auto img_norm1_out_vec = img_norm1->forward(ctx, img_hidden_states, temb);
|
||||
auto img_norm2_out_vec = img_norm2->forward(ctx, img_hidden_states, temb);
|
||||
auto img_norm3_out_vec = img_norm3->forward(ctx, img_hidden_states, temb);
|
||||
auto instruct_norm1_out_vec = instruct_norm1->forward(ctx, instruct_hidden_states, temb);
|
||||
auto instruct_norm2_out_vec = instruct_norm2->forward(ctx, instruct_hidden_states, temb);
|
||||
|
||||
auto img_norm1_out = std::get<0>(img_norm1_out_vec);
|
||||
auto img_gate_msa = std::get<1>(img_norm1_out_vec);
|
||||
auto img_scale_mlp = std::get<2>(img_norm1_out_vec);
|
||||
auto img_gate_mlp = std::get<3>(img_norm1_out_vec);
|
||||
|
||||
auto img_norm2_out = std::get<0>(img_norm2_out_vec);
|
||||
auto img_shift_mlp = std::get<1>(img_norm2_out_vec);
|
||||
|
||||
auto img_norm3_out = std::get<0>(img_norm3_out_vec);
|
||||
auto img_gate_self = std::get<1>(img_norm3_out_vec);
|
||||
|
||||
auto instruct_norm1_out = std::get<0>(instruct_norm1_out_vec);
|
||||
auto instruct_gate_msa = std::get<1>(instruct_norm1_out_vec);
|
||||
auto instruct_scale_mlp = std::get<2>(instruct_norm1_out_vec);
|
||||
auto instruct_gate_mlp = std::get<3>(instruct_norm1_out_vec);
|
||||
|
||||
auto instruct_norm2_out = std::get<0>(instruct_norm2_out_vec);
|
||||
auto instruct_shift_mlp = std::get<1>(instruct_norm2_out_vec);
|
||||
|
||||
auto joint_attn_out = img_instruct_attn->forward(ctx, img_norm1_out, instruct_norm1_out, joint_rotary_emb);
|
||||
auto instruct_attn_out = ggml_ext_slice(ctx->ggml_ctx, joint_attn_out, 1, 0, L_instruct);
|
||||
auto img_attn_out = ggml_ext_slice(ctx->ggml_ctx, joint_attn_out, 1, L_instruct, joint_attn_out->ne[1]);
|
||||
|
||||
auto img_self_attn_out = img_self_attn->forward(ctx, img_norm3_out, img_norm3_out, img_rotary_emb);
|
||||
|
||||
img_hidden_states = gate_residual(ctx->ggml_ctx, img_hidden_states, img_attn_norm->forward(ctx, img_attn_out), img_gate_msa);
|
||||
img_hidden_states = gate_residual(ctx->ggml_ctx, img_hidden_states, img_self_attn_norm->forward(ctx, img_self_attn_out), img_gate_self);
|
||||
|
||||
auto img_mlp_input = scale_modulate(ctx->ggml_ctx, img_norm2_out, img_scale_mlp);
|
||||
img_shift_mlp = ggml_reshape_3d(ctx->ggml_ctx, img_shift_mlp, img_shift_mlp->ne[0], 1, img_shift_mlp->ne[1]);
|
||||
img_mlp_input = ggml_add(ctx->ggml_ctx, img_mlp_input, img_shift_mlp);
|
||||
auto img_mlp_out = img_feed_forward->forward(ctx, img_ffn_norm1->forward(ctx, img_mlp_input));
|
||||
img_hidden_states = gate_residual(ctx->ggml_ctx, img_hidden_states, img_ffn_norm2->forward(ctx, img_mlp_out), img_gate_mlp);
|
||||
|
||||
instruct_hidden_states = gate_residual(ctx->ggml_ctx, instruct_hidden_states, instruct_attn_norm->forward(ctx, instruct_attn_out), instruct_gate_msa);
|
||||
auto instruct_mlp_input = scale_modulate(ctx->ggml_ctx, instruct_norm2_out, instruct_scale_mlp);
|
||||
instruct_shift_mlp = ggml_reshape_3d(ctx->ggml_ctx, instruct_shift_mlp, instruct_shift_mlp->ne[0], 1, instruct_shift_mlp->ne[1]);
|
||||
instruct_mlp_input = ggml_add(ctx->ggml_ctx, instruct_mlp_input, instruct_shift_mlp);
|
||||
auto instruct_mlp_out = instruct_feed_forward->forward(ctx, instruct_ffn_norm1->forward(ctx, instruct_mlp_input));
|
||||
instruct_hidden_states = gate_residual(ctx->ggml_ctx, instruct_hidden_states, instruct_ffn_norm2->forward(ctx, instruct_mlp_out), instruct_gate_mlp);
|
||||
|
||||
return {img_hidden_states, instruct_hidden_states};
|
||||
}
|
||||
};
|
||||
|
||||
struct BooguImageModel : public GGMLBlock {
|
||||
BooguConfig config;
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map = {}, const std::string prefix = "") override {
|
||||
GGML_UNUSED(tensor_storage_map);
|
||||
GGML_UNUSED(prefix);
|
||||
params["image_index_embedding"] = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, config.hidden_size, 5);
|
||||
}
|
||||
|
||||
BooguImageModel() = default;
|
||||
BooguImageModel(BooguConfig config)
|
||||
: config(std::move(config)) {
|
||||
blocks["x_embedder"] = std::make_shared<Linear>(this->config.patch_size * this->config.patch_size * this->config.in_channels, this->config.hidden_size, true);
|
||||
blocks["ref_image_patch_embedder"] = std::make_shared<Linear>(this->config.patch_size * this->config.patch_size * this->config.in_channels, this->config.hidden_size, true);
|
||||
blocks["time_caption_embed"] = std::make_shared<LuminaCombinedTimestepCaptionEmbedding>(this->config.hidden_size,
|
||||
this->config.instruction_feat_dim,
|
||||
256,
|
||||
this->config.norm_eps,
|
||||
this->config.timestep_scale);
|
||||
|
||||
for (int i = 0; i < this->config.num_refiner_layers; i++) {
|
||||
blocks["noise_refiner." + std::to_string(i)] = std::make_shared<BooguImageTransformerBlock>(this->config.hidden_size,
|
||||
this->config.num_attention_heads,
|
||||
this->config.num_kv_heads,
|
||||
this->config.multiple_of,
|
||||
this->config.norm_eps,
|
||||
true);
|
||||
blocks["ref_image_refiner." + std::to_string(i)] = std::make_shared<BooguImageTransformerBlock>(this->config.hidden_size,
|
||||
this->config.num_attention_heads,
|
||||
this->config.num_kv_heads,
|
||||
this->config.multiple_of,
|
||||
this->config.norm_eps,
|
||||
true);
|
||||
blocks["context_refiner." + std::to_string(i)] = std::make_shared<BooguImageTransformerBlock>(this->config.hidden_size,
|
||||
this->config.num_attention_heads,
|
||||
this->config.num_kv_heads,
|
||||
this->config.multiple_of,
|
||||
this->config.norm_eps,
|
||||
false);
|
||||
}
|
||||
|
||||
for (int i = 0; i < this->config.num_double_stream_layers; i++) {
|
||||
blocks["double_stream_layers." + std::to_string(i)] = std::make_shared<BooguImageDoubleStreamBlock>(this->config.hidden_size,
|
||||
this->config.num_attention_heads,
|
||||
this->config.num_kv_heads,
|
||||
this->config.multiple_of,
|
||||
this->config.norm_eps);
|
||||
}
|
||||
|
||||
for (int i = 0; i < this->config.num_layers; i++) {
|
||||
blocks["single_stream_layers." + std::to_string(i)] = std::make_shared<BooguImageTransformerBlock>(this->config.hidden_size,
|
||||
this->config.num_attention_heads,
|
||||
this->config.num_kv_heads,
|
||||
this->config.multiple_of,
|
||||
this->config.norm_eps,
|
||||
true);
|
||||
}
|
||||
|
||||
blocks["norm_out"] = std::make_shared<LuminaLayerNormContinuous>(this->config.hidden_size,
|
||||
this->config.timestep_embed_dim,
|
||||
this->config.patch_size * this->config.patch_size * this->config.out_channels);
|
||||
}
|
||||
|
||||
ggml_tensor* image_index_embedding(GGMLRunnerContext* ctx, int index) {
|
||||
GGML_ASSERT(index >= 0 && index < 5);
|
||||
auto embedding = params["image_index_embedding"];
|
||||
auto out = ggml_view_1d(ctx->ggml_ctx,
|
||||
embedding,
|
||||
config.hidden_size,
|
||||
index * config.hidden_size * ggml_element_size(embedding));
|
||||
out = ggml_reshape_3d(ctx->ggml_ctx, out, config.hidden_size, 1, 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
ggml_tensor* embed_refs(GGMLRunnerContext* ctx, const std::vector<ggml_tensor*>& ref_latents) {
|
||||
if (ref_latents.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto ref_image_patch_embedder = std::dynamic_pointer_cast<Linear>(blocks["ref_image_patch_embedder"]);
|
||||
|
||||
ggml_tensor* ref_img = nullptr;
|
||||
for (int i = 0; i < static_cast<int>(ref_latents.size()); i++) {
|
||||
auto ref = DiT::pad_and_patchify(ctx, ref_latents[i], config.patch_size, config.patch_size, false);
|
||||
ref = ref_image_patch_embedder->forward(ctx, ref);
|
||||
ref = ggml_add(ctx->ggml_ctx, ref, image_index_embedding(ctx, std::min(i, 4)));
|
||||
ref_img = ref_img == nullptr ? ref : ggml_concat(ctx->ggml_ctx, ref_img, ref, 1);
|
||||
}
|
||||
return ref_img;
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* timesteps,
|
||||
ggml_tensor* context,
|
||||
ggml_tensor* pe,
|
||||
std::vector<ggml_tensor*> ref_latents = {}) {
|
||||
int64_t W = x->ne[0];
|
||||
int64_t H = x->ne[1];
|
||||
int64_t N = x->ne[3];
|
||||
GGML_ASSERT(N == 1);
|
||||
|
||||
auto x_embedder = std::dynamic_pointer_cast<Linear>(blocks["x_embedder"]);
|
||||
auto time_caption_embed = std::dynamic_pointer_cast<LuminaCombinedTimestepCaptionEmbedding>(blocks["time_caption_embed"]);
|
||||
auto norm_out = std::dynamic_pointer_cast<LuminaLayerNormContinuous>(blocks["norm_out"]);
|
||||
|
||||
auto timestep = ggml_sub(ctx->ggml_ctx, ggml_ext_ones_like(ctx->ggml_ctx, timesteps), timesteps);
|
||||
auto embeds = time_caption_embed->forward(ctx, timestep, context);
|
||||
auto temb = embeds.first;
|
||||
auto txt = embeds.second;
|
||||
|
||||
auto img = DiT::pad_and_patchify(ctx, x, config.patch_size, config.patch_size, false);
|
||||
int64_t img_len = img->ne[1];
|
||||
img = x_embedder->forward(ctx, img);
|
||||
auto ref_img = embed_refs(ctx, ref_latents);
|
||||
int64_t ref_len = ref_img != nullptr ? ref_img->ne[1] : 0;
|
||||
int64_t txt_len = txt->ne[1];
|
||||
|
||||
GGML_ASSERT(pe->ne[3] == txt_len + ref_len + img_len);
|
||||
auto txt_pe = ggml_ext_slice(ctx->ggml_ctx, pe, 3, 0, txt_len);
|
||||
auto noise_pe = ggml_ext_slice(ctx->ggml_ctx, pe, 3, txt_len + ref_len, txt_len + ref_len + img_len);
|
||||
|
||||
for (int i = 0; i < config.num_refiner_layers; i++) {
|
||||
auto block = std::dynamic_pointer_cast<BooguImageTransformerBlock>(blocks["context_refiner." + std::to_string(i)]);
|
||||
txt = block->forward(ctx, txt, txt_pe);
|
||||
sd::ggml_graph_cut::mark_graph_cut(txt, "boogu.context_refiner." + std::to_string(i), "txt");
|
||||
}
|
||||
|
||||
for (int i = 0; i < config.num_refiner_layers; i++) {
|
||||
auto block = std::dynamic_pointer_cast<BooguImageTransformerBlock>(blocks["noise_refiner." + std::to_string(i)]);
|
||||
img = block->forward(ctx, img, noise_pe, temb);
|
||||
sd::ggml_graph_cut::mark_graph_cut(img, "boogu.noise_refiner." + std::to_string(i), "img");
|
||||
}
|
||||
|
||||
ggml_tensor* combined_img = img;
|
||||
if (ref_img != nullptr) {
|
||||
auto ref_pe = ggml_ext_slice(ctx->ggml_ctx, pe, 3, txt_len, txt_len + ref_len);
|
||||
for (int i = 0; i < config.num_refiner_layers; i++) {
|
||||
auto block = std::dynamic_pointer_cast<BooguImageTransformerBlock>(blocks["ref_image_refiner." + std::to_string(i)]);
|
||||
ref_img = block->forward(ctx, ref_img, ref_pe, temb);
|
||||
sd::ggml_graph_cut::mark_graph_cut(ref_img, "boogu.ref_image_refiner." + std::to_string(i), "ref_img");
|
||||
}
|
||||
combined_img = ggml_concat(ctx->ggml_ctx, ref_img, img, 1);
|
||||
}
|
||||
|
||||
auto img_pe = ggml_ext_slice(ctx->ggml_ctx, pe, 3, txt_len, txt_len + combined_img->ne[1]);
|
||||
for (int i = 0; i < config.num_double_stream_layers; i++) {
|
||||
auto block = std::dynamic_pointer_cast<BooguImageDoubleStreamBlock>(blocks["double_stream_layers." + std::to_string(i)]);
|
||||
auto result = block->forward(ctx, combined_img, txt, pe, img_pe, temb);
|
||||
combined_img = result.first;
|
||||
txt = result.second;
|
||||
sd::ggml_graph_cut::mark_graph_cut(combined_img, "boogu.double_stream_layers." + std::to_string(i), "img");
|
||||
sd::ggml_graph_cut::mark_graph_cut(txt, "boogu.double_stream_layers." + std::to_string(i), "txt");
|
||||
}
|
||||
|
||||
auto hidden_states = ggml_concat(ctx->ggml_ctx, txt, combined_img, 1);
|
||||
for (int i = 0; i < config.num_layers; i++) {
|
||||
auto block = std::dynamic_pointer_cast<BooguImageTransformerBlock>(blocks["single_stream_layers." + std::to_string(i)]);
|
||||
hidden_states = block->forward(ctx, hidden_states, pe, temb);
|
||||
sd::ggml_graph_cut::mark_graph_cut(hidden_states, "boogu.single_stream_layers." + std::to_string(i), "hidden_states");
|
||||
}
|
||||
|
||||
hidden_states = norm_out->forward(ctx, hidden_states, temb);
|
||||
hidden_states = ggml_ext_slice(ctx->ggml_ctx, hidden_states, 1, hidden_states->ne[1] - img_len, hidden_states->ne[1]);
|
||||
hidden_states = DiT::unpatchify_and_crop(ctx->ggml_ctx, hidden_states, H, W, config.patch_size, config.patch_size, false);
|
||||
hidden_states = ggml_ext_scale(ctx->ggml_ctx, hidden_states, -1.f);
|
||||
return hidden_states;
|
||||
}
|
||||
};
|
||||
|
||||
__STATIC_INLINE__ int patched_token_count(int64_t size, int patch_size) {
|
||||
int pad = (patch_size - (static_cast<int>(size) % patch_size)) % patch_size;
|
||||
return (static_cast<int>(size) + pad) / patch_size;
|
||||
}
|
||||
|
||||
__STATIC_INLINE__ void append_spatial_ids(std::vector<std::vector<float>>& ids,
|
||||
int bs,
|
||||
int pe_shift,
|
||||
int h_tokens,
|
||||
int w_tokens) {
|
||||
std::vector<std::vector<float>> image_ids(h_tokens * w_tokens, std::vector<float>(3, 0.0f));
|
||||
for (int h = 0; h < h_tokens; h++) {
|
||||
for (int w = 0; w < w_tokens; w++) {
|
||||
image_ids[h * w_tokens + w][0] = static_cast<float>(pe_shift);
|
||||
image_ids[h * w_tokens + w][1] = static_cast<float>(h);
|
||||
image_ids[h * w_tokens + w][2] = static_cast<float>(w);
|
||||
}
|
||||
}
|
||||
for (int b = 0; b < bs; b++) {
|
||||
ids.insert(ids.end(), image_ids.begin(), image_ids.end());
|
||||
}
|
||||
}
|
||||
|
||||
__STATIC_INLINE__ std::vector<float> gen_boogu_pe(int h,
|
||||
int w,
|
||||
int patch_size,
|
||||
int bs,
|
||||
int context_len,
|
||||
const std::vector<ggml_tensor*>& ref_latents,
|
||||
int theta,
|
||||
const std::vector<int>& axes_dim) {
|
||||
std::vector<std::vector<float>> ids;
|
||||
ids.reserve(static_cast<size_t>(bs) * context_len);
|
||||
for (int b = 0; b < bs; b++) {
|
||||
for (int i = 0; i < context_len; i++) {
|
||||
float pos = static_cast<float>(i);
|
||||
ids.push_back({pos, pos, pos});
|
||||
}
|
||||
}
|
||||
|
||||
int pe_shift = context_len;
|
||||
for (ggml_tensor* ref : ref_latents) {
|
||||
int ref_h_tokens = patched_token_count(ref->ne[1], patch_size);
|
||||
int ref_w_tokens = patched_token_count(ref->ne[0], patch_size);
|
||||
append_spatial_ids(ids, bs, pe_shift, ref_h_tokens, ref_w_tokens);
|
||||
pe_shift += std::max(ref_h_tokens, ref_w_tokens);
|
||||
}
|
||||
|
||||
int h_tokens = patched_token_count(h, patch_size);
|
||||
int w_tokens = patched_token_count(w, patch_size);
|
||||
append_spatial_ids(ids, bs, pe_shift, h_tokens, w_tokens);
|
||||
|
||||
return Rope::embed_nd(ids, bs, static_cast<float>(theta), axes_dim);
|
||||
}
|
||||
|
||||
struct BooguImageRunner : public DiffusionModelRunner {
|
||||
BooguConfig config;
|
||||
BooguImageModel boogu;
|
||||
std::vector<float> pe_vec;
|
||||
|
||||
BooguImageRunner(ggml_backend_t backend,
|
||||
const String2TensorStorage& tensor_storage_map = {},
|
||||
const std::string prefix = "",
|
||||
SDVersion version = VERSION_BOOGU_IMAGE,
|
||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
: DiffusionModelRunner(backend, prefix, weight_manager),
|
||||
config(BooguConfig::detect_from_weights(tensor_storage_map, prefix)) {
|
||||
boogu = BooguImageModel(config);
|
||||
boogu.init(params_ctx, tensor_storage_map, prefix);
|
||||
}
|
||||
|
||||
std::string get_desc() override {
|
||||
return "boogu_image";
|
||||
}
|
||||
|
||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors, const std::string& prefix) override {
|
||||
boogu.get_param_tensors(tensors, prefix);
|
||||
}
|
||||
|
||||
ggml_cgraph* build_graph(const sd::Tensor<float>& x_tensor,
|
||||
const sd::Tensor<float>& timesteps_tensor,
|
||||
const sd::Tensor<float>& context_tensor,
|
||||
const std::vector<sd::Tensor<float>>& ref_latents_tensor = {}) {
|
||||
ggml_cgraph* gf = new_graph_custom(BOOGU_GRAPH_SIZE);
|
||||
ggml_tensor* x = make_input(x_tensor);
|
||||
ggml_tensor* timesteps = make_input(timesteps_tensor);
|
||||
GGML_ASSERT(x->ne[3] == 1);
|
||||
GGML_ASSERT(!context_tensor.empty());
|
||||
ggml_tensor* context = make_input(context_tensor);
|
||||
|
||||
std::vector<ggml_tensor*> ref_latents;
|
||||
ref_latents.reserve(ref_latents_tensor.size());
|
||||
for (const auto& ref_latent_tensor : ref_latents_tensor) {
|
||||
ref_latents.push_back(make_input(ref_latent_tensor));
|
||||
}
|
||||
|
||||
pe_vec = gen_boogu_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]),
|
||||
ref_latents,
|
||||
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 runner_ctx = get_context();
|
||||
ggml_tensor* out = boogu.forward(&runner_ctx, x, timesteps, context, pe, ref_latents);
|
||||
ggml_build_forward_expand(gf, out);
|
||||
return gf;
|
||||
}
|
||||
|
||||
sd::Tensor<float> compute(int n_threads,
|
||||
const sd::Tensor<float>& x,
|
||||
const sd::Tensor<float>& timesteps,
|
||||
const sd::Tensor<float>& context,
|
||||
const std::vector<sd::Tensor<float>>& ref_latents = {}) {
|
||||
auto get_graph = [&]() -> ggml_cgraph* {
|
||||
return build_graph(x, timesteps, context, ref_latents);
|
||||
};
|
||||
return restore_trailing_singleton_dims(GGMLRunner::compute<float>(get_graph, n_threads, false, false, false), x.dim());
|
||||
}
|
||||
|
||||
sd::Tensor<float> compute(int n_threads,
|
||||
const DiffusionParams& diffusion_params) override {
|
||||
GGML_ASSERT(diffusion_params.x != nullptr);
|
||||
GGML_ASSERT(diffusion_params.timesteps != nullptr);
|
||||
static const std::vector<sd::Tensor<float>> empty_ref_latents;
|
||||
return compute(n_threads,
|
||||
*diffusion_params.x,
|
||||
*diffusion_params.timesteps,
|
||||
tensor_or_empty(diffusion_params.context),
|
||||
diffusion_params.ref_latents ? *diffusion_params.ref_latents : empty_ref_latents);
|
||||
}
|
||||
};
|
||||
} // namespace Boogu
|
||||
|
||||
#endif // __SD_MODEL_DIFFUSION_BOOGU_HPP__
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef __SD_MODEL_DIFFUSION_CONTROL_HPP__
|
||||
#ifndef __SD_MODEL_DIFFUSION_CONTROL_HPP__
|
||||
#define __SD_MODEL_DIFFUSION_CONTROL_HPP__
|
||||
|
||||
#include "model/common/block.hpp"
|
||||
@@ -312,16 +312,17 @@ struct ControlNet : public GGMLRunner {
|
||||
ControlNetBlock control_net;
|
||||
std::string weight_prefix;
|
||||
|
||||
ggml_backend_buffer_t control_buffer = nullptr;
|
||||
ggml_context* control_ctx = nullptr;
|
||||
std::vector<ggml_tensor*> control_outputs_ggml;
|
||||
ggml_tensor* guided_hint_output_ggml = nullptr;
|
||||
std::vector<sd::Tensor<float>> controls;
|
||||
sd::Tensor<float> guided_hint;
|
||||
bool guided_hint_cached = false;
|
||||
std::shared_ptr<ModelManager> owned_model_manager;
|
||||
ggml_backend_t params_backend = nullptr;
|
||||
|
||||
static const char* guided_hint_cache_name() {
|
||||
return "controlnet.guided_hint";
|
||||
}
|
||||
|
||||
ControlNet(ggml_backend_t backend,
|
||||
ggml_backend_t params_backend_,
|
||||
const String2TensorStorage& tensor_storage_map = {},
|
||||
@@ -336,44 +337,12 @@ struct ControlNet : public GGMLRunner {
|
||||
free_control_ctx();
|
||||
}
|
||||
|
||||
void alloc_control_ctx(std::vector<ggml_tensor*> outs) {
|
||||
ggml_init_params params;
|
||||
params.mem_size = static_cast<size_t>(outs.size() * ggml_tensor_overhead()) + 1024 * 1024;
|
||||
params.mem_buffer = nullptr;
|
||||
params.no_alloc = true;
|
||||
control_ctx = ggml_init(params);
|
||||
|
||||
control_outputs_ggml.resize(outs.size() - 1);
|
||||
|
||||
size_t control_buffer_size = 0;
|
||||
|
||||
guided_hint_output_ggml = ggml_dup_tensor(control_ctx, outs[0]);
|
||||
control_buffer_size += ggml_nbytes(guided_hint_output_ggml);
|
||||
|
||||
for (int i = 0; i < outs.size() - 1; i++) {
|
||||
control_outputs_ggml[i] = ggml_dup_tensor(control_ctx, outs[i + 1]);
|
||||
control_buffer_size += ggml_nbytes(control_outputs_ggml[i]);
|
||||
}
|
||||
|
||||
control_buffer = ggml_backend_alloc_ctx_tensors(control_ctx, runtime_backend);
|
||||
|
||||
LOG_DEBUG("control buffer size %.2fMB", control_buffer_size * 1.f / 1024.f / 1024.f);
|
||||
}
|
||||
|
||||
void free_control_ctx() {
|
||||
if (control_buffer != nullptr) {
|
||||
ggml_backend_buffer_free(control_buffer);
|
||||
control_buffer = nullptr;
|
||||
}
|
||||
if (control_ctx != nullptr) {
|
||||
ggml_free(control_ctx);
|
||||
control_ctx = nullptr;
|
||||
}
|
||||
guided_hint_output_ggml = nullptr;
|
||||
guided_hint_cached = false;
|
||||
guided_hint = {};
|
||||
control_outputs_ggml.clear();
|
||||
controls.clear();
|
||||
free_cache_ctx_and_buffer();
|
||||
}
|
||||
|
||||
std::string get_desc() override {
|
||||
@@ -397,11 +366,17 @@ struct ControlNet : public GGMLRunner {
|
||||
ggml_tensor* context = make_optional_input(context_tensor);
|
||||
ggml_tensor* y = make_optional_input(y_tensor);
|
||||
|
||||
guided_hint_output_ggml = nullptr;
|
||||
control_outputs_ggml.clear();
|
||||
|
||||
ggml_tensor* guided_hint_input = nullptr;
|
||||
if (guided_hint_cached && !guided_hint.empty()) {
|
||||
guided_hint_input = make_input(guided_hint);
|
||||
hint = nullptr;
|
||||
} else {
|
||||
if (guided_hint_cached) {
|
||||
guided_hint_input = get_cache_tensor_by_name(guided_hint_cache_name());
|
||||
if (guided_hint_input == nullptr) {
|
||||
guided_hint_cached = false;
|
||||
}
|
||||
}
|
||||
if (guided_hint_input == nullptr) {
|
||||
hint = make_input(hint_tensor);
|
||||
}
|
||||
|
||||
@@ -415,13 +390,19 @@ struct ControlNet : public GGMLRunner {
|
||||
context,
|
||||
y);
|
||||
|
||||
if (control_ctx == nullptr) {
|
||||
alloc_control_ctx(outs);
|
||||
if (guided_hint_input == nullptr && !outs.empty()) {
|
||||
guided_hint_output_ggml = outs[0];
|
||||
ggml_set_output(guided_hint_output_ggml);
|
||||
cache(guided_hint_cache_name(), guided_hint_output_ggml);
|
||||
ggml_build_forward_expand(gf, guided_hint_output_ggml);
|
||||
}
|
||||
|
||||
ggml_build_forward_expand(gf, ggml_cpy(compute_ctx, outs[0], guided_hint_output_ggml));
|
||||
for (int i = 0; i < outs.size() - 1; i++) {
|
||||
ggml_build_forward_expand(gf, ggml_cpy(compute_ctx, outs[i + 1], control_outputs_ggml[i]));
|
||||
control_outputs_ggml.reserve(outs.size() > 0 ? outs.size() - 1 : 0);
|
||||
for (size_t i = 1; i < outs.size(); i++) {
|
||||
ggml_tensor* control_output = outs[i];
|
||||
ggml_set_output(control_output);
|
||||
ggml_build_forward_expand(gf, control_output);
|
||||
control_outputs_ggml.push_back(control_output);
|
||||
}
|
||||
|
||||
return gf;
|
||||
@@ -441,15 +422,12 @@ struct ControlNet : public GGMLRunner {
|
||||
return build_graph(x, hint, timesteps, context, y);
|
||||
};
|
||||
|
||||
auto compute_result = GGMLRunner::compute<float>(get_graph, n_threads, false, false, false);
|
||||
auto compute_result = GGMLRunner::compute<float>(get_graph, n_threads, false, false, false, true);
|
||||
if (!compute_result.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (guided_hint_output_ggml != nullptr) {
|
||||
guided_hint = restore_trailing_singleton_dims(sd::make_sd_tensor_from_ggml<float>(guided_hint_output_ggml),
|
||||
4);
|
||||
}
|
||||
guided_hint_cached = get_cache_tensor_by_name(guided_hint_cache_name()) != nullptr;
|
||||
controls.clear();
|
||||
controls.reserve(control_outputs_ggml.size());
|
||||
for (ggml_tensor* control : control_outputs_ggml) {
|
||||
@@ -457,7 +435,6 @@ struct ControlNet : public GGMLRunner {
|
||||
GGML_ASSERT(!control_host.empty());
|
||||
controls.push_back(std::move(control_host));
|
||||
}
|
||||
guided_hint_cached = true;
|
||||
return controls;
|
||||
}
|
||||
|
||||
@@ -482,7 +459,7 @@ struct ControlNet : public GGMLRunner {
|
||||
manager->set_n_threads(n_threads);
|
||||
if (!manager->register_param_tensors("ControlNet",
|
||||
std::move(tensors),
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
runtime_backend,
|
||||
params_backend) ||
|
||||
!manager->validate_registered_tensors()) {
|
||||
|
||||
+133
-18
@@ -4,9 +4,11 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "model/adapter/pulid.hpp"
|
||||
#include "model/common/rope.hpp"
|
||||
#include "model/diffusion/dit.hpp"
|
||||
#include "model/diffusion/model.hpp"
|
||||
#include "model/diffusion/sefi_image.hpp"
|
||||
#include "model_loader.h"
|
||||
|
||||
#define FLUX_GRAPH_SIZE 10240
|
||||
@@ -25,6 +27,9 @@ namespace Flux {
|
||||
struct FluxConfig {
|
||||
SDVersion version = VERSION_FLUX;
|
||||
bool is_chroma = false;
|
||||
bool is_sefi = false;
|
||||
int64_t semantic_channels = 0;
|
||||
float sefi_delta_t = 0.1f;
|
||||
int patch_size = 2;
|
||||
int64_t in_channels = 64;
|
||||
int64_t out_channels = 64;
|
||||
@@ -49,6 +54,10 @@ namespace Flux {
|
||||
float ref_index_scale = 1.f;
|
||||
ChromaRadianceConfig chroma_radiance_params;
|
||||
|
||||
bool pulid_enabled = false;
|
||||
int pulid_double_interval = 2;
|
||||
int pulid_single_interval = 4;
|
||||
|
||||
static FluxConfig detect_from_weights(const String2TensorStorage& tensor_storage_map,
|
||||
const std::string& prefix,
|
||||
SDVersion version = VERSION_FLUX) {
|
||||
@@ -83,6 +92,21 @@ namespace Flux {
|
||||
config.share_modulation = true;
|
||||
config.ref_index_scale = 10.f;
|
||||
config.use_mlp_silu_act = true;
|
||||
} else if (sd_version_is_sefi_image(version)) {
|
||||
config.is_sefi = true;
|
||||
config.semantic_channels = 16;
|
||||
config.in_channels = 128 + config.semantic_channels;
|
||||
config.patch_size = 1;
|
||||
config.out_channels = 128 + config.semantic_channels;
|
||||
config.mlp_ratio = 3.f;
|
||||
config.theta = 2000;
|
||||
config.axes_dim = {32, 32, 32, 32};
|
||||
config.vec_in_dim = 0;
|
||||
config.qkv_bias = false;
|
||||
config.disable_bias = true;
|
||||
config.share_modulation = true;
|
||||
config.ref_index_scale = 10.f;
|
||||
config.use_mlp_silu_act = true;
|
||||
} else if (sd_version_is_longcat(version)) {
|
||||
config.context_in_dim = 3584;
|
||||
config.vec_in_dim = 0;
|
||||
@@ -138,6 +162,9 @@ namespace Flux {
|
||||
if (ends_with(name, "double_blocks.0.txt_attn.norm.key_norm.scale")) {
|
||||
head_dim = tensor_storage.ne[0];
|
||||
}
|
||||
if (name.find("pulid_ca.") != std::string::npos) {
|
||||
config.pulid_enabled = true;
|
||||
}
|
||||
}
|
||||
if (actual_radiance_patch_size > 0 && actual_radiance_patch_size != config.patch_size) {
|
||||
GGML_ASSERT(config.patch_size == 2 * actual_radiance_patch_size);
|
||||
@@ -715,8 +742,8 @@ namespace Flux {
|
||||
|
||||
auto m = adaLN_modulation_1->forward(ctx, ggml_silu(ctx->ggml_ctx, c)); // [N, 2 * hidden_size]
|
||||
auto m_vec = ggml_ext_chunk(ctx->ggml_ctx, m, 2, 0);
|
||||
shift = m_vec[0]; // [N, hidden_size]
|
||||
scale = m_vec[1]; // [N, hidden_size]
|
||||
shift = m_vec[0];
|
||||
scale = m_vec[1];
|
||||
}
|
||||
|
||||
x = Flux::modulate(ctx->ggml_ctx, norm_final->forward(ctx, x), shift, scale);
|
||||
@@ -894,6 +921,8 @@ namespace Flux {
|
||||
}
|
||||
if (config.is_chroma) {
|
||||
blocks["distilled_guidance_layer"] = std::make_shared<ChromaApproximator>(config.in_dim, config.hidden_size);
|
||||
} else if (config.is_sefi) {
|
||||
blocks["dual_time_embed"] = std::make_shared<SefiImage::SefiDualTimestepEmbeddings>(256, config.hidden_size);
|
||||
} else {
|
||||
blocks["time_in"] = std::make_shared<MLPEmbedder>(256, config.hidden_size, !config.disable_bias);
|
||||
if (config.vec_in_dim > 0) {
|
||||
@@ -957,6 +986,20 @@ namespace Flux {
|
||||
blocks["double_stream_modulation_txt"] = std::make_shared<Modulation>(config.hidden_size, true, !config.disable_bias);
|
||||
blocks["single_stream_modulation"] = std::make_shared<Modulation>(config.hidden_size, false, !config.disable_bias);
|
||||
}
|
||||
|
||||
if (config.pulid_enabled) {
|
||||
int num_double_ca = (config.depth + config.pulid_double_interval - 1) / config.pulid_double_interval;
|
||||
int num_single_ca = (config.depth_single_blocks + config.pulid_single_interval - 1) / config.pulid_single_interval;
|
||||
int num_ca = num_double_ca + num_single_ca;
|
||||
for (int i = 0; i < num_ca; i++) {
|
||||
blocks["pulid_ca." + std::to_string(i)] =
|
||||
std::shared_ptr<GGMLBlock>(new PuLIDPerceiverAttentionCA(
|
||||
/*dim=*/config.hidden_size,
|
||||
/*dim_head=*/PuLIDPerceiverAttentionCA::DEFAULT_DIM_HEAD,
|
||||
/*heads=*/PuLIDPerceiverAttentionCA::DEFAULT_HEADS,
|
||||
/*kv_dim=*/PuLIDPerceiverAttentionCA::DEFAULT_KV_DIM));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ggml_tensor* forward_orig(GGMLRunnerContext* ctx,
|
||||
@@ -967,7 +1010,9 @@ namespace Flux {
|
||||
ggml_tensor* guidance,
|
||||
ggml_tensor* pe,
|
||||
ggml_tensor* mod_index_arange = nullptr,
|
||||
std::vector<int> skip_layers = {}) {
|
||||
std::vector<int> skip_layers = {},
|
||||
ggml_tensor* pulid_id = nullptr,
|
||||
float pulid_id_weight = 1.0f) {
|
||||
auto img_in = std::dynamic_pointer_cast<Linear>(blocks["img_in"]);
|
||||
auto txt_in = std::dynamic_pointer_cast<Linear>(blocks["txt_in"]);
|
||||
auto final_layer = std::dynamic_pointer_cast<LastLayer>(blocks["final_layer"]);
|
||||
@@ -1003,6 +1048,11 @@ namespace Flux {
|
||||
if (y != nullptr) {
|
||||
txt_img_mask = ggml_pad(ctx->ggml_ctx, y, static_cast<int>(img->ne[1]), 0, 0, 0);
|
||||
}
|
||||
} else if (config.is_sefi) {
|
||||
auto dual_time_embed = std::dynamic_pointer_cast<SefiImage::SefiDualTimestepEmbeddings>(blocks["dual_time_embed"]);
|
||||
auto timestep_sem = ggml_view_1d(ctx->ggml_ctx, timesteps, 1, 0);
|
||||
auto timestep_tex = ggml_view_1d(ctx->ggml_ctx, timesteps, 1, ggml_element_size(timesteps));
|
||||
vec = dual_time_embed->forward(ctx, timestep_sem, timestep_tex);
|
||||
} else {
|
||||
auto time_in = std::dynamic_pointer_cast<MLPEmbedder>(blocks["time_in"]);
|
||||
vec = time_in->forward(ctx, ggml_ext_timestep_embedding(ctx->ggml_ctx, timesteps, 256, 10000, 1000.f));
|
||||
@@ -1044,6 +1094,13 @@ namespace Flux {
|
||||
sd::ggml_graph_cut::mark_graph_cut(txt, "flux.prelude", "txt");
|
||||
sd::ggml_graph_cut::mark_graph_cut(vec, "flux.prelude", "vec");
|
||||
|
||||
const bool pulid_active = config.pulid_enabled && pulid_id != nullptr;
|
||||
if (pulid_active && !skip_layers.empty()) {
|
||||
LOG_WARN("PuLID + skip_layers is not supported; disabling PuLID for this generation.");
|
||||
}
|
||||
const bool pulid_run = pulid_active && skip_layers.empty();
|
||||
int ca_idx = 0;
|
||||
|
||||
for (int i = 0; i < config.depth; i++) {
|
||||
if (skip_layers.size() > 0 && std::find(skip_layers.begin(), skip_layers.end(), i) != skip_layers.end()) {
|
||||
continue;
|
||||
@@ -1056,9 +1113,19 @@ namespace Flux {
|
||||
txt = img_txt.second; // [N, n_txt_token, hidden_size]
|
||||
sd::ggml_graph_cut::mark_graph_cut(img, "flux.double_blocks." + std::to_string(i), "img");
|
||||
sd::ggml_graph_cut::mark_graph_cut(txt, "flux.double_blocks." + std::to_string(i), "txt");
|
||||
|
||||
if (pulid_run && (i % config.pulid_double_interval == 0)) {
|
||||
auto pulid_ca = std::dynamic_pointer_cast<PuLIDPerceiverAttentionCA>(
|
||||
blocks["pulid_ca." + std::to_string(ca_idx)]);
|
||||
ggml_tensor* ca_out = pulid_ca->forward(ctx, pulid_id, img); // [N, n_img_token, hidden_size]
|
||||
img = ggml_add(ctx->ggml_ctx, img, ggml_scale(ctx->ggml_ctx, ca_out, pulid_id_weight));
|
||||
sd::ggml_graph_cut::mark_graph_cut(img, "flux.pulid_ca." + std::to_string(ca_idx), "img");
|
||||
ca_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
auto txt_img = ggml_concat(ctx->ggml_ctx, txt, img, 1); // [N, n_txt_token + n_img_token, hidden_size]
|
||||
auto txt_img = ggml_concat(ctx->ggml_ctx, txt, img, 1); // [N, n_txt_token + n_img_token, hidden_size]
|
||||
const int64_t n_txt_tok = txt->ne[1];
|
||||
for (int i = 0; i < config.depth_single_blocks; i++) {
|
||||
if (skip_layers.size() > 0 && std::find(skip_layers.begin(), skip_layers.end(), i + config.depth) != skip_layers.end()) {
|
||||
continue;
|
||||
@@ -1067,6 +1134,29 @@ namespace Flux {
|
||||
|
||||
txt_img = block->forward(ctx, txt_img, vec, pe, txt_img_mask, ss_mods);
|
||||
sd::ggml_graph_cut::mark_graph_cut(txt_img, "flux.single_blocks." + std::to_string(i), "txt_img");
|
||||
|
||||
if (pulid_run && (i % config.pulid_single_interval == 0)) {
|
||||
auto pulid_ca = std::dynamic_pointer_cast<PuLIDPerceiverAttentionCA>(
|
||||
blocks["pulid_ca." + std::to_string(ca_idx)]);
|
||||
ggml_tensor* txt_part = ggml_view_3d(ctx->ggml_ctx, txt_img,
|
||||
txt_img->ne[0], n_txt_tok, txt_img->ne[2],
|
||||
txt_img->nb[1], txt_img->nb[2],
|
||||
0);
|
||||
ggml_tensor* img_part = ggml_view_3d(ctx->ggml_ctx, txt_img,
|
||||
txt_img->ne[0],
|
||||
txt_img->ne[1] - n_txt_tok,
|
||||
txt_img->ne[2],
|
||||
txt_img->nb[1],
|
||||
txt_img->nb[2],
|
||||
n_txt_tok * txt_img->nb[1]);
|
||||
txt_part = ggml_cont(ctx->ggml_ctx, txt_part);
|
||||
img_part = ggml_cont(ctx->ggml_ctx, img_part);
|
||||
ggml_tensor* ca_out = pulid_ca->forward(ctx, pulid_id, img_part);
|
||||
img_part = ggml_add(ctx->ggml_ctx, img_part, ggml_scale(ctx->ggml_ctx, ca_out, pulid_id_weight));
|
||||
txt_img = ggml_concat(ctx->ggml_ctx, txt_part, img_part, 1);
|
||||
sd::ggml_graph_cut::mark_graph_cut(txt_img, "flux.pulid_ca." + std::to_string(ca_idx), "txt_img");
|
||||
ca_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
img = ggml_view_3d(ctx->ggml_ctx,
|
||||
@@ -1105,7 +1195,9 @@ namespace Flux {
|
||||
ggml_tensor* mod_index_arange = nullptr,
|
||||
ggml_tensor* dct = nullptr,
|
||||
std::vector<ggml_tensor*> ref_latents = {},
|
||||
std::vector<int> skip_layers = {}) {
|
||||
std::vector<int> skip_layers = {},
|
||||
ggml_tensor* pulid_id = nullptr,
|
||||
float pulid_id_weight = 1.0f) {
|
||||
GGML_ASSERT(x->ne[3] == 1);
|
||||
|
||||
int64_t W = x->ne[0];
|
||||
@@ -1131,7 +1223,8 @@ namespace Flux {
|
||||
img = ggml_reshape_3d(ctx->ggml_ctx, img, img->ne[0] * img->ne[1], img->ne[2], img->ne[3]); // [N, hidden_size, H/patch_size*W/patch_size]
|
||||
img = ggml_cont(ctx->ggml_ctx, ggml_ext_torch_permute(ctx->ggml_ctx, img, 1, 0, 2, 3)); // [N, H/patch_size*W/patch_size, hidden_size]
|
||||
|
||||
auto out = forward_orig(ctx, img, context, timestep, y, guidance, pe, mod_index_arange, skip_layers); // [N, n_img_token, hidden_size]
|
||||
auto out = forward_orig(ctx, img, context, timestep, y, guidance, pe, mod_index_arange, skip_layers,
|
||||
pulid_id, pulid_id_weight); // [N, n_img_token, hidden_size]
|
||||
|
||||
// nerf decode
|
||||
auto nerf_image_embedder = std::dynamic_pointer_cast<NerfEmbedder>(blocks["nerf_image_embedder"]);
|
||||
@@ -1179,7 +1272,9 @@ namespace Flux {
|
||||
ggml_tensor* mod_index_arange = nullptr,
|
||||
ggml_tensor* dct = nullptr,
|
||||
std::vector<ggml_tensor*> ref_latents = {},
|
||||
std::vector<int> skip_layers = {}) {
|
||||
std::vector<int> skip_layers = {},
|
||||
ggml_tensor* pulid_id = nullptr,
|
||||
float pulid_id_weight = 1.0f) {
|
||||
GGML_ASSERT(x->ne[3] == 1);
|
||||
|
||||
int64_t W = x->ne[0];
|
||||
@@ -1226,7 +1321,8 @@ namespace Flux {
|
||||
}
|
||||
}
|
||||
|
||||
auto out = forward_orig(ctx, img, context, timestep, y, guidance, pe, mod_index_arange, skip_layers); // [N, num_tokens, C * patch_size * patch_size]
|
||||
auto out = forward_orig(ctx, img, context, timestep, y, guidance, pe, mod_index_arange, skip_layers,
|
||||
pulid_id, pulid_id_weight); // [N, num_tokens, C * patch_size * patch_size]
|
||||
|
||||
if (out->ne[1] > img_tokens) {
|
||||
out = ggml_view_3d(ctx->ggml_ctx, out, out->ne[0], img_tokens, out->ne[2], out->nb[1], out->nb[2], 0);
|
||||
@@ -1248,7 +1344,9 @@ namespace Flux {
|
||||
ggml_tensor* mod_index_arange = nullptr,
|
||||
ggml_tensor* dct = nullptr,
|
||||
std::vector<ggml_tensor*> ref_latents = {},
|
||||
std::vector<int> skip_layers = {}) {
|
||||
std::vector<int> skip_layers = {},
|
||||
ggml_tensor* pulid_id = nullptr,
|
||||
float pulid_id_weight = 1.0f) {
|
||||
// Forward pass of DiT.
|
||||
// x: (N, C, H, W) tensor of spatial inputs (images or latent representations of images)
|
||||
// timestep: (N,) tensor of diffusion timesteps
|
||||
@@ -1271,7 +1369,9 @@ namespace Flux {
|
||||
mod_index_arange,
|
||||
dct,
|
||||
ref_latents,
|
||||
skip_layers);
|
||||
skip_layers,
|
||||
pulid_id,
|
||||
pulid_id_weight);
|
||||
} else {
|
||||
return forward_flux_chroma(ctx,
|
||||
x,
|
||||
@@ -1284,7 +1384,9 @@ namespace Flux {
|
||||
mod_index_arange,
|
||||
dct,
|
||||
ref_latents,
|
||||
skip_layers);
|
||||
skip_layers,
|
||||
pulid_id,
|
||||
pulid_id_weight);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1384,7 +1486,9 @@ namespace Flux {
|
||||
const sd::Tensor<float>& guidance_tensor = {},
|
||||
const std::vector<sd::Tensor<float>>& ref_latents_tensor = {},
|
||||
bool increase_ref_index = false,
|
||||
std::vector<int> skip_layers = {}) {
|
||||
std::vector<int> skip_layers = {},
|
||||
const sd::Tensor<float>& pulid_id_tensor = {},
|
||||
float pulid_id_weight = 1.0f) {
|
||||
ggml_tensor* x = make_input(x_tensor);
|
||||
ggml_tensor* timesteps = make_input(timesteps_tensor);
|
||||
ggml_tensor* context = make_optional_input(context_tensor);
|
||||
@@ -1422,7 +1526,7 @@ namespace Flux {
|
||||
set_backend_tensor_data(mod_index_arange, mod_index_arange_vec.data());
|
||||
}
|
||||
std::set<int> txt_arange_dims;
|
||||
if (sd_version_is_flux2(version)) {
|
||||
if (sd_version_is_flux2(version) || sd_version_is_sefi_image(version)) {
|
||||
txt_arange_dims = {3};
|
||||
increase_ref_index = true;
|
||||
} else if (version == VERSION_OVIS_IMAGE) {
|
||||
@@ -1461,6 +1565,10 @@ namespace Flux {
|
||||
set_backend_tensor_data(dct, dct_vec.data());
|
||||
}
|
||||
|
||||
ggml_tensor* pulid_id = pulid_id_tensor.empty()
|
||||
? nullptr
|
||||
: make_input(pulid_id_tensor);
|
||||
|
||||
auto runner_ctx = get_context();
|
||||
|
||||
ggml_tensor* out = flux.forward(&runner_ctx,
|
||||
@@ -1474,7 +1582,9 @@ namespace Flux {
|
||||
mod_index_arange,
|
||||
dct,
|
||||
ref_latents,
|
||||
skip_layers);
|
||||
skip_layers,
|
||||
pulid_id,
|
||||
pulid_id_weight);
|
||||
|
||||
ggml_build_forward_expand(gf, out);
|
||||
|
||||
@@ -1490,14 +1600,17 @@ namespace Flux {
|
||||
const sd::Tensor<float>& guidance = {},
|
||||
const std::vector<sd::Tensor<float>>& ref_latents = {},
|
||||
bool increase_ref_index = false,
|
||||
std::vector<int> skip_layers = std::vector<int>()) {
|
||||
std::vector<int> skip_layers = std::vector<int>(),
|
||||
const sd::Tensor<float>& pulid_id = {},
|
||||
float pulid_id_weight = 1.0f) {
|
||||
// x: [N, in_channels, h, w]
|
||||
// timesteps: [N, ]
|
||||
// context: [N, max_position, hidden_size]
|
||||
// y: [N, adm_in_channels] or [1, adm_in_channels]
|
||||
// guidance: [N, ]
|
||||
// pulid_id: empty (no injection) or [N, num_id_tokens=32, kv_dim=2048]
|
||||
auto get_graph = [&]() -> ggml_cgraph* {
|
||||
return build_graph(x, timesteps, context, c_concat, y, guidance, ref_latents, increase_ref_index, skip_layers);
|
||||
return build_graph(x, timesteps, context, c_concat, y, guidance, ref_latents, increase_ref_index, skip_layers, pulid_id, pulid_id_weight);
|
||||
};
|
||||
|
||||
auto result = restore_trailing_singleton_dims(GGMLRunner::compute<float>(get_graph, n_threads, false, false, false), x.dim());
|
||||
@@ -1520,7 +1633,9 @@ namespace Flux {
|
||||
tensor_or_empty(extra->guidance),
|
||||
diffusion_params.ref_latents ? *diffusion_params.ref_latents : empty_ref_latents,
|
||||
diffusion_params.increase_ref_index,
|
||||
extra->skip_layers ? *extra->skip_layers : empty_skip_layers);
|
||||
extra->skip_layers ? *extra->skip_layers : empty_skip_layers,
|
||||
tensor_or_empty(extra->pulid_id),
|
||||
extra->pulid_id_weight);
|
||||
}
|
||||
|
||||
void test() {
|
||||
@@ -1609,7 +1724,7 @@ namespace Flux {
|
||||
if (!model_manager->register_runner_params("Flux test",
|
||||
*flux,
|
||||
"model.diffusion_model",
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
backend,
|
||||
backend) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef __SD_MODEL_DIFFUSION_HIDREAM_O1_HPP__
|
||||
#ifndef __SD_MODEL_DIFFUSION_HIDREAM_O1_HPP__
|
||||
#define __SD_MODEL_DIFFUSION_HIDREAM_O1_HPP__
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -0,0 +1,683 @@
|
||||
#ifndef __SD_MODEL_DIFFUSION_KREA2_HPP__
|
||||
#define __SD_MODEL_DIFFUSION_KREA2_HPP__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "core/ggml_extend.hpp"
|
||||
#include "core/ggml_graph_cut.h"
|
||||
#include "model/common/rope.hpp"
|
||||
#include "model/diffusion/dit.hpp"
|
||||
#include "model/diffusion/flux.hpp"
|
||||
#include "model/diffusion/model.hpp"
|
||||
#include "model_loader.h"
|
||||
|
||||
namespace Krea2 {
|
||||
constexpr int KREA2_GRAPH_SIZE = 65536;
|
||||
|
||||
struct Krea2Config {
|
||||
int patch_size = 2;
|
||||
int64_t in_channels = 16;
|
||||
int64_t out_channels = 16;
|
||||
int64_t features = 6144;
|
||||
int64_t timestep_dim = 256;
|
||||
int64_t text_dim = 2560;
|
||||
int64_t text_layers = 12;
|
||||
int64_t layers = 28;
|
||||
int64_t heads = 48;
|
||||
int64_t kv_heads = 12;
|
||||
int64_t text_heads = 20;
|
||||
int64_t text_kv_heads = 20;
|
||||
int64_t mlp_multiplier = 4;
|
||||
float theta = 1000.f;
|
||||
float norm_eps = 1e-5f;
|
||||
std::vector<int> axes_dim = {32, 48, 48};
|
||||
int axes_dim_sum = 128;
|
||||
|
||||
int64_t head_dim() const {
|
||||
return features / heads;
|
||||
}
|
||||
|
||||
static int64_t count_blocks(const String2TensorStorage& tensor_storage_map,
|
||||
const std::string& prefix,
|
||||
const std::string& block_prefix) {
|
||||
int64_t count = 0;
|
||||
std::string full_prefix = prefix.empty() ? block_prefix : prefix + "." + block_prefix;
|
||||
for (const auto& [name, _] : tensor_storage_map) {
|
||||
if (!starts_with(name, full_prefix)) {
|
||||
continue;
|
||||
}
|
||||
std::string tail = name.substr(full_prefix.size());
|
||||
size_t dot = tail.find('.');
|
||||
if (dot == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
int block_index = std::atoi(tail.substr(0, dot).c_str());
|
||||
count = std::max<int64_t>(count, block_index + 1);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void update_axes_dim() {
|
||||
int64_t dim_head = head_dim();
|
||||
int64_t unit = dim_head / 16;
|
||||
axes_dim = {
|
||||
static_cast<int>(dim_head - 12 * unit),
|
||||
static_cast<int>(6 * unit),
|
||||
static_cast<int>(6 * unit),
|
||||
};
|
||||
axes_dim_sum = axes_dim[0] + axes_dim[1] + axes_dim[2];
|
||||
}
|
||||
|
||||
static Krea2Config detect_from_weights(const String2TensorStorage& tensor_storage_map,
|
||||
const std::string& prefix) {
|
||||
Krea2Config config;
|
||||
int64_t detected_head_dim = 0;
|
||||
int64_t detected_text_head_dim = 0;
|
||||
|
||||
for (const auto& [name, tensor_storage] : tensor_storage_map) {
|
||||
if (!starts_with(name, prefix)) {
|
||||
continue;
|
||||
}
|
||||
if (ends_with(name, "first.weight") && tensor_storage.n_dims == 2) {
|
||||
config.in_channels = tensor_storage.ne[0] / (config.patch_size * config.patch_size);
|
||||
config.out_channels = config.in_channels;
|
||||
config.features = tensor_storage.ne[1];
|
||||
} else if (ends_with(name, "blocks.0.attn.qknorm.qnorm.scale") && tensor_storage.n_dims == 1) {
|
||||
detected_head_dim = tensor_storage.ne[0];
|
||||
} else if (ends_with(name, "blocks.0.attn.wq.weight") && tensor_storage.n_dims == 2) {
|
||||
if (detected_head_dim > 0) {
|
||||
config.heads = tensor_storage.ne[1] / detected_head_dim;
|
||||
}
|
||||
} else if (ends_with(name, "blocks.0.attn.wk.weight") && tensor_storage.n_dims == 2) {
|
||||
if (detected_head_dim > 0) {
|
||||
config.kv_heads = tensor_storage.ne[1] / detected_head_dim;
|
||||
}
|
||||
} else if (ends_with(name, "txtfusion.projector.weight") && tensor_storage.n_dims == 2) {
|
||||
config.text_layers = tensor_storage.ne[0];
|
||||
} else if (ends_with(name, "txtfusion.layerwise_blocks.0.prenorm.scale") && tensor_storage.n_dims == 1) {
|
||||
config.text_dim = tensor_storage.ne[0];
|
||||
} else if (ends_with(name, "txtfusion.layerwise_blocks.0.attn.qknorm.qnorm.scale") && tensor_storage.n_dims == 1) {
|
||||
detected_text_head_dim = tensor_storage.ne[0];
|
||||
} else if (ends_with(name, "txtfusion.layerwise_blocks.0.attn.wq.weight") && tensor_storage.n_dims == 2) {
|
||||
if (detected_text_head_dim > 0) {
|
||||
config.text_heads = tensor_storage.ne[1] / detected_text_head_dim;
|
||||
}
|
||||
} else if (ends_with(name, "txtfusion.layerwise_blocks.0.attn.wk.weight") && tensor_storage.n_dims == 2) {
|
||||
if (detected_text_head_dim > 0) {
|
||||
config.text_kv_heads = tensor_storage.ne[1] / detected_text_head_dim;
|
||||
}
|
||||
} else if (ends_with(name, "last.linear.weight") && tensor_storage.n_dims == 2) {
|
||||
config.out_channels = tensor_storage.ne[1] / (config.patch_size * config.patch_size);
|
||||
}
|
||||
}
|
||||
|
||||
config.layers = std::max<int64_t>(1, count_blocks(tensor_storage_map, prefix, "blocks."));
|
||||
if (detected_head_dim > 0 && config.features > 0) {
|
||||
config.heads = config.features / detected_head_dim;
|
||||
}
|
||||
if (detected_head_dim > 0) {
|
||||
std::string wk_name = prefix.empty() ? "blocks.0.attn.wk.weight" : prefix + ".blocks.0.attn.wk.weight";
|
||||
auto it = tensor_storage_map.find(wk_name);
|
||||
if (it != tensor_storage_map.end() && it->second.n_dims == 2) {
|
||||
config.kv_heads = it->second.ne[1] / detected_head_dim;
|
||||
}
|
||||
}
|
||||
if (detected_text_head_dim > 0 && config.text_dim > 0) {
|
||||
config.text_heads = config.text_dim / detected_text_head_dim;
|
||||
}
|
||||
if (detected_text_head_dim > 0) {
|
||||
std::string wk_name = prefix.empty() ? "txtfusion.layerwise_blocks.0.attn.wk.weight" : prefix + ".txtfusion.layerwise_blocks.0.attn.wk.weight";
|
||||
auto it = tensor_storage_map.find(wk_name);
|
||||
if (it != tensor_storage_map.end() && it->second.n_dims == 2) {
|
||||
config.text_kv_heads = it->second.ne[1] / detected_text_head_dim;
|
||||
}
|
||||
}
|
||||
config.update_axes_dim();
|
||||
|
||||
LOG_DEBUG("krea2: layers=%" PRId64 ", features=%" PRId64 ", heads=%" PRId64 ", kv_heads=%" PRId64 ", text_dim=%" PRId64 ", text_layers=%" PRId64 ", text_heads=%" PRId64 ", text_kv_heads=%" PRId64 ", channels=%" PRId64,
|
||||
config.layers,
|
||||
config.features,
|
||||
config.heads,
|
||||
config.kv_heads,
|
||||
config.text_dim,
|
||||
config.text_layers,
|
||||
config.text_heads,
|
||||
config.text_kv_heads,
|
||||
config.in_channels);
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
__STATIC_INLINE__ int64_t ceil_to_multiple(int64_t value, int64_t multiple) {
|
||||
return ((value + multiple - 1) / multiple) * multiple;
|
||||
}
|
||||
|
||||
class KreaRMSNorm : public UnaryBlock {
|
||||
protected:
|
||||
int64_t hidden_size;
|
||||
float eps;
|
||||
std::string prefix;
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map = {}, const std::string prefix = "") override {
|
||||
GGML_UNUSED(tensor_storage_map);
|
||||
this->prefix = prefix;
|
||||
params["scale"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, hidden_size);
|
||||
}
|
||||
|
||||
public:
|
||||
KreaRMSNorm(int64_t hidden_size, float eps = 1e-5f)
|
||||
: hidden_size(hidden_size),
|
||||
eps(eps) {}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
ggml_tensor* scale = params["scale"];
|
||||
scale = ggml_add(ctx->ggml_ctx, scale, ggml_ext_ones(ctx->ggml_ctx, scale->ne[0], 1, 1, 1));
|
||||
x = ggml_rms_norm(ctx->ggml_ctx, x, eps);
|
||||
x = ggml_mul_inplace(ctx->ggml_ctx, x, scale);
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
class KreaSwiGLU : public UnaryBlock {
|
||||
public:
|
||||
KreaSwiGLU(int64_t features, int64_t multiplier) {
|
||||
int64_t mlp_dim = ceil_to_multiple(((2 * features) / 3) * multiplier, 128);
|
||||
blocks["gate"] = std::make_shared<Linear>(features, mlp_dim, false);
|
||||
blocks["up"] = std::make_shared<Linear>(features, mlp_dim, false);
|
||||
blocks["down"] = std::make_shared<Linear>(mlp_dim, features, false);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
auto gate = std::dynamic_pointer_cast<Linear>(blocks["gate"]);
|
||||
auto up = std::dynamic_pointer_cast<Linear>(blocks["up"]);
|
||||
auto down = std::dynamic_pointer_cast<Linear>(blocks["down"]);
|
||||
|
||||
auto gated = ggml_silu(ctx->ggml_ctx, gate->forward(ctx, x));
|
||||
auto up_x = up->forward(ctx, x);
|
||||
x = ggml_mul(ctx->ggml_ctx, gated, up_x);
|
||||
return down->forward(ctx, x);
|
||||
}
|
||||
};
|
||||
|
||||
class KreaAttention : public GGMLBlock {
|
||||
protected:
|
||||
int64_t features;
|
||||
int64_t heads;
|
||||
int64_t kv_heads;
|
||||
int64_t head_dim_;
|
||||
|
||||
ggml_tensor* attention_no_rope(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* q,
|
||||
ggml_tensor* k,
|
||||
ggml_tensor* v,
|
||||
ggml_tensor* mask) {
|
||||
int64_t Lq = q->ne[2];
|
||||
int64_t Lk = k->ne[2];
|
||||
int64_t N = q->ne[3];
|
||||
q = ggml_reshape_3d(ctx->ggml_ctx, ggml_cont(ctx->ggml_ctx, q), head_dim_ * heads, Lq, N);
|
||||
k = ggml_reshape_3d(ctx->ggml_ctx, ggml_cont(ctx->ggml_ctx, k), head_dim_ * kv_heads, Lk, N);
|
||||
v = ggml_reshape_3d(ctx->ggml_ctx, ggml_cont(ctx->ggml_ctx, v), head_dim_ * kv_heads, Lk, N);
|
||||
return ggml_ext_attention_ext(ctx->ggml_ctx,
|
||||
ctx->backend,
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
heads,
|
||||
mask,
|
||||
false,
|
||||
ctx->flash_attn_enabled);
|
||||
}
|
||||
|
||||
public:
|
||||
KreaAttention(int64_t features,
|
||||
int64_t heads,
|
||||
int64_t kv_heads,
|
||||
float eps = 1e-5f)
|
||||
: features(features),
|
||||
heads(heads),
|
||||
kv_heads(kv_heads),
|
||||
head_dim_(features / heads) {
|
||||
blocks["wq"] = std::make_shared<Linear>(features, heads * head_dim_, false);
|
||||
blocks["wk"] = std::make_shared<Linear>(features, kv_heads * head_dim_, false);
|
||||
blocks["wv"] = std::make_shared<Linear>(features, kv_heads * head_dim_, false);
|
||||
blocks["gate"] = std::make_shared<Linear>(features, features, false);
|
||||
blocks["qknorm.qnorm"] = std::make_shared<KreaRMSNorm>(head_dim_, eps);
|
||||
blocks["qknorm.knorm"] = std::make_shared<KreaRMSNorm>(head_dim_, eps);
|
||||
blocks["wo"] = std::make_shared<Linear>(features, features, false);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* pe = nullptr,
|
||||
ggml_tensor* mask = nullptr) {
|
||||
auto wq = std::dynamic_pointer_cast<Linear>(blocks["wq"]);
|
||||
auto wk = std::dynamic_pointer_cast<Linear>(blocks["wk"]);
|
||||
auto wv = std::dynamic_pointer_cast<Linear>(blocks["wv"]);
|
||||
auto gate = std::dynamic_pointer_cast<Linear>(blocks["gate"]);
|
||||
auto qnorm = std::dynamic_pointer_cast<KreaRMSNorm>(blocks["qknorm.qnorm"]);
|
||||
auto knorm = std::dynamic_pointer_cast<KreaRMSNorm>(blocks["qknorm.knorm"]);
|
||||
auto wo = std::dynamic_pointer_cast<Linear>(blocks["wo"]);
|
||||
|
||||
if (sd_backend_is(ctx->backend, "Vulkan")) {
|
||||
wo->set_force_prec_f32(true);
|
||||
}
|
||||
|
||||
int64_t L = x->ne[1];
|
||||
int64_t N = x->ne[2];
|
||||
|
||||
auto q = wq->forward(ctx, x);
|
||||
q = ggml_reshape_4d(ctx->ggml_ctx, q, head_dim_, heads, L, N);
|
||||
auto k = wk->forward(ctx, x);
|
||||
k = ggml_reshape_4d(ctx->ggml_ctx, k, head_dim_, kv_heads, L, N);
|
||||
auto v = wv->forward(ctx, x);
|
||||
v = ggml_reshape_4d(ctx->ggml_ctx, v, head_dim_, kv_heads, L, N);
|
||||
|
||||
q = qnorm->forward(ctx, q);
|
||||
k = knorm->forward(ctx, k);
|
||||
|
||||
auto out = pe != nullptr ? Rope::attention(ctx, q, k, v, pe, mask)
|
||||
: attention_no_rope(ctx, q, k, v, mask);
|
||||
out = ggml_mul(ctx->ggml_ctx, out, ggml_sigmoid(ctx->ggml_ctx, gate->forward(ctx, x)));
|
||||
out = wo->forward(ctx, out);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
class KreaDoubleSharedModulation : public GGMLBlock {
|
||||
protected:
|
||||
int64_t dim;
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map = {}, const std::string prefix = "") override {
|
||||
GGML_UNUSED(tensor_storage_map);
|
||||
GGML_UNUSED(prefix);
|
||||
params["lin"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, dim * 6);
|
||||
}
|
||||
|
||||
public:
|
||||
KreaDoubleSharedModulation(int64_t dim)
|
||||
: dim(dim) {}
|
||||
|
||||
std::vector<ggml_tensor*> forward(GGMLRunnerContext* ctx, ggml_tensor* vec) {
|
||||
auto lin = ggml_repeat(ctx->ggml_ctx, params["lin"], vec);
|
||||
auto out = ggml_add(ctx->ggml_ctx, vec, lin);
|
||||
return ggml_ext_chunk(ctx->ggml_ctx, out, 6, 0);
|
||||
}
|
||||
};
|
||||
|
||||
class KreaFinalModulation : public GGMLBlock {
|
||||
protected:
|
||||
int64_t dim;
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map = {}, const std::string prefix = "") override {
|
||||
GGML_UNUSED(tensor_storage_map);
|
||||
GGML_UNUSED(prefix);
|
||||
params["lin"] = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, dim, 2);
|
||||
}
|
||||
|
||||
public:
|
||||
KreaFinalModulation(int64_t dim)
|
||||
: dim(dim) {}
|
||||
|
||||
std::vector<ggml_tensor*> forward(GGMLRunnerContext* ctx, ggml_tensor* vec) {
|
||||
auto out = ggml_add(ctx->ggml_ctx, params["lin"], vec);
|
||||
return ggml_ext_chunk(ctx->ggml_ctx, out, 2, 1);
|
||||
}
|
||||
};
|
||||
|
||||
class KreaTextFusionBlock : public UnaryBlock {
|
||||
public:
|
||||
KreaTextFusionBlock(int64_t dim,
|
||||
int64_t heads,
|
||||
int64_t kv_heads,
|
||||
int64_t multiplier,
|
||||
float eps) {
|
||||
blocks["prenorm"] = std::make_shared<KreaRMSNorm>(dim, eps);
|
||||
blocks["postnorm"] = std::make_shared<KreaRMSNorm>(dim, eps);
|
||||
blocks["attn"] = std::make_shared<KreaAttention>(dim, heads, kv_heads, eps);
|
||||
blocks["mlp"] = std::make_shared<KreaSwiGLU>(dim, multiplier);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
auto prenorm = std::dynamic_pointer_cast<KreaRMSNorm>(blocks["prenorm"]);
|
||||
auto postnorm = std::dynamic_pointer_cast<KreaRMSNorm>(blocks["postnorm"]);
|
||||
auto attn = std::dynamic_pointer_cast<KreaAttention>(blocks["attn"]);
|
||||
auto mlp = std::dynamic_pointer_cast<KreaSwiGLU>(blocks["mlp"]);
|
||||
|
||||
x = ggml_add(ctx->ggml_ctx, x, attn->forward(ctx, prenorm->forward(ctx, x)));
|
||||
x = ggml_add(ctx->ggml_ctx, x, mlp->forward(ctx, postnorm->forward(ctx, x)));
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
class KreaTextFusionTransformer : public UnaryBlock {
|
||||
protected:
|
||||
Krea2Config config;
|
||||
|
||||
public:
|
||||
explicit KreaTextFusionTransformer(Krea2Config config)
|
||||
: config(std::move(config)) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
blocks["layerwise_blocks." + std::to_string(i)] = std::make_shared<KreaTextFusionBlock>(this->config.text_dim,
|
||||
this->config.text_heads,
|
||||
this->config.text_kv_heads,
|
||||
this->config.mlp_multiplier,
|
||||
this->config.norm_eps);
|
||||
blocks["refiner_blocks." + std::to_string(i)] = std::make_shared<KreaTextFusionBlock>(this->config.text_dim,
|
||||
this->config.text_heads,
|
||||
this->config.text_kv_heads,
|
||||
this->config.mlp_multiplier,
|
||||
this->config.norm_eps);
|
||||
}
|
||||
blocks["projector"] = std::make_shared<Linear>(this->config.text_layers, 1, false);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* context) override {
|
||||
int64_t text_tokens = context->ne[1];
|
||||
int64_t batch = context->ne[2];
|
||||
|
||||
context = ggml_reshape_3d(ctx->ggml_ctx,
|
||||
context,
|
||||
config.text_dim,
|
||||
config.text_layers,
|
||||
text_tokens * batch);
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
auto block = std::dynamic_pointer_cast<KreaTextFusionBlock>(blocks["layerwise_blocks." + std::to_string(i)]);
|
||||
context = block->forward(ctx, context);
|
||||
}
|
||||
|
||||
context = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, context, 1, 0, 2, 3));
|
||||
auto projector = std::dynamic_pointer_cast<Linear>(blocks["projector"]);
|
||||
context = projector->forward(ctx, context);
|
||||
context = ggml_reshape_3d(ctx->ggml_ctx, context, config.text_dim, text_tokens, batch);
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
auto block = std::dynamic_pointer_cast<KreaTextFusionBlock>(blocks["refiner_blocks." + std::to_string(i)]);
|
||||
context = block->forward(ctx, context);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
};
|
||||
|
||||
class KreaSingleStreamBlock : public UnaryBlock {
|
||||
public:
|
||||
explicit KreaSingleStreamBlock(Krea2Config config) {
|
||||
blocks["mod"] = std::make_shared<KreaDoubleSharedModulation>(config.features);
|
||||
blocks["prenorm"] = std::make_shared<KreaRMSNorm>(config.features, config.norm_eps);
|
||||
blocks["postnorm"] = std::make_shared<KreaRMSNorm>(config.features, config.norm_eps);
|
||||
blocks["attn"] = std::make_shared<KreaAttention>(config.features, config.heads, config.kv_heads, config.norm_eps);
|
||||
blocks["mlp"] = std::make_shared<KreaSwiGLU>(config.features, config.mlp_multiplier);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* vec,
|
||||
ggml_tensor* pe) {
|
||||
auto mod = std::dynamic_pointer_cast<KreaDoubleSharedModulation>(blocks["mod"]);
|
||||
auto prenorm = std::dynamic_pointer_cast<KreaRMSNorm>(blocks["prenorm"]);
|
||||
auto postnorm = std::dynamic_pointer_cast<KreaRMSNorm>(blocks["postnorm"]);
|
||||
auto attn = std::dynamic_pointer_cast<KreaAttention>(blocks["attn"]);
|
||||
auto mlp = std::dynamic_pointer_cast<KreaSwiGLU>(blocks["mlp"]);
|
||||
|
||||
auto mods = mod->forward(ctx, vec);
|
||||
auto attn_input = Flux::modulate(ctx->ggml_ctx,
|
||||
prenorm->forward(ctx, x),
|
||||
mods[1],
|
||||
mods[0],
|
||||
true);
|
||||
auto attn_out = attn->forward(ctx, attn_input, pe);
|
||||
x = ggml_add(ctx->ggml_ctx, x, ggml_mul(ctx->ggml_ctx, attn_out, mods[2]));
|
||||
|
||||
auto mlp_input = Flux::modulate(ctx->ggml_ctx,
|
||||
postnorm->forward(ctx, x),
|
||||
mods[4],
|
||||
mods[3],
|
||||
true);
|
||||
auto mlp_out = mlp->forward(ctx, mlp_input);
|
||||
x = ggml_add(ctx->ggml_ctx, x, ggml_mul(ctx->ggml_ctx, mlp_out, mods[5]));
|
||||
return x;
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
GGML_UNUSED(ctx);
|
||||
GGML_UNUSED(x);
|
||||
GGML_ABORT("KreaSingleStreamBlock requires conditioning");
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class KreaTimeMLP : public UnaryBlock {
|
||||
public:
|
||||
explicit KreaTimeMLP(Krea2Config config) {
|
||||
blocks["0"] = std::make_shared<Linear>(config.timestep_dim, config.features, true);
|
||||
blocks["2"] = std::make_shared<Linear>(config.features, config.features, true);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
auto linear_0 = std::dynamic_pointer_cast<Linear>(blocks["0"]);
|
||||
auto linear_2 = std::dynamic_pointer_cast<Linear>(blocks["2"]);
|
||||
x = linear_0->forward(ctx, x);
|
||||
x = ggml_ext_gelu(ctx->ggml_ctx, x, false);
|
||||
x = linear_2->forward(ctx, x);
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
class KreaTProj : public UnaryBlock {
|
||||
public:
|
||||
explicit KreaTProj(Krea2Config config) {
|
||||
blocks["1"] = std::make_shared<Linear>(config.features, config.features * 6, true);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
auto linear_1 = std::dynamic_pointer_cast<Linear>(blocks["1"]);
|
||||
x = ggml_ext_gelu(ctx->ggml_ctx, x, false);
|
||||
x = linear_1->forward(ctx, x);
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
class KreaTextMLP : public UnaryBlock {
|
||||
public:
|
||||
explicit KreaTextMLP(Krea2Config config) {
|
||||
blocks["0"] = std::make_shared<KreaRMSNorm>(config.text_dim, config.norm_eps);
|
||||
blocks["1"] = std::make_shared<Linear>(config.text_dim, config.features, true);
|
||||
blocks["3"] = std::make_shared<Linear>(config.features, config.features, true);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
auto norm = std::dynamic_pointer_cast<KreaRMSNorm>(blocks["0"]);
|
||||
auto linear_1 = std::dynamic_pointer_cast<Linear>(blocks["1"]);
|
||||
auto linear_3 = std::dynamic_pointer_cast<Linear>(blocks["3"]);
|
||||
x = norm->forward(ctx, x);
|
||||
x = linear_1->forward(ctx, x);
|
||||
x = ggml_ext_gelu(ctx->ggml_ctx, x, true);
|
||||
x = linear_3->forward(ctx, x);
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
class KreaLastLayer : public GGMLBlock {
|
||||
public:
|
||||
explicit KreaLastLayer(Krea2Config config) {
|
||||
blocks["norm"] = std::make_shared<KreaRMSNorm>(config.features, config.norm_eps);
|
||||
blocks["linear"] = std::make_shared<Linear>(config.features, config.patch_size * config.patch_size * config.out_channels, true);
|
||||
blocks["modulation"] = std::make_shared<KreaFinalModulation>(config.features);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* vec) {
|
||||
auto norm = std::dynamic_pointer_cast<KreaRMSNorm>(blocks["norm"]);
|
||||
auto linear = std::dynamic_pointer_cast<Linear>(blocks["linear"]);
|
||||
auto modulation = std::dynamic_pointer_cast<KreaFinalModulation>(blocks["modulation"]);
|
||||
|
||||
auto mods = modulation->forward(ctx, vec);
|
||||
x = Flux::modulate(ctx->ggml_ctx,
|
||||
norm->forward(ctx, x),
|
||||
mods[1],
|
||||
mods[0],
|
||||
true);
|
||||
x = linear->forward(ctx, x);
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
class Krea2Model : public GGMLBlock {
|
||||
protected:
|
||||
Krea2Config config;
|
||||
|
||||
public:
|
||||
Krea2Model() = default;
|
||||
explicit Krea2Model(Krea2Config config)
|
||||
: config(std::move(config)) {
|
||||
blocks["first"] = std::make_shared<Linear>(this->config.patch_size * this->config.patch_size * this->config.in_channels,
|
||||
this->config.features,
|
||||
true);
|
||||
blocks["tmlp"] = std::make_shared<KreaTimeMLP>(this->config);
|
||||
blocks["txtfusion"] = std::make_shared<KreaTextFusionTransformer>(this->config);
|
||||
blocks["txtmlp"] = std::make_shared<KreaTextMLP>(this->config);
|
||||
blocks["tproj"] = std::make_shared<KreaTProj>(this->config);
|
||||
for (int i = 0; i < this->config.layers; ++i) {
|
||||
blocks["blocks." + std::to_string(i)] = std::make_shared<KreaSingleStreamBlock>(this->config);
|
||||
}
|
||||
blocks["last"] = std::make_shared<KreaLastLayer>(this->config);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* x,
|
||||
ggml_tensor* timestep,
|
||||
ggml_tensor* context,
|
||||
ggml_tensor* pe) {
|
||||
int64_t W = x->ne[0];
|
||||
int64_t H = x->ne[1];
|
||||
int64_t N = x->ne[3];
|
||||
GGML_ASSERT(N == 1);
|
||||
|
||||
auto first = std::dynamic_pointer_cast<Linear>(blocks["first"]);
|
||||
auto tmlp = std::dynamic_pointer_cast<KreaTimeMLP>(blocks["tmlp"]);
|
||||
auto txtfusion = std::dynamic_pointer_cast<KreaTextFusionTransformer>(blocks["txtfusion"]);
|
||||
auto txtmlp = std::dynamic_pointer_cast<KreaTextMLP>(blocks["txtmlp"]);
|
||||
auto tproj = std::dynamic_pointer_cast<KreaTProj>(blocks["tproj"]);
|
||||
auto last = std::dynamic_pointer_cast<KreaLastLayer>(blocks["last"]);
|
||||
|
||||
auto img = DiT::pad_and_patchify(ctx, x, config.patch_size, config.patch_size, true);
|
||||
int64_t img_len = img->ne[1];
|
||||
img = first->forward(ctx, img);
|
||||
|
||||
auto t = ggml_ext_timestep_embedding(ctx->ggml_ctx, timestep, static_cast<int>(config.timestep_dim), 10000, 1000.f);
|
||||
t = tmlp->forward(ctx, t);
|
||||
t = ggml_reshape_3d(ctx->ggml_ctx, t, t->ne[0], 1, t->ne[1]);
|
||||
auto tvec = tproj->forward(ctx, t);
|
||||
|
||||
auto txt = txtfusion->forward(ctx, context);
|
||||
txt = txtmlp->forward(ctx, txt);
|
||||
int64_t txt_len = txt->ne[1];
|
||||
|
||||
auto hidden_states = ggml_concat(ctx->ggml_ctx, txt, img, 1);
|
||||
for (int i = 0; i < config.layers; ++i) {
|
||||
auto block = std::dynamic_pointer_cast<KreaSingleStreamBlock>(blocks["blocks." + std::to_string(i)]);
|
||||
hidden_states = block->forward(ctx, hidden_states, tvec, pe);
|
||||
sd::ggml_graph_cut::mark_graph_cut(hidden_states, "krea2.blocks." + std::to_string(i), "hidden_states");
|
||||
}
|
||||
|
||||
hidden_states = last->forward(ctx, hidden_states, t);
|
||||
hidden_states = ggml_ext_slice(ctx->ggml_ctx, hidden_states, 1, txt_len, txt_len + img_len);
|
||||
hidden_states = DiT::unpatchify_and_crop(ctx->ggml_ctx, hidden_states, H, W, config.patch_size, config.patch_size, true);
|
||||
return hidden_states;
|
||||
}
|
||||
};
|
||||
|
||||
__STATIC_INLINE__ std::vector<float> gen_krea2_pe(int h,
|
||||
int w,
|
||||
int patch_size,
|
||||
int bs,
|
||||
int context_len,
|
||||
float theta,
|
||||
const std::vector<int>& axes_dim) {
|
||||
auto txt_ids = Rope::gen_flux_txt_ids(bs, context_len, 3, {});
|
||||
auto img_ids = Rope::gen_flux_img_ids(h, w, patch_size, bs, 3, 0, 0, 0, false);
|
||||
auto ids = Rope::concat_ids(txt_ids, img_ids, bs);
|
||||
return Rope::embed_nd(ids, bs, theta, axes_dim);
|
||||
}
|
||||
|
||||
struct Krea2Runner : public DiffusionModelRunner {
|
||||
Krea2Config config;
|
||||
Krea2Model model;
|
||||
std::vector<float> pe_vec;
|
||||
|
||||
Krea2Runner(ggml_backend_t backend,
|
||||
const String2TensorStorage& tensor_storage_map = {},
|
||||
const std::string prefix = "",
|
||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
: DiffusionModelRunner(backend, prefix, weight_manager),
|
||||
config(Krea2Config::detect_from_weights(tensor_storage_map, prefix)) {
|
||||
model = Krea2Model(config);
|
||||
model.init(params_ctx, tensor_storage_map, prefix);
|
||||
}
|
||||
|
||||
std::string get_desc() override {
|
||||
return "krea2";
|
||||
}
|
||||
|
||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors, const std::string& prefix) override {
|
||||
model.get_param_tensors(tensors, prefix);
|
||||
}
|
||||
|
||||
ggml_cgraph* build_graph(const sd::Tensor<float>& x_tensor,
|
||||
const sd::Tensor<float>& timesteps_tensor,
|
||||
const sd::Tensor<float>& context_tensor) {
|
||||
ggml_cgraph* gf = new_graph_custom(KREA2_GRAPH_SIZE);
|
||||
ggml_tensor* x = make_input(x_tensor);
|
||||
ggml_tensor* timesteps = make_input(timesteps_tensor);
|
||||
GGML_ASSERT(x->ne[3] == 1);
|
||||
GGML_ASSERT(!context_tensor.empty());
|
||||
ggml_tensor* context = make_input(context_tensor);
|
||||
|
||||
pe_vec = gen_krea2_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]),
|
||||
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 runner_ctx = get_context();
|
||||
ggml_tensor* out = model.forward(&runner_ctx, x, timesteps, context, pe);
|
||||
ggml_build_forward_expand(gf, out);
|
||||
return gf;
|
||||
}
|
||||
|
||||
sd::Tensor<float> compute(int n_threads,
|
||||
const sd::Tensor<float>& x,
|
||||
const sd::Tensor<float>& timesteps,
|
||||
const sd::Tensor<float>& context) {
|
||||
auto get_graph = [&]() -> ggml_cgraph* {
|
||||
return build_graph(x, timesteps, context);
|
||||
};
|
||||
return restore_trailing_singleton_dims(GGMLRunner::compute<float>(get_graph, n_threads, false, false, false), x.dim());
|
||||
}
|
||||
|
||||
sd::Tensor<float> compute(int n_threads,
|
||||
const DiffusionParams& diffusion_params) override {
|
||||
GGML_ASSERT(diffusion_params.x != nullptr);
|
||||
GGML_ASSERT(diffusion_params.timesteps != nullptr);
|
||||
return compute(n_threads,
|
||||
*diffusion_params.x,
|
||||
*diffusion_params.timesteps,
|
||||
tensor_or_empty(diffusion_params.context));
|
||||
}
|
||||
};
|
||||
} // namespace Krea2
|
||||
|
||||
#endif // __SD_MODEL_DIFFUSION_KREA2_HPP__
|
||||
@@ -2048,7 +2048,7 @@ namespace LTXV {
|
||||
if (!model_manager->register_runner_params("LTXAV test",
|
||||
*ltxav,
|
||||
"model.diffusion_model",
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
backend,
|
||||
backend) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
|
||||
@@ -1015,7 +1015,7 @@ struct MMDiTRunner : public DiffusionModelRunner {
|
||||
if (!model_manager->register_runner_params("MMDiT test",
|
||||
*mmdit,
|
||||
"model.diffusion_model",
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
backend,
|
||||
backend) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef __SD_MODEL_DIFFUSION_MODEL_HPP__
|
||||
#ifndef __SD_MODEL_DIFFUSION_MODEL_HPP__
|
||||
#define __SD_MODEL_DIFFUSION_MODEL_HPP__
|
||||
|
||||
#include <string>
|
||||
@@ -22,6 +22,8 @@ struct SkipLayerDiffusionExtra {
|
||||
struct FluxDiffusionExtra {
|
||||
const sd::Tensor<float>* guidance = nullptr;
|
||||
const std::vector<int>* skip_layers = nullptr;
|
||||
const sd::Tensor<float>* pulid_id = nullptr;
|
||||
float pulid_id_weight = 1.0f;
|
||||
};
|
||||
|
||||
struct AnimaDiffusionExtra {
|
||||
|
||||
@@ -715,7 +715,7 @@ namespace Qwen {
|
||||
if (!model_manager->register_runner_params("Qwen image test",
|
||||
*qwen_image,
|
||||
"model.diffusion_model",
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
backend,
|
||||
backend) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
#ifndef __SD_MODEL_DIFFUSION_SEFI_IMAGE_HPP__
|
||||
#define __SD_MODEL_DIFFUSION_SEFI_IMAGE_HPP__
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "model/common/block.hpp"
|
||||
|
||||
namespace SefiImage {
|
||||
struct SefiImageConfig {
|
||||
int64_t semantic_channels = 16;
|
||||
int64_t texture_latent_channels = 32;
|
||||
int64_t timestep_guidance_in_dim = 256;
|
||||
int64_t hidden_size = 3072;
|
||||
float timestep_shift_alpha = 0.3f;
|
||||
float delta_t = 0.1f;
|
||||
|
||||
int64_t packed_texture_channels(int patch_size) const {
|
||||
return texture_latent_channels * patch_size * patch_size;
|
||||
}
|
||||
|
||||
int64_t packed_input_channels(int patch_size) const {
|
||||
return semantic_channels + packed_texture_channels(patch_size);
|
||||
}
|
||||
|
||||
static SefiImageConfig detect_from_weights(const String2TensorStorage& tensor_storage_map,
|
||||
const std::string& prefix) {
|
||||
SefiImageConfig config;
|
||||
for (const auto& [name, tensor_storage] : tensor_storage_map) {
|
||||
if (!starts_with(name, prefix)) {
|
||||
continue;
|
||||
}
|
||||
if (ends_with(name, "dual_time_embed.semantic_embedder.linear_1.weight") && tensor_storage.n_dims == 2) {
|
||||
config.timestep_guidance_in_dim = tensor_storage.ne[0];
|
||||
config.hidden_size = tensor_storage.ne[1] * 2;
|
||||
}
|
||||
}
|
||||
LOG_DEBUG("sefi_image: semantic_channels = %" PRId64 ", texture_latent_channels = %" PRId64 ", hidden_size = %" PRId64,
|
||||
config.semantic_channels,
|
||||
config.texture_latent_channels,
|
||||
config.hidden_size);
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
struct SefiTimestepEmbedding : public GGMLBlock {
|
||||
public:
|
||||
SefiTimestepEmbedding(int64_t in_channels, int64_t time_embed_dim) {
|
||||
blocks["linear_1"] = std::shared_ptr<GGMLBlock>(new Linear(in_channels, time_embed_dim, false));
|
||||
blocks["linear_2"] = std::shared_ptr<GGMLBlock>(new Linear(time_embed_dim, time_embed_dim, false));
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* sample) {
|
||||
auto linear_1 = std::dynamic_pointer_cast<Linear>(blocks["linear_1"]);
|
||||
auto linear_2 = std::dynamic_pointer_cast<Linear>(blocks["linear_2"]);
|
||||
|
||||
sample = linear_1->forward(ctx, sample);
|
||||
sample = ggml_silu_inplace(ctx->ggml_ctx, sample);
|
||||
sample = linear_2->forward(ctx, sample);
|
||||
return sample;
|
||||
}
|
||||
};
|
||||
|
||||
struct SefiDualTimestepEmbeddings : public GGMLBlock {
|
||||
public:
|
||||
SefiDualTimestepEmbeddings(int64_t in_channels, int64_t embedding_dim) {
|
||||
GGML_ASSERT(embedding_dim % 2 == 0);
|
||||
int64_t half_dim = embedding_dim / 2;
|
||||
blocks["semantic_embedder"] = std::make_shared<SefiTimestepEmbedding>(in_channels, half_dim);
|
||||
blocks["texture_embedder"] = std::make_shared<SefiTimestepEmbedding>(in_channels, half_dim);
|
||||
timestep_guidance_in_dim = in_channels;
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
ggml_tensor* timestep_sem,
|
||||
ggml_tensor* timestep_tex) {
|
||||
auto semantic_embedder = std::dynamic_pointer_cast<SefiTimestepEmbedding>(blocks["semantic_embedder"]);
|
||||
auto texture_embedder = std::dynamic_pointer_cast<SefiTimestepEmbedding>(blocks["texture_embedder"]);
|
||||
|
||||
auto sem_proj = ggml_ext_timestep_embedding(ctx->ggml_ctx, timestep_sem, (int)timestep_guidance_in_dim, 10000, 1.f);
|
||||
auto tex_proj = ggml_ext_timestep_embedding(ctx->ggml_ctx, timestep_tex, (int)timestep_guidance_in_dim, 10000, 1.f);
|
||||
auto sem_emb = semantic_embedder->forward(ctx, sem_proj);
|
||||
auto tex_emb = texture_embedder->forward(ctx, tex_proj);
|
||||
return ggml_concat(ctx->ggml_ctx, sem_emb, tex_emb, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
int64_t timestep_guidance_in_dim = 256;
|
||||
};
|
||||
} // namespace SefiImage
|
||||
|
||||
#endif // __SD_MODEL_DIFFUSION_SEFI_IMAGE_HPP__
|
||||
@@ -1040,7 +1040,7 @@ namespace WAN {
|
||||
if (!model_manager->register_runner_params("Wan test",
|
||||
*wan,
|
||||
"model.diffusion_model",
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
backend,
|
||||
backend) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
|
||||
@@ -723,7 +723,7 @@ namespace ZImage {
|
||||
if (!model_manager->register_runner_params("ZImage test",
|
||||
*z_image,
|
||||
"model.diffusion_model",
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
backend,
|
||||
backend) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef __SD_MODEL_TE_CLIP_HPP__
|
||||
#ifndef __SD_MODEL_TE_CLIP_HPP__
|
||||
#define __SD_MODEL_TE_CLIP_HPP__
|
||||
|
||||
#include "core/ggml_extend.hpp"
|
||||
|
||||
+65
-13
@@ -1,4 +1,4 @@
|
||||
#ifndef __SD_MODEL_TE_LLM_HPP__
|
||||
#ifndef __SD_MODEL_TE_LLM_HPP__
|
||||
#define __SD_MODEL_TE_LLM_HPP__
|
||||
|
||||
#include <algorithm>
|
||||
@@ -79,6 +79,7 @@ namespace LLM {
|
||||
int window_size = 112;
|
||||
int num_position_embeddings = 0;
|
||||
std::set<int> fullatt_block_indexes = {7, 15, 23, 31};
|
||||
bool split_patch_embed = false;
|
||||
};
|
||||
|
||||
struct LLMConfig {
|
||||
@@ -179,7 +180,8 @@ namespace LLM {
|
||||
config.num_experts_per_tok = 4;
|
||||
}
|
||||
|
||||
config.num_layers = 0;
|
||||
config.num_layers = 0;
|
||||
int detected_vision_layers = 0;
|
||||
for (const auto& [name, tensor_storage] : tensor_storage_map) {
|
||||
if (!starts_with(name, prefix)) {
|
||||
continue;
|
||||
@@ -190,6 +192,38 @@ namespace LLM {
|
||||
if (contains(name, "attn.q_proj")) {
|
||||
config.llama_cpp_style = true;
|
||||
}
|
||||
if (contains(name, "visual.patch_embed.proj.1.weight")) {
|
||||
config.vision.split_patch_embed = true;
|
||||
}
|
||||
if (contains(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];
|
||||
}
|
||||
if (contains(name, "visual.patch_embed.bias")) {
|
||||
config.vision.hidden_size = tensor_storage.ne[0];
|
||||
}
|
||||
if (contains(name, "visual.pos_embed.weight")) {
|
||||
config.vision.hidden_size = tensor_storage.ne[0];
|
||||
config.vision.num_position_embeddings = static_cast<int>(tensor_storage.ne[1]);
|
||||
}
|
||||
if (contains(name, "visual.blocks.")) {
|
||||
auto items = split_string(name.substr(pos), '.');
|
||||
if (items.size() > 2) {
|
||||
int block_index = atoi(items[2].c_str());
|
||||
if (block_index + 1 > detected_vision_layers) {
|
||||
detected_vision_layers = block_index + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (contains(name, "visual.blocks.0.mlp.linear_fc1.weight") ||
|
||||
contains(name, "visual.blocks.0.mlp.gate_proj.weight")) {
|
||||
config.vision.intermediate_size = tensor_storage.ne[1];
|
||||
}
|
||||
if (contains(name, "visual.merger.linear_fc2.weight") ||
|
||||
contains(name, "visual.merger.mlp.2.weight")) {
|
||||
config.vision.out_hidden_size = tensor_storage.ne[1];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
pos = name.find("layers.");
|
||||
@@ -216,9 +250,12 @@ namespace LLM {
|
||||
config.intermediate_size = tensor_storage.ne[1];
|
||||
}
|
||||
}
|
||||
if (arch == LLMArch::QWEN3 && config.num_layers == 28) {
|
||||
if ((arch == LLMArch::QWEN3 || arch == LLMArch::QWEN3_VL) && config.num_layers == 28) {
|
||||
config.num_heads = 16;
|
||||
}
|
||||
if (detected_vision_layers > 0) {
|
||||
config.vision.num_layers = detected_vision_layers;
|
||||
}
|
||||
LOG_DEBUG("llm: num_layers = %" PRId64 ", vocab_size = %" PRId64 ", hidden_size = %" PRId64 ", intermediate_size = %" PRId64,
|
||||
config.num_layers,
|
||||
config.vocab_size,
|
||||
@@ -539,40 +576,51 @@ namespace LLM {
|
||||
|
||||
struct VisionPatchEmbed : public GGMLBlock {
|
||||
protected:
|
||||
bool llama_cpp_style;
|
||||
bool split_patch_embed;
|
||||
bool bias;
|
||||
int patch_size;
|
||||
int temporal_patch_size;
|
||||
int64_t in_channels;
|
||||
int64_t embed_dim;
|
||||
|
||||
void init_params(ggml_context* ctx,
|
||||
const String2TensorStorage& tensor_storage_map = {},
|
||||
const std::string prefix = "") override {
|
||||
GGML_UNUSED(tensor_storage_map);
|
||||
GGML_UNUSED(prefix);
|
||||
if (split_patch_embed && bias) {
|
||||
params["bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, embed_dim);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
VisionPatchEmbed(bool llama_cpp_style,
|
||||
VisionPatchEmbed(bool split_patch_embed,
|
||||
LLMVisionArch arch,
|
||||
int patch_size = 14,
|
||||
int temporal_patch_size = 2,
|
||||
int64_t in_channels = 3,
|
||||
int64_t embed_dim = 1152)
|
||||
: llama_cpp_style(llama_cpp_style),
|
||||
: split_patch_embed(split_patch_embed),
|
||||
bias(arch == LLMVisionArch::QWEN3_VL),
|
||||
patch_size(patch_size),
|
||||
temporal_patch_size(temporal_patch_size),
|
||||
in_channels(in_channels),
|
||||
embed_dim(embed_dim) {
|
||||
bool bias = arch == LLMVisionArch::QWEN3_VL;
|
||||
if (llama_cpp_style) {
|
||||
if (split_patch_embed) {
|
||||
blocks["proj.0"] = std::shared_ptr<GGMLBlock>(new Conv2d(in_channels,
|
||||
embed_dim,
|
||||
{patch_size, patch_size},
|
||||
{patch_size, patch_size},
|
||||
{0, 0},
|
||||
{1, 1},
|
||||
bias));
|
||||
false));
|
||||
blocks["proj.1"] = std::shared_ptr<GGMLBlock>(new Conv2d(in_channels,
|
||||
embed_dim,
|
||||
{patch_size, patch_size},
|
||||
{patch_size, patch_size},
|
||||
{0, 0},
|
||||
{1, 1},
|
||||
bias));
|
||||
false));
|
||||
} else {
|
||||
std::tuple<int, int, int> kernel_size = {(int)temporal_patch_size, (int)patch_size, (int)patch_size};
|
||||
blocks["proj"] = std::shared_ptr<GGMLBlock>(new Conv3d(in_channels,
|
||||
@@ -593,7 +641,7 @@ namespace LLM {
|
||||
temporal_patch_size,
|
||||
ggml_nelements(x) / (temporal_patch_size * patch_size * patch_size));
|
||||
|
||||
if (llama_cpp_style) {
|
||||
if (split_patch_embed) {
|
||||
auto proj_0 = std::dynamic_pointer_cast<Conv2d>(blocks["proj.0"]);
|
||||
auto proj_1 = std::dynamic_pointer_cast<Conv2d>(blocks["proj.1"]);
|
||||
|
||||
@@ -606,6 +654,10 @@ namespace LLM {
|
||||
x1 = proj_1->forward(ctx, x1);
|
||||
|
||||
x = ggml_add(ctx->ggml_ctx, x0, x1);
|
||||
if (bias) {
|
||||
auto b = ggml_reshape_4d(ctx->ggml_ctx, params["bias"], 1, 1, embed_dim, 1);
|
||||
x = ggml_add_inplace(ctx->ggml_ctx, x, b);
|
||||
}
|
||||
} else {
|
||||
auto proj = std::dynamic_pointer_cast<Conv3d>(blocks["proj"]);
|
||||
|
||||
@@ -798,7 +850,7 @@ namespace LLM {
|
||||
spatial_merge_size(vision_params.spatial_merge_size),
|
||||
num_grid_per_side(vision_params.num_position_embeddings > 0 ? static_cast<int>(std::sqrt(vision_params.num_position_embeddings)) : 0),
|
||||
fullatt_block_indexes(vision_params.fullatt_block_indexes) {
|
||||
blocks["patch_embed"] = std::shared_ptr<GGMLBlock>(new VisionPatchEmbed(llama_cpp_style,
|
||||
blocks["patch_embed"] = std::shared_ptr<GGMLBlock>(new VisionPatchEmbed(vision_params.split_patch_embed,
|
||||
arch_,
|
||||
vision_params.patch_size,
|
||||
vision_params.temporal_patch_size,
|
||||
@@ -2084,7 +2136,7 @@ namespace LLM {
|
||||
if (!model_manager->register_runner_params("LLM test",
|
||||
*llm,
|
||||
"text_encoders.llm",
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
backend,
|
||||
backend) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
#ifndef __SD_MODEL_TE_T5_HPP__
|
||||
#ifndef __SD_MODEL_TE_T5_HPP__
|
||||
#define __SD_MODEL_TE_T5_HPP__
|
||||
|
||||
#include <cfloat>
|
||||
@@ -592,7 +592,7 @@ struct T5Embedder {
|
||||
if (!model_manager->register_runner_params("T5 test",
|
||||
*t5,
|
||||
"",
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
backend,
|
||||
backend) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef __SD_MODEL_UPSCALER_ESRGAN_HPP__
|
||||
#ifndef __SD_MODEL_UPSCALER_ESRGAN_HPP__
|
||||
#define __SD_MODEL_UPSCALER_ESRGAN_HPP__
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef __SD_MODEL_UPSCALER_LTX_LATENT_UPSCALER_HPP__
|
||||
#ifndef __SD_MODEL_UPSCALER_LTX_LATENT_UPSCALER_HPP__
|
||||
#define __SD_MODEL_UPSCALER_LTX_LATENT_UPSCALER_HPP__
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -682,7 +682,7 @@ struct AutoEncoderKL : public VAE {
|
||||
} else if (sd_version_is_sd3(version)) {
|
||||
scale_factor = 1.5305f;
|
||||
shift_factor = 0.0609f;
|
||||
} else if (sd_version_is_flux(version) || sd_version_is_z_image(version) || sd_version_is_longcat(version)) {
|
||||
} else if (sd_version_uses_flux_vae(version)) {
|
||||
scale_factor = 0.3611f;
|
||||
shift_factor = 0.1159f;
|
||||
} else if (sd_version_uses_flux2_vae(version)) {
|
||||
@@ -816,12 +816,13 @@ struct AutoEncoderKL : public VAE {
|
||||
}
|
||||
|
||||
sd::Tensor<float> diffusion_to_vae_latents(const sd::Tensor<float>& latents) override {
|
||||
auto latents_ = sd_version_is_sefi_image(version) ? sd::ops::slice(latents, 2, 16, 144) : latents;
|
||||
if (sd_version_uses_flux2_vae(version)) {
|
||||
int channel_dim = 2;
|
||||
auto [mean_tensor, std_tensor] = get_latents_mean_std(latents, channel_dim);
|
||||
return (latents * std_tensor) / scale_factor + mean_tensor;
|
||||
auto [mean_tensor, std_tensor] = get_latents_mean_std(latents_, channel_dim);
|
||||
return (latents_ * std_tensor) / scale_factor + mean_tensor;
|
||||
}
|
||||
return (latents / scale_factor) + shift_factor;
|
||||
return (latents_ / scale_factor) + shift_factor;
|
||||
}
|
||||
|
||||
sd::Tensor<float> vae_to_diffusion_latents(const sd::Tensor<float>& latents) override {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef __SD_MODEL_VAE_LTX_AUDIO_VAE_HPP__
|
||||
#ifndef __SD_MODEL_VAE_LTX_AUDIO_VAE_HPP__
|
||||
#define __SD_MODEL_VAE_LTX_AUDIO_VAE_HPP__
|
||||
|
||||
#include <cmath>
|
||||
@@ -1082,7 +1082,7 @@ namespace LTXV {
|
||||
|
||||
if (!model_manager->register_runner_params("LTX audio VAE test",
|
||||
*ltx_audio_vae,
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
backend,
|
||||
backend) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
|
||||
@@ -1426,7 +1426,7 @@ struct LTXVideoVAE : public VAE {
|
||||
const sd::Tensor<float>& z,
|
||||
bool decode_graph) override {
|
||||
if (!decode_graph && decode_only) {
|
||||
LOG_ERROR("LTX video VAE encode requires encoder weights; create the context with vae_decode_only=false");
|
||||
LOG_ERROR("LTX video VAE encode requires encoder weights");
|
||||
return {};
|
||||
}
|
||||
sd::Tensor<float> input = z;
|
||||
@@ -1538,7 +1538,7 @@ struct LTXVideoVAE : public VAE {
|
||||
|
||||
if (!model_manager->register_runner_params("LTX VAE test",
|
||||
*vae,
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
backend,
|
||||
backend) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
|
||||
@@ -548,7 +548,7 @@ public:
|
||||
}
|
||||
auto result = decoder->forward(ctx, z);
|
||||
if (sd_version_is_wan(version) || sd_version_is_ltxav(version)) {
|
||||
// (W, H, C, T) -> (W, H, T, C)
|
||||
// (W, H, T, C) -> (W, H, C, T)
|
||||
result = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, result, 0, 1, 3, 2));
|
||||
}
|
||||
return result;
|
||||
@@ -556,8 +556,10 @@ public:
|
||||
|
||||
ggml_tensor* encode(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
auto encoder = std::dynamic_pointer_cast<TinyVideoEncoder>(blocks["encoder"]);
|
||||
// (W, H, T, C) -> (W, H, C, T)
|
||||
x = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, x, 0, 1, 3, 2));
|
||||
if (sd_version_is_wan(version) || sd_version_is_ltxav(version)) {
|
||||
// (W, H, T, C) -> (W, H, C, T)
|
||||
x = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, x, 0, 1, 3, 2));
|
||||
}
|
||||
int64_t num_frames = x->ne[3];
|
||||
if (num_frames % encoder->t_downscale) {
|
||||
// pad to multiple of encoder->t_downscale at the end
|
||||
@@ -567,7 +569,10 @@ public:
|
||||
}
|
||||
}
|
||||
x = encoder->forward(ctx, x);
|
||||
x = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, x, 0, 1, 3, 2));
|
||||
if (sd_version_is_wan(version) || sd_version_is_ltxav(version)) {
|
||||
// (W, H, C, T) -> (W, H, T, C)
|
||||
x = ggml_cont(ctx->ggml_ctx, ggml_permute(ctx->ggml_ctx, x, 0, 1, 3, 2));
|
||||
}
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef __SD_MODEL_VAE_VAE_HPP__
|
||||
#ifndef __SD_MODEL_VAE_VAE_HPP__
|
||||
#define __SD_MODEL_VAE_VAE_HPP__
|
||||
|
||||
#include "core/tensor_ggml.hpp"
|
||||
|
||||
+176
-48
@@ -113,6 +113,24 @@ namespace WAN {
|
||||
}
|
||||
};
|
||||
|
||||
class Conv2dBut3d : public Conv2d {
|
||||
public:
|
||||
using Conv2d::Conv2d;
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
ggml_tensor* x_swapped = ggml_permute(ctx->ggml_ctx, x, 0, 1, 3, 2);
|
||||
x_swapped = ggml_cont(ctx->ggml_ctx, x_swapped);
|
||||
|
||||
ggml_tensor* out = Conv2d::forward(ctx, x_swapped);
|
||||
|
||||
ggml_tensor* out_swapped = ggml_permute(ctx->ggml_ctx, out, 0, 1, 3, 2);
|
||||
|
||||
out_swapped = ggml_cont(ctx->ggml_ctx, out_swapped);
|
||||
|
||||
return out_swapped;
|
||||
}
|
||||
};
|
||||
|
||||
class Resample : public GGMLBlock {
|
||||
protected:
|
||||
int64_t dim;
|
||||
@@ -338,19 +356,32 @@ namespace WAN {
|
||||
protected:
|
||||
int64_t in_dim;
|
||||
int64_t out_dim;
|
||||
bool is_2D;
|
||||
|
||||
public:
|
||||
ResidualBlock(int64_t in_dim, int64_t out_dim)
|
||||
: in_dim(in_dim), out_dim(out_dim) {
|
||||
ResidualBlock(int64_t in_dim, int64_t out_dim, bool is_2D = false)
|
||||
: in_dim(in_dim), out_dim(out_dim), is_2D(is_2D) {
|
||||
blocks["residual.0"] = std::shared_ptr<GGMLBlock>(new RMS_norm(in_dim));
|
||||
// residual.1 is nn.SiLU()
|
||||
blocks["residual.2"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(in_dim, out_dim, {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
if (is_2D) {
|
||||
blocks["residual.2"] = std::shared_ptr<GGMLBlock>(new Conv2dBut3d(in_dim, out_dim, {3, 3}, {1, 1}, {1, 1}));
|
||||
} else {
|
||||
blocks["residual.2"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(in_dim, out_dim, {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
}
|
||||
blocks["residual.3"] = std::shared_ptr<GGMLBlock>(new RMS_norm(out_dim));
|
||||
// residual.4 is nn.SiLU()
|
||||
// residual.5 is nn.Dropout()
|
||||
blocks["residual.6"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(out_dim, out_dim, {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
if (is_2D) {
|
||||
blocks["residual.6"] = std::shared_ptr<GGMLBlock>(new Conv2dBut3d(out_dim, out_dim, {3, 3}, {1, 1}, {1, 1}));
|
||||
} else {
|
||||
blocks["residual.6"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(out_dim, out_dim, {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
}
|
||||
if (in_dim != out_dim) {
|
||||
blocks["shortcut"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(in_dim, out_dim, {1, 1, 1}));
|
||||
if (is_2D) {
|
||||
blocks["shortcut"] = std::shared_ptr<GGMLBlock>(new Conv2dBut3d(in_dim, out_dim, {1, 1}));
|
||||
} else {
|
||||
blocks["shortcut"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(in_dim, out_dim, {1, 1, 1}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,9 +394,15 @@ namespace WAN {
|
||||
GGML_ASSERT(b == 1);
|
||||
ggml_tensor* h = x;
|
||||
if (in_dim != out_dim) {
|
||||
auto shortcut = std::dynamic_pointer_cast<CausalConv3d>(blocks["shortcut"]);
|
||||
if (is_2D) {
|
||||
auto shortcut = std::dynamic_pointer_cast<Conv2dBut3d>(blocks["shortcut"]);
|
||||
|
||||
h = shortcut->forward(ctx, x);
|
||||
h = shortcut->forward(ctx, x);
|
||||
} else {
|
||||
auto shortcut = std::dynamic_pointer_cast<CausalConv3d>(blocks["shortcut"]);
|
||||
|
||||
h = shortcut->forward(ctx, x);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
@@ -385,8 +422,13 @@ namespace WAN {
|
||||
cache_x,
|
||||
2);
|
||||
}
|
||||
if (is_2D) {
|
||||
auto layer = std::dynamic_pointer_cast<Conv2dBut3d>(blocks["residual." + std::to_string(i)]);
|
||||
|
||||
x = layer->forward(ctx, x, feat_cache[idx]);
|
||||
x = layer->forward(ctx, x);
|
||||
} else {
|
||||
x = layer->forward(ctx, x, feat_cache[idx]);
|
||||
}
|
||||
feat_cache[idx] = cache_x;
|
||||
feat_idx += 1;
|
||||
}
|
||||
@@ -412,13 +454,14 @@ namespace WAN {
|
||||
int64_t out_dim,
|
||||
int mult,
|
||||
bool temperal_downsample = false,
|
||||
bool down_flag = false)
|
||||
bool down_flag = false,
|
||||
bool is_2D = false)
|
||||
: mult(mult), down_flag(down_flag) {
|
||||
blocks["avg_shortcut"] = std::shared_ptr<GGMLBlock>(new AvgDown3D(in_dim, out_dim, temperal_downsample ? 2 : 1, down_flag ? 2 : 1));
|
||||
|
||||
int i = 0;
|
||||
for (; i < mult; i++) {
|
||||
blocks["downsamples." + std::to_string(i)] = std::shared_ptr<GGMLBlock>(new ResidualBlock(in_dim, out_dim));
|
||||
blocks["downsamples." + std::to_string(i)] = std::shared_ptr<GGMLBlock>(new ResidualBlock(in_dim, out_dim, is_2D));
|
||||
in_dim = out_dim;
|
||||
}
|
||||
if (down_flag) {
|
||||
@@ -472,7 +515,8 @@ namespace WAN {
|
||||
int64_t out_dim,
|
||||
int mult,
|
||||
bool temperal_upsample = false,
|
||||
bool up_flag = false)
|
||||
bool up_flag = false,
|
||||
bool is_2D = false)
|
||||
: mult(mult), up_flag(up_flag) {
|
||||
if (up_flag) {
|
||||
blocks["avg_shortcut"] = std::shared_ptr<GGMLBlock>(new DupUp3D(in_dim, out_dim, temperal_upsample ? 2 : 1, up_flag ? 2 : 1));
|
||||
@@ -480,7 +524,7 @@ namespace WAN {
|
||||
|
||||
int i = 0;
|
||||
for (; i < mult; i++) {
|
||||
blocks["upsamples." + std::to_string(i)] = std::shared_ptr<GGMLBlock>(new ResidualBlock(in_dim, out_dim));
|
||||
blocks["upsamples." + std::to_string(i)] = std::shared_ptr<GGMLBlock>(new ResidualBlock(in_dim, out_dim, is_2D));
|
||||
in_dim = out_dim;
|
||||
}
|
||||
if (up_flag) {
|
||||
@@ -592,6 +636,7 @@ namespace WAN {
|
||||
std::vector<int> dim_mult;
|
||||
int num_res_blocks;
|
||||
std::vector<bool> temperal_downsample;
|
||||
bool is_2D = false;
|
||||
|
||||
public:
|
||||
Encoder3d(int64_t dim = 128,
|
||||
@@ -599,23 +644,26 @@ namespace WAN {
|
||||
std::vector<int> dim_mult = {1, 2, 4, 4},
|
||||
int num_res_blocks = 2,
|
||||
std::vector<bool> temperal_downsample = {false, true, true},
|
||||
bool wan2_2 = false)
|
||||
bool wan2_2 = false,
|
||||
bool is_2D = false)
|
||||
: dim(dim),
|
||||
z_dim(z_dim),
|
||||
dim_mult(dim_mult),
|
||||
num_res_blocks(num_res_blocks),
|
||||
temperal_downsample(temperal_downsample),
|
||||
wan2_2(wan2_2) {
|
||||
wan2_2(wan2_2),
|
||||
is_2D(is_2D) {
|
||||
// attn_scales is always []
|
||||
std::vector<int64_t> dims = {dim};
|
||||
for (int u : dim_mult) {
|
||||
dims.push_back(dim * u);
|
||||
}
|
||||
|
||||
if (wan2_2) {
|
||||
blocks["conv1"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(12, dims[0], {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
int64_t input_dim = wan2_2 ? 12 : 3;
|
||||
if (is_2D) {
|
||||
blocks["conv1"] = std::shared_ptr<GGMLBlock>(new Conv2dBut3d(input_dim, dims[0], {3, 3}, {1, 1}, {1, 1}));
|
||||
} else {
|
||||
blocks["conv1"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(3, dims[0], {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
blocks["conv1"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(input_dim, dims[0], {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
@@ -630,12 +678,13 @@ namespace WAN {
|
||||
out_dim,
|
||||
num_res_blocks,
|
||||
t_down_flag,
|
||||
i != dim_mult.size() - 1));
|
||||
i != dim_mult.size() - 1,
|
||||
is_2D));
|
||||
|
||||
blocks["downsamples." + std::to_string(index++)] = block;
|
||||
} else {
|
||||
for (int j = 0; j < num_res_blocks; j++) {
|
||||
auto block = std::shared_ptr<GGMLBlock>(new ResidualBlock(in_dim, out_dim));
|
||||
auto block = std::shared_ptr<GGMLBlock>(new ResidualBlock(in_dim, out_dim, is_2D));
|
||||
blocks["downsamples." + std::to_string(index++)] = block;
|
||||
in_dim = out_dim;
|
||||
}
|
||||
@@ -648,13 +697,17 @@ namespace WAN {
|
||||
}
|
||||
}
|
||||
|
||||
blocks["middle.0"] = std::shared_ptr<GGMLBlock>(new ResidualBlock(out_dim, out_dim));
|
||||
blocks["middle.0"] = std::shared_ptr<GGMLBlock>(new ResidualBlock(out_dim, out_dim, is_2D));
|
||||
blocks["middle.1"] = std::shared_ptr<GGMLBlock>(new AttentionBlock(out_dim));
|
||||
blocks["middle.2"] = std::shared_ptr<GGMLBlock>(new ResidualBlock(out_dim, out_dim));
|
||||
blocks["middle.2"] = std::shared_ptr<GGMLBlock>(new ResidualBlock(out_dim, out_dim, is_2D));
|
||||
|
||||
blocks["head.0"] = std::shared_ptr<GGMLBlock>(new RMS_norm(out_dim));
|
||||
// head.1 is nn.SiLU()
|
||||
blocks["head.2"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(out_dim, z_dim, {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
if (is_2D) {
|
||||
blocks["head.2"] = std::shared_ptr<GGMLBlock>(new Conv2dBut3d(out_dim, z_dim, {3, 3}, {1, 1}, {1, 1}));
|
||||
} else {
|
||||
blocks["head.2"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(out_dim, z_dim, {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
}
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx,
|
||||
@@ -673,7 +726,10 @@ namespace WAN {
|
||||
auto head_2 = std::dynamic_pointer_cast<CausalConv3d>(blocks["head.2"]);
|
||||
|
||||
// conv1
|
||||
if (feat_cache.size() > 0) {
|
||||
if (is_2D) {
|
||||
auto conv1 = std::dynamic_pointer_cast<Conv2dBut3d>(blocks["conv1"]);
|
||||
x = conv1->forward(ctx, x);
|
||||
} else if (feat_cache.size() > 0) {
|
||||
int idx = feat_idx;
|
||||
auto cache_x = ggml_ext_slice(ctx->ggml_ctx, x, 2, -CACHE_T, x->ne[2]);
|
||||
if (cache_x->ne[2] < 2 && feat_cache[idx] != nullptr) {
|
||||
@@ -728,7 +784,10 @@ namespace WAN {
|
||||
// head
|
||||
x = head_0->forward(ctx, x);
|
||||
x = ggml_silu(ctx->ggml_ctx, x);
|
||||
if (feat_cache.size() > 0) {
|
||||
if (is_2D) {
|
||||
auto head_2 = std::dynamic_pointer_cast<Conv2dBut3d>(blocks["head.2"]);
|
||||
x = head_2->forward(ctx, x);
|
||||
} else if (feat_cache.size() > 0) {
|
||||
int idx = feat_idx;
|
||||
auto cache_x = ggml_ext_slice(ctx->ggml_ctx, x, 2, -CACHE_T, x->ne[2]);
|
||||
if (cache_x->ne[2] < 2 && feat_cache[idx] != nullptr) {
|
||||
@@ -758,6 +817,7 @@ namespace WAN {
|
||||
std::vector<int> dim_mult;
|
||||
int num_res_blocks;
|
||||
std::vector<bool> temperal_upsample;
|
||||
bool is_2D = false;
|
||||
|
||||
public:
|
||||
Decoder3d(int64_t dim = 128,
|
||||
@@ -765,13 +825,15 @@ namespace WAN {
|
||||
std::vector<int> dim_mult = {1, 2, 4, 4},
|
||||
int num_res_blocks = 2,
|
||||
std::vector<bool> temperal_upsample = {true, true, false},
|
||||
bool wan2_2 = false)
|
||||
bool wan2_2 = false,
|
||||
bool is_2D = false)
|
||||
: dim(dim),
|
||||
z_dim(z_dim),
|
||||
dim_mult(dim_mult),
|
||||
num_res_blocks(num_res_blocks),
|
||||
temperal_upsample(temperal_upsample),
|
||||
wan2_2(wan2_2) {
|
||||
wan2_2(wan2_2),
|
||||
is_2D(is_2D) {
|
||||
// attn_scales is always []
|
||||
std::vector<int64_t> dims = {dim_mult[dim_mult.size() - 1] * dim};
|
||||
for (int i = static_cast<int>(dim_mult.size()) - 1; i >= 0; i--) {
|
||||
@@ -779,12 +841,16 @@ namespace WAN {
|
||||
}
|
||||
|
||||
// init block
|
||||
blocks["conv1"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(z_dim, dims[0], {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
if (is_2D) {
|
||||
blocks["conv1"] = std::shared_ptr<GGMLBlock>(new Conv2dBut3d(z_dim, dims[0], {3, 3}, {1, 1}, {1, 1}));
|
||||
} else {
|
||||
blocks["conv1"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(z_dim, dims[0], {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
}
|
||||
|
||||
// middle blocks
|
||||
blocks["middle.0"] = std::shared_ptr<GGMLBlock>(new ResidualBlock(dims[0], dims[0]));
|
||||
blocks["middle.0"] = std::shared_ptr<GGMLBlock>(new ResidualBlock(dims[0], dims[0], is_2D));
|
||||
blocks["middle.1"] = std::shared_ptr<GGMLBlock>(new AttentionBlock(dims[0]));
|
||||
blocks["middle.2"] = std::shared_ptr<GGMLBlock>(new ResidualBlock(dims[0], dims[0]));
|
||||
blocks["middle.2"] = std::shared_ptr<GGMLBlock>(new ResidualBlock(dims[0], dims[0], is_2D));
|
||||
|
||||
// upsample blocks
|
||||
int index = 0;
|
||||
@@ -799,7 +865,8 @@ namespace WAN {
|
||||
out_dim,
|
||||
num_res_blocks + 1,
|
||||
t_up_flag,
|
||||
i != dim_mult.size() - 1));
|
||||
i != dim_mult.size() - 1,
|
||||
is_2D));
|
||||
|
||||
blocks["upsamples." + std::to_string(index++)] = block;
|
||||
} else {
|
||||
@@ -807,7 +874,7 @@ namespace WAN {
|
||||
in_dim = in_dim / 2;
|
||||
}
|
||||
for (int j = 0; j < num_res_blocks + 1; j++) {
|
||||
auto block = std::shared_ptr<GGMLBlock>(new ResidualBlock(in_dim, out_dim));
|
||||
auto block = std::shared_ptr<GGMLBlock>(new ResidualBlock(in_dim, out_dim, is_2D));
|
||||
blocks["upsamples." + std::to_string(index++)] = block;
|
||||
in_dim = out_dim;
|
||||
}
|
||||
@@ -821,13 +888,14 @@ namespace WAN {
|
||||
}
|
||||
|
||||
// output blocks
|
||||
blocks["head.0"] = std::shared_ptr<GGMLBlock>(new RMS_norm(out_dim));
|
||||
blocks["head.0"] = std::shared_ptr<GGMLBlock>(new RMS_norm(out_dim));
|
||||
int64_t final_dim = wan2_2 ? 12 : 3;
|
||||
// head.1 is nn.SiLU()
|
||||
if (wan2_2) {
|
||||
blocks["head.2"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(out_dim, 12, {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
if (is_2D) {
|
||||
blocks["head.2"] = std::shared_ptr<GGMLBlock>(new Conv2dBut3d(out_dim, final_dim, {3, 3}, {1, 1}, {1, 1}));
|
||||
|
||||
} else {
|
||||
blocks["head.2"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(out_dim, 3, {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
blocks["head.2"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(out_dim, final_dim, {3, 3, 3}, {1, 1, 1}, {1, 1, 1}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -847,7 +915,10 @@ namespace WAN {
|
||||
auto head_2 = std::dynamic_pointer_cast<CausalConv3d>(blocks["head.2"]);
|
||||
|
||||
// conv1
|
||||
if (feat_cache.size() > 0) {
|
||||
if (is_2D) {
|
||||
auto conv1 = std::dynamic_pointer_cast<Conv2dBut3d>(blocks["conv1"]);
|
||||
x = conv1->forward(ctx, x);
|
||||
} else if (feat_cache.size() > 0) {
|
||||
int idx = feat_idx;
|
||||
auto cache_x = ggml_ext_slice(ctx->ggml_ctx, x, 2, -CACHE_T, x->ne[2]);
|
||||
if (cache_x->ne[2] < 2 && feat_cache[idx] != nullptr) {
|
||||
@@ -902,7 +973,10 @@ namespace WAN {
|
||||
// head
|
||||
x = head_0->forward(ctx, x);
|
||||
x = ggml_silu(ctx->ggml_ctx, x);
|
||||
if (feat_cache.size() > 0) {
|
||||
if (is_2D) {
|
||||
auto head_2 = std::dynamic_pointer_cast<Conv2dBut3d>(blocks["head.2"]);
|
||||
x = head_2->forward(ctx, x);
|
||||
} else if (feat_cache.size() > 0) {
|
||||
int idx = feat_idx;
|
||||
auto cache_x = ggml_ext_slice(ctx->ggml_ctx, x, 2, -CACHE_T, x->ne[2]);
|
||||
if (cache_x->ne[2] < 2 && feat_cache[idx] != nullptr) {
|
||||
@@ -935,6 +1009,7 @@ namespace WAN {
|
||||
int num_res_blocks = 2;
|
||||
std::vector<bool> temperal_upsample = {true, true, false};
|
||||
std::vector<bool> temperal_downsample = {false, true, true};
|
||||
bool is_2D = false;
|
||||
|
||||
int _conv_num = 33;
|
||||
int _conv_idx = 0;
|
||||
@@ -951,8 +1026,8 @@ namespace WAN {
|
||||
}
|
||||
|
||||
public:
|
||||
WanVAE(bool decode_only = true, bool wan2_2 = false)
|
||||
: decode_only(decode_only), wan2_2(wan2_2) {
|
||||
WanVAE(bool decode_only = true, bool wan2_2 = false, bool is_2D = false)
|
||||
: decode_only(decode_only), wan2_2(wan2_2), is_2D(is_2D) {
|
||||
// attn_scales is always []
|
||||
if (wan2_2) {
|
||||
dim = 160;
|
||||
@@ -962,12 +1037,26 @@ namespace WAN {
|
||||
_conv_num = 34;
|
||||
_enc_conv_num = 26;
|
||||
}
|
||||
|
||||
if (is_2D) {
|
||||
temperal_upsample = {false, false, false};
|
||||
temperal_downsample = {false, false, false};
|
||||
}
|
||||
|
||||
if (!decode_only) {
|
||||
blocks["encoder"] = std::shared_ptr<GGMLBlock>(new Encoder3d(dim, z_dim * 2, dim_mult, num_res_blocks, temperal_downsample, wan2_2));
|
||||
blocks["conv1"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(z_dim * 2, z_dim * 2, {1, 1, 1}));
|
||||
blocks["encoder"] = std::shared_ptr<GGMLBlock>(new Encoder3d(dim, z_dim * 2, dim_mult, num_res_blocks, temperal_downsample, wan2_2, is_2D));
|
||||
if (is_2D) {
|
||||
blocks["conv1"] = std::shared_ptr<GGMLBlock>(new Conv2dBut3d(z_dim * 2, z_dim * 2, {1, 1}));
|
||||
} else {
|
||||
blocks["conv1"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(z_dim * 2, z_dim * 2, {1, 1, 1}));
|
||||
}
|
||||
}
|
||||
blocks["decoder"] = std::shared_ptr<GGMLBlock>(new Decoder3d(dec_dim, z_dim, dim_mult, num_res_blocks, temperal_upsample, wan2_2, is_2D));
|
||||
if (is_2D) {
|
||||
blocks["conv2"] = std::shared_ptr<GGMLBlock>(new Conv2dBut3d(z_dim, z_dim, {1, 1}));
|
||||
} else {
|
||||
blocks["conv2"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(z_dim, z_dim, {1, 1, 1}));
|
||||
}
|
||||
blocks["decoder"] = std::shared_ptr<GGMLBlock>(new Decoder3d(dec_dim, z_dim, dim_mult, num_res_blocks, temperal_upsample, wan2_2));
|
||||
blocks["conv2"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(z_dim, z_dim, {1, 1, 1}));
|
||||
}
|
||||
|
||||
static ggml_tensor* patchify(ggml_context* ctx,
|
||||
@@ -1030,6 +1119,10 @@ namespace WAN {
|
||||
GGML_ASSERT(b == 1);
|
||||
GGML_ASSERT(decode_only == false);
|
||||
|
||||
if (x->ne[2] > 1 && is_2D) {
|
||||
LOG_WARN("Using 2D VAE to encode video, expect poor results");
|
||||
}
|
||||
|
||||
clear_cache();
|
||||
|
||||
if (wan2_2) {
|
||||
@@ -1049,12 +1142,18 @@ namespace WAN {
|
||||
auto in = ggml_ext_slice(ctx->ggml_ctx, x, 2, 0, 1); // [b*c, 1, h, w]
|
||||
out = encoder->forward(ctx, in, b, _enc_feat_map, _enc_conv_idx, i);
|
||||
} else {
|
||||
auto in = ggml_ext_slice(ctx->ggml_ctx, x, 2, 1 + 4 * (i - 1), 1 + 4 * i); // [b*c, 4, h, w]
|
||||
// if is_2D, drop 3 out of 4 frames
|
||||
auto in = ggml_ext_slice(ctx->ggml_ctx, x, 2, 1 + 4 * (i - 1), (is_2D ? 1 - 3 : 1) + 4 * i); // [b*c, 4, h, w]
|
||||
auto out_ = encoder->forward(ctx, in, b, _enc_feat_map, _enc_conv_idx, i);
|
||||
out = ggml_concat(ctx->ggml_ctx, out, out_, 2);
|
||||
}
|
||||
}
|
||||
out = conv1->forward(ctx, out);
|
||||
if (is_2D) {
|
||||
auto conv1 = std::dynamic_pointer_cast<Conv2dBut3d>(blocks["conv1"]);
|
||||
out = conv1->forward(ctx, out);
|
||||
} else {
|
||||
out = conv1->forward(ctx, out);
|
||||
}
|
||||
auto mu = ggml_ext_chunk(ctx->ggml_ctx, out, 2, 3)[0];
|
||||
// sd::ggml_graph_cut::mark_graph_cut(mu, "wan_vae.encode.final", "mu");
|
||||
clear_cache();
|
||||
@@ -1067,13 +1166,23 @@ namespace WAN {
|
||||
// z: [b*c, t, h, w]
|
||||
GGML_ASSERT(b == 1);
|
||||
|
||||
if (z->ne[2] > 1 && is_2D) {
|
||||
LOG_WARN("Using 2D VAE to decode video, expect poor results");
|
||||
}
|
||||
|
||||
clear_cache();
|
||||
|
||||
auto decoder = std::dynamic_pointer_cast<Decoder3d>(blocks["decoder"]);
|
||||
auto conv2 = std::dynamic_pointer_cast<CausalConv3d>(blocks["conv2"]);
|
||||
|
||||
int64_t iter_ = z->ne[2];
|
||||
auto x = conv2->forward(ctx, z);
|
||||
auto x = z;
|
||||
if (is_2D) {
|
||||
auto conv2 = std::dynamic_pointer_cast<Conv2dBut3d>(blocks["conv2"]);
|
||||
x = conv2->forward(ctx, z);
|
||||
} else {
|
||||
x = conv2->forward(ctx, z);
|
||||
}
|
||||
// sd::ggml_graph_cut::mark_graph_cut(x, "wan_vae.decode.prelude", "x");
|
||||
ggml_tensor* out;
|
||||
for (int i = 0; i < iter_; i++) {
|
||||
@@ -1085,6 +1194,12 @@ namespace WAN {
|
||||
auto in = ggml_ext_slice(ctx->ggml_ctx, x, 2, i, i + 1); // [b*c, 1, h, w]
|
||||
auto out_ = decoder->forward(ctx, in, b, _feat_map, _conv_idx, i);
|
||||
out = ggml_concat(ctx->ggml_ctx, out, out_, 2);
|
||||
if (is_2D) {
|
||||
// repeat frames to avoid mismatch
|
||||
for (int j = 0; j < 4 - 1; j++) {
|
||||
out = ggml_concat(ctx->ggml_ctx, out, out_, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wan2_2) {
|
||||
@@ -1129,7 +1244,20 @@ namespace WAN {
|
||||
bool decode_only = false,
|
||||
SDVersion version = VERSION_WAN2,
|
||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
: VAE(version, backend, prefix, weight_manager), decode_only(decode_only), ae(decode_only, version == VERSION_WAN2_2_TI2V) {
|
||||
: VAE(version, backend, prefix, weight_manager), decode_only(decode_only) {
|
||||
bool is_2D = false;
|
||||
for (const auto& [name, tensor_storage] : tensor_storage_map) {
|
||||
if (ends_with(name, "decoder.conv1.weight")) {
|
||||
if (tensor_storage.ne[2] > 3) {
|
||||
is_2D = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_2D) {
|
||||
LOG_DEBUG("USING 2D VAE");
|
||||
}
|
||||
ae = WanVAE(decode_only, version == VERSION_WAN2_2_TI2V, is_2D);
|
||||
ae.init(params_ctx, tensor_storage_map, prefix);
|
||||
}
|
||||
|
||||
@@ -1340,7 +1468,7 @@ namespace WAN {
|
||||
|
||||
if (!model_manager->register_runner_params("Wan VAE test",
|
||||
*vae,
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
ModelManager::ResidencyMode::ParamBackend,
|
||||
backend,
|
||||
backend) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
|
||||
+24
-2
@@ -66,7 +66,6 @@ const char* unused_tensors[] = {
|
||||
// "v_pred", // Used to detect SDXL vpred models
|
||||
"text_encoders.llm.output.weight",
|
||||
"text_encoders.llm.lm_head.",
|
||||
"first_stage_model.bn.",
|
||||
};
|
||||
|
||||
bool is_unused_tensor(const std::string& name) {
|
||||
@@ -453,6 +452,10 @@ SDVersion ModelLoader::get_sd_version() {
|
||||
if (tensor_storage.name.find("embed_image_indicator.weight") != std::string::npos) {
|
||||
return VERSION_IDEOGRAM4;
|
||||
}
|
||||
if (tensor_storage.name.find("model.diffusion_model.txtfusion.projector.weight") != std::string::npos ||
|
||||
tensor_storage.name.find("model.diffusion_model.text_fusion.projector.weight") != std::string::npos) {
|
||||
return VERSION_KREA2;
|
||||
}
|
||||
if (tensor_storage.name.find("model.diffusion_model.nerf_final_layer_conv.") != std::string::npos) {
|
||||
return VERSION_CHROMA_RADIANCE;
|
||||
}
|
||||
@@ -476,6 +479,9 @@ SDVersion ModelLoader::get_sd_version() {
|
||||
if (tensor_storage.name.find("model.diffusion_model.double_stream_modulation_img.lin.weight") != std::string::npos) {
|
||||
is_flux2 = true;
|
||||
}
|
||||
if (tensor_storage.name.find("dual_time_embed.semantic_embedder.linear_1.weight") != std::string::npos) {
|
||||
return VERSION_SEFI_IMAGE;
|
||||
}
|
||||
if (tensor_storage.name.find("single_blocks.47.linear1.weight") != std::string::npos) {
|
||||
has_single_block_47 = true;
|
||||
}
|
||||
@@ -485,6 +491,9 @@ SDVersion ModelLoader::get_sd_version() {
|
||||
if (tensor_storage.name.find("model.diffusion_model.cap_embedder.0.weight") != std::string::npos) {
|
||||
return VERSION_Z_IMAGE;
|
||||
}
|
||||
if (tensor_storage.name.find("double_stream_layers.0.img_instruct_attn.processor.img_to_q.weight") != std::string::npos) {
|
||||
return VERSION_BOOGU_IMAGE;
|
||||
}
|
||||
if (tensor_storage.name.find("model.diffusion_model.layers.0.adaLN_sa_ln.weight") != std::string::npos) {
|
||||
return VERSION_ERNIE_IMAGE;
|
||||
}
|
||||
@@ -1002,6 +1011,7 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb,
|
||||
std::atomic<size_t> tensor_idx(0);
|
||||
std::atomic<bool> failed(false);
|
||||
std::vector<std::thread> workers;
|
||||
std::mutex rpc_backend_mutex;
|
||||
|
||||
for (int i = 0; i < n_threads; ++i) {
|
||||
workers.emplace_back([&, file_path, is_zip]() {
|
||||
@@ -1158,7 +1168,19 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb,
|
||||
|
||||
if (dst_tensor->buffer != nullptr && !ggml_backend_buffer_is_host(dst_tensor->buffer)) {
|
||||
t0 = ggml_time_ms();
|
||||
ggml_backend_tensor_set(dst_tensor, convert_buf, 0, ggml_nbytes(dst_tensor));
|
||||
|
||||
// RPC backends require serialized access to prevent concurrency issues
|
||||
const char* buffer_type_name = ggml_backend_buft_name(ggml_backend_buffer_get_type(dst_tensor->buffer));
|
||||
bool is_rpc_buffer = buffer_type_name != nullptr &&
|
||||
std::string(buffer_type_name).find("RPC") != std::string::npos;
|
||||
|
||||
if (is_rpc_buffer) {
|
||||
std::lock_guard<std::mutex> lock(rpc_backend_mutex);
|
||||
ggml_backend_tensor_set(dst_tensor, convert_buf, 0, ggml_nbytes(dst_tensor));
|
||||
} else {
|
||||
ggml_backend_tensor_set(dst_tensor, convert_buf, 0, ggml_nbytes(dst_tensor));
|
||||
}
|
||||
|
||||
t1 = ggml_time_ms();
|
||||
copy_to_backend_time_ms.fetch_add(t1 - t0);
|
||||
}
|
||||
|
||||
+15
-9
@@ -147,6 +147,17 @@ bool ModelManager::register_param_tensors(const std::string& desc,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModelManager::load_all_params_eagerly() {
|
||||
std::vector<TensorState*> all_states;
|
||||
all_states.reserve(tensor_states_.size());
|
||||
for (const auto& s : tensor_states_) {
|
||||
if (s != nullptr) {
|
||||
all_states.push_back(s.get());
|
||||
}
|
||||
}
|
||||
return load_tensors_to_params_backend(all_states);
|
||||
}
|
||||
|
||||
bool ModelManager::validate_registered_tensors() {
|
||||
bool ok = true;
|
||||
for (const auto& state : tensor_states_) {
|
||||
@@ -469,7 +480,7 @@ bool ModelManager::mmap_params(const std::vector<TensorState*>& states,
|
||||
return true;
|
||||
}
|
||||
|
||||
auto mmap_store = model_loader_.mmap_tensors(mmap_candidates, {}, true);
|
||||
auto mmap_store = model_loader_.mmap_tensors(mmap_candidates, {}, writable_mmap_);
|
||||
if (mmap_store.empty()) {
|
||||
return true;
|
||||
}
|
||||
@@ -492,7 +503,7 @@ bool ModelManager::mmap_params(const std::vector<TensorState*>& states,
|
||||
}
|
||||
|
||||
bool ModelManager::can_mmap_storage(const TensorState& state) const {
|
||||
if (!enable_mmap_ || state.residency_mode != ResidencyMode::Resident) {
|
||||
if (!enable_mmap_ || state.residency_mode != ResidencyMode::ParamBackend) {
|
||||
return false;
|
||||
}
|
||||
if (state.compute_backend == nullptr || state.params_backend == nullptr) {
|
||||
@@ -577,13 +588,8 @@ bool ModelManager::alloc_params_buffers(const std::vector<TensorState*>& states,
|
||||
for (TensorState* state : states) {
|
||||
ggml_tensor* tensor = state->tensor;
|
||||
size_t tensor_size = GGML_PAD(ggml_backend_buft_get_alloc_size(params_buft, tensor), alignment);
|
||||
if (max_size > 0 && tensor_size > max_size) {
|
||||
LOG_ERROR("model manager tensor '%s' is too large for params buffer: %zu > %zu",
|
||||
ggml_get_name(tensor),
|
||||
tensor_size,
|
||||
max_size);
|
||||
return false;
|
||||
}
|
||||
// Some backends, e.g. Vulkan, report a preferred chunk size here rather than a
|
||||
// hard per-tensor allocation limit. Oversized tensors are allocated alone.
|
||||
if (!chunk.empty() && max_size > 0 && chunk_size + tensor_size > max_size) {
|
||||
if (!alloc_chunk(chunk, chunk_size)) {
|
||||
return false;
|
||||
|
||||
+5
-2
@@ -16,7 +16,7 @@ class ModelManager : public RunnerWeightManager {
|
||||
public:
|
||||
enum class ResidencyMode {
|
||||
Disk,
|
||||
Resident,
|
||||
ParamBackend,
|
||||
};
|
||||
|
||||
struct LoraSpec {
|
||||
@@ -33,7 +33,7 @@ private:
|
||||
ggml_tensor* tensor = nullptr;
|
||||
std::string desc;
|
||||
|
||||
ResidencyMode residency_mode = ResidencyMode::Resident;
|
||||
ResidencyMode residency_mode = ResidencyMode::ParamBackend;
|
||||
ggml_backend_t compute_backend = nullptr;
|
||||
ggml_backend_t params_backend = nullptr;
|
||||
bool metadata_validated = false;
|
||||
@@ -69,6 +69,7 @@ private:
|
||||
uint64_t current_lora_epoch_ = 0;
|
||||
int n_threads_ = 0;
|
||||
bool enable_mmap_ = false;
|
||||
bool writable_mmap_ = false;
|
||||
|
||||
void finish_compute_backend_usage(const std::vector<TensorState*>& states);
|
||||
void release_all();
|
||||
@@ -110,6 +111,7 @@ public:
|
||||
model_loader_.set_n_threads(n_threads);
|
||||
}
|
||||
void set_enable_mmap(bool enable_mmap) { enable_mmap_ = enable_mmap; }
|
||||
void set_writable_mmap(bool writable_mmap) { writable_mmap_ = writable_mmap; }
|
||||
void set_common_ignore_tensors(std::set<std::string> ignore_tensors);
|
||||
void set_loras(std::vector<LoraSpec> loras, SDVersion version);
|
||||
|
||||
@@ -158,6 +160,7 @@ public:
|
||||
}
|
||||
|
||||
bool validate_registered_tensors();
|
||||
bool load_all_params_eagerly();
|
||||
|
||||
bool prepare_params(const std::vector<ggml_tensor*>& tensors) override;
|
||||
void release_compute_backend_params(const std::vector<ggml_tensor*>& tensors) override;
|
||||
|
||||
+132
-3
@@ -184,6 +184,27 @@ std::string convert_cond_stage_model_name(std::string name, std::string prefix)
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string convert_qwen3_vl_vision_name(std::string name) {
|
||||
static const std::vector<std::pair<std::string, std::string>> qwen3_vl_vision_name_map{
|
||||
{"mm.0.", "merger.linear_fc1."},
|
||||
{"mm.2.", "merger.linear_fc2."},
|
||||
{"v.post_ln.", "merger.norm."},
|
||||
{"v.position_embd.weight", "pos_embed.weight"},
|
||||
{"v.patch_embd.weight.1", "patch_embed.proj.1.weight"},
|
||||
{"v.patch_embd.weight", "patch_embed.proj.0.weight"},
|
||||
{"v.patch_embd.bias", "patch_embed.bias"},
|
||||
{"v.blk.", "blocks."},
|
||||
{"attn_qkv.", "attn.qkv."},
|
||||
{"attn_out.", "attn.proj."},
|
||||
{"ffn_up.", "mlp.linear_fc1."},
|
||||
{"ffn_down.", "mlp.linear_fc2."},
|
||||
{"ln1.", "norm1."},
|
||||
{"ln2.", "norm2."},
|
||||
};
|
||||
replace_with_name_map(name, qwen3_vl_vision_name_map);
|
||||
return name;
|
||||
}
|
||||
|
||||
// ref: https://github.com/huggingface/diffusers/blob/main/scripts/convert_diffusers_to_original_stable_diffusion.py
|
||||
std::string convert_diffusers_unet_to_original_sd1(std::string name) {
|
||||
// (stable-diffusion, HF Diffusers)
|
||||
@@ -683,6 +704,38 @@ std::string convert_other_dit_to_original_anima(std::string name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string convert_diffusers_dit_to_original_krea2(std::string name) {
|
||||
static const std::vector<std::pair<std::string, std::string>> prefix_map = {
|
||||
{"img_in.", "first."},
|
||||
{"time_embed.linear_1.", "tmlp.0."},
|
||||
{"time_embed.linear_2.", "tmlp.2."},
|
||||
{"time_mod_proj.", "tproj.1."},
|
||||
{"txt_in.linear_1.", "txtmlp.1."},
|
||||
{"txt_in.linear_2.", "txtmlp.3."},
|
||||
{"text_fusion.", "txtfusion."},
|
||||
{"transformer_blocks.", "blocks."},
|
||||
{"final_layer.", "last."},
|
||||
};
|
||||
static const std::vector<std::pair<std::string, std::string>> name_map = {
|
||||
{"attn.to_out.0.", "attn.wo."},
|
||||
{"attn.to_out.", "attn.wo."},
|
||||
{"attn.to_gate.", "attn.gate."},
|
||||
{"attn.to_q.", "attn.wq."},
|
||||
{"attn.to_k.", "attn.wk."},
|
||||
{"attn.to_v.", "attn.wv."},
|
||||
{"ff.gate.", "mlp.gate."},
|
||||
{"ff.up.", "mlp.up."},
|
||||
{"ff.down.", "mlp.down."},
|
||||
{"txt_in.norm.", "txtmlp.0."},
|
||||
{"last.norm.weight", "last.norm.scale"},
|
||||
{"last.modulation.weight", "last.modulation.lin"},
|
||||
};
|
||||
|
||||
replace_with_prefix_map(name, prefix_map);
|
||||
replace_with_name_map(name, name_map);
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string convert_diffusion_model_name(std::string name, std::string prefix, SDVersion version) {
|
||||
if (sd_version_is_sd1(version) || sd_version_is_sd2(version)) {
|
||||
name = convert_diffusers_unet_to_original_sd1(name);
|
||||
@@ -690,12 +743,14 @@ std::string convert_diffusion_model_name(std::string name, std::string prefix, S
|
||||
name = convert_diffusers_unet_to_original_sdxl(name);
|
||||
} else if (sd_version_is_sd3(version)) {
|
||||
name = convert_diffusers_dit_to_original_sd3(name);
|
||||
} else if (sd_version_is_flux(version) || sd_version_is_flux2(version) || sd_version_is_longcat(version)) {
|
||||
} else if (sd_version_is_flux(version) || sd_version_is_flux2(version) || sd_version_is_longcat(version) || sd_version_is_sefi_image(version)) {
|
||||
name = convert_diffusers_dit_to_original_flux(name);
|
||||
} else if (sd_version_is_z_image(version)) {
|
||||
name = convert_diffusers_dit_to_original_lumina2(name);
|
||||
} else if (sd_version_is_anima(version)) {
|
||||
name = convert_other_dit_to_original_anima(name);
|
||||
} else if (sd_version_is_krea2(version)) {
|
||||
name = convert_diffusers_dit_to_original_krea2(name);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
@@ -795,7 +850,77 @@ std::string convert_diffusers_vae_to_original_sd1(std::string name) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string convert_first_stage_model_name(std::string name, std::string prefix) {
|
||||
std::string convert_diffusers_to_original_wan_vae(std::string name) {
|
||||
static const std::vector<std::pair<std::string, std::string>> prefix_map = {
|
||||
{"quant_conv.", "conv1."},
|
||||
{"post_quant_conv.", "conv2."},
|
||||
|
||||
{"decoder.up_blocks.0.resnets.0.", "decoder.upsamples.0.residual."},
|
||||
{"decoder.up_blocks.0.resnets.1.", "decoder.upsamples.1.residual."},
|
||||
{"decoder.up_blocks.0.resnets.2.", "decoder.upsamples.2.residual."},
|
||||
{"decoder.up_blocks.0.upsamplers.0.", "decoder.upsamples.3."},
|
||||
|
||||
{"decoder.up_blocks.1.resnets.0.conv_shortcut.", "decoder.upsamples.4.shortcut."},
|
||||
{"decoder.up_blocks.1.resnets.0.", "decoder.upsamples.4.residual."},
|
||||
{"decoder.up_blocks.1.resnets.1.", "decoder.upsamples.5.residual."},
|
||||
{"decoder.up_blocks.1.resnets.2.", "decoder.upsamples.6.residual."},
|
||||
{"decoder.up_blocks.1.upsamplers.0.", "decoder.upsamples.7."},
|
||||
{"decoder.up_blocks.2.resnets.0.", "decoder.upsamples.8.residual."},
|
||||
{"decoder.up_blocks.2.resnets.1.", "decoder.upsamples.9.residual."},
|
||||
{"decoder.up_blocks.2.resnets.2.", "decoder.upsamples.10.residual."},
|
||||
{"decoder.up_blocks.2.upsamplers.0.", "decoder.upsamples.11."},
|
||||
{"decoder.up_blocks.3.resnets.0.", "decoder.upsamples.12.residual."},
|
||||
{"decoder.up_blocks.3.resnets.1.", "decoder.upsamples.13.residual."},
|
||||
{"decoder.up_blocks.3.resnets.2.", "decoder.upsamples.14.residual."},
|
||||
|
||||
{"encoder.down_blocks.0.", "encoder.downsamples.0.residual."},
|
||||
{"encoder.down_blocks.1.", "encoder.downsamples.1.residual."},
|
||||
{"encoder.down_blocks.2.", "encoder.downsamples.2."},
|
||||
{"encoder.down_blocks.3.conv_shortcut.", "encoder.downsamples.3.shortcut."},
|
||||
{"encoder.down_blocks.3.", "encoder.downsamples.3.residual."},
|
||||
{"encoder.down_blocks.4.", "encoder.downsamples.4.residual."},
|
||||
{"encoder.down_blocks.5.", "encoder.downsamples.5."},
|
||||
{"encoder.down_blocks.6.conv_shortcut.", "encoder.downsamples.6.shortcut."},
|
||||
{"encoder.down_blocks.6.", "encoder.downsamples.6.residual."},
|
||||
{"encoder.down_blocks.7.", "encoder.downsamples.7.residual."},
|
||||
{"encoder.down_blocks.8.", "encoder.downsamples.8."},
|
||||
{"encoder.down_blocks.9.", "encoder.downsamples.9.residual."},
|
||||
{"encoder.down_blocks.10.", "encoder.downsamples.10.residual."},
|
||||
};
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> shared_name_map = {
|
||||
{".conv_in.", ".conv1."},
|
||||
{".norm_out.", ".head.0."},
|
||||
{".conv_out.", ".head.2."},
|
||||
|
||||
{".mid_block.attentions.0.", ".middle.1."},
|
||||
{".mid_block.resnets.0.", ".middle.0.residual."},
|
||||
{".mid_block.resnets.1.", ".middle.2.residual."},
|
||||
};
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> resnet_name_map = {
|
||||
{".norm1.", ".0."},
|
||||
{".conv1.", ".2."},
|
||||
{".norm2.", ".3."},
|
||||
{".conv2.", ".6."},
|
||||
};
|
||||
|
||||
replace_with_name_map(name, shared_name_map);
|
||||
replace_with_prefix_map(name, prefix_map);
|
||||
|
||||
// Only apply the ResNet-specific renaming if the tensor belongs to a ResNet block.
|
||||
// This prevents generic ".conv1." or ".conv2." matching on top-level encoder/decoder convolutions.
|
||||
if (name.find(".residual.") != std::string::npos) {
|
||||
replace_with_name_map(name, resnet_name_map);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string convert_first_stage_model_name(std::string name, std::string prefix, SDVersion version) {
|
||||
if (sd_version_uses_wan_vae(version)) {
|
||||
return convert_diffusers_to_original_wan_vae(name);
|
||||
}
|
||||
static std::unordered_map<std::string, std::string> vae_name_map = {
|
||||
{"decoder.post_quant_conv.", "post_quant_conv."},
|
||||
{"encoder.quant_conv.", "quant_conv."},
|
||||
@@ -1154,6 +1279,10 @@ std::string convert_tensor_name(std::string name, SDVersion version) {
|
||||
|
||||
replace_with_prefix_map(name, prefix_map);
|
||||
|
||||
if ((sd_version_is_boogu_image(version) || sd_version_is_krea2(version)) && starts_with(name, "text_encoders.llm.visual.")) {
|
||||
name = convert_qwen3_vl_vision_name(std::move(name));
|
||||
}
|
||||
|
||||
// diffusion model
|
||||
{
|
||||
for (const auto& prefix : diffuison_model_prefix_vec) {
|
||||
@@ -1180,7 +1309,7 @@ std::string convert_tensor_name(std::string name, SDVersion version) {
|
||||
{
|
||||
for (const auto& prefix : first_stage_model_prefix_vec) {
|
||||
if (starts_with(name, prefix)) {
|
||||
name = convert_first_stage_model_name(name.substr(prefix.size()), prefix);
|
||||
name = convert_first_stage_model_name(name.substr(prefix.size()), prefix, version);
|
||||
if (version == VERSION_SDXS_512_DS || version == VERSION_SDXS_09) {
|
||||
name = "tae." + name;
|
||||
} else {
|
||||
|
||||
+571
-29
@@ -302,6 +302,137 @@ struct KarrasScheduler : SigmaScheduler {
|
||||
}
|
||||
};
|
||||
|
||||
struct BetaScheduler : SigmaScheduler {
|
||||
static constexpr double alpha = 0.6;
|
||||
static constexpr double beta = 0.6;
|
||||
|
||||
static double log_beta(double a, double b) {
|
||||
return std::lgamma(a) + std::lgamma(b) - std::lgamma(a + b);
|
||||
}
|
||||
|
||||
static double incbeta(double x, double a, double b) {
|
||||
if (x <= 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
if (x >= 1.0) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// Continued fraction approximation using Lentz's method.
|
||||
const int max_iter = 200;
|
||||
const double epsilon = 3.0e-7;
|
||||
const double tiny = 1e-30;
|
||||
|
||||
const double qab = a + b;
|
||||
const double qap = a + 1.0;
|
||||
const double qam = a - 1.0;
|
||||
|
||||
double c = 1.0;
|
||||
double d = 1.0 - qab * x / qap;
|
||||
if (std::abs(d) < tiny) {
|
||||
d = tiny;
|
||||
}
|
||||
d = 1.0 / d;
|
||||
double h = d;
|
||||
|
||||
for (int m = 1; m <= max_iter; m++) {
|
||||
const int m2 = 2 * m;
|
||||
|
||||
double aa = m * (b - m) * x / ((qam + m2) * (a + m2));
|
||||
d = 1.0 + aa * d;
|
||||
if (std::abs(d) < tiny) {
|
||||
d = tiny;
|
||||
}
|
||||
c = 1.0 + aa / c;
|
||||
if (std::abs(c) < tiny) {
|
||||
c = tiny;
|
||||
}
|
||||
d = 1.0 / d;
|
||||
h *= d * c;
|
||||
|
||||
aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
|
||||
d = 1.0 + aa * d;
|
||||
if (std::abs(d) < tiny) {
|
||||
d = tiny;
|
||||
}
|
||||
c = 1.0 + aa / c;
|
||||
if (std::abs(c) < tiny) {
|
||||
c = tiny;
|
||||
}
|
||||
d = 1.0 / d;
|
||||
const double del = d * c;
|
||||
h *= del;
|
||||
|
||||
if (std::abs(del - 1.0) < epsilon) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return std::exp(a * std::log(x) + b * std::log(1.0 - x) - log_beta(a, b)) / a * h;
|
||||
}
|
||||
|
||||
static double beta_cdf(double x, double a, double b) {
|
||||
if (x == 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
if (x == 1.0) {
|
||||
return 1.0;
|
||||
}
|
||||
if (x < (a + 1.0) / (a + b + 2.0)) {
|
||||
return incbeta(x, a, b);
|
||||
}
|
||||
return 1.0 - incbeta(1.0 - x, b, a);
|
||||
}
|
||||
|
||||
static double beta_ppf(double u, double a, double b, int max_iter = 30) {
|
||||
double x = 0.5;
|
||||
for (int i = 0; i < max_iter; i++) {
|
||||
const double f = beta_cdf(x, a, b) - u;
|
||||
if (std::abs(f) < 1e-10) {
|
||||
break;
|
||||
}
|
||||
const double df = std::exp((a - 1.0) * std::log(x) + (b - 1.0) * std::log(1.0 - x) - log_beta(a, b));
|
||||
x -= f / df;
|
||||
if (x <= 0.0) {
|
||||
x = 1e-10;
|
||||
}
|
||||
if (x >= 1.0) {
|
||||
x = 1.0 - 1e-10;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
std::vector<float> get_sigmas(uint32_t n, float /*sigma_min*/, float /*sigma_max*/, t_to_sigma_t t_to_sigma) override {
|
||||
std::vector<float> result;
|
||||
result.reserve(n + 1);
|
||||
|
||||
const int t_max = TIMESTEPS - 1;
|
||||
if (n == 0) {
|
||||
return result;
|
||||
} else if (n == 1) {
|
||||
result.push_back(t_to_sigma(static_cast<float>(t_max)));
|
||||
result.push_back(0.f);
|
||||
return result;
|
||||
}
|
||||
|
||||
int last_t = -1;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
const double u = 1.0 - static_cast<double>(i) / static_cast<double>(n);
|
||||
const double t_cont = beta_ppf(u, alpha, beta) * t_max;
|
||||
const int t = static_cast<int>(std::lround(t_cont));
|
||||
|
||||
if (t != last_t) {
|
||||
result.push_back(t_to_sigma(static_cast<float>(t)));
|
||||
last_t = t;
|
||||
}
|
||||
}
|
||||
|
||||
result.push_back(0.f);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct SimpleScheduler : SigmaScheduler {
|
||||
std::vector<float> get_sigmas(uint32_t n, float sigma_min, float sigma_max, t_to_sigma_t t_to_sigma) override {
|
||||
std::vector<float> result_sigmas;
|
||||
@@ -559,6 +690,318 @@ struct LTX2Scheduler : SigmaScheduler {
|
||||
}
|
||||
};
|
||||
|
||||
inline float flux_time_shift(float mu, float sigma, float t) {
|
||||
return ::expf(mu) / (::expf(mu) + ::powf((1.0f / t - 1.0f), sigma));
|
||||
}
|
||||
|
||||
// https://github.com/black-forest-labs/flux/blob/main/src/flux/sampling.py#L289
|
||||
struct FluxScheduler : SigmaScheduler {
|
||||
int image_seq_len = 0;
|
||||
float base_shift = 0.5f;
|
||||
float max_shift = 1.15f;
|
||||
|
||||
explicit FluxScheduler(int image_seq_len, const char* extra_sample_args = nullptr)
|
||||
: image_seq_len(image_seq_len) {
|
||||
parse_extra_sample_args(extra_sample_args);
|
||||
}
|
||||
|
||||
void parse_extra_sample_args(const char* extra_sample_args) {
|
||||
for (const auto& [key, value] : parse_key_value_args(extra_sample_args, "flux scheduler arg")) {
|
||||
if (key == "base_shift") {
|
||||
if (!parse_strict_float(value, base_shift)) {
|
||||
LOG_WARN("ignoring invalid flux scheduler arg '%s=%s'", key.c_str(), value.c_str());
|
||||
}
|
||||
} else if (key == "max_shift") {
|
||||
if (!parse_strict_float(value, max_shift)) {
|
||||
LOG_WARN("ignoring invalid flux scheduler arg '%s=%s'", key.c_str(), value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float compute_mu() const {
|
||||
constexpr float base_shift_anchor = 256.0f;
|
||||
constexpr float max_shift_anchor = 4096.0f;
|
||||
float m = (max_shift - base_shift) / (max_shift_anchor - base_shift_anchor);
|
||||
float b = base_shift - m * base_shift_anchor;
|
||||
return static_cast<float>(image_seq_len) * m + b;
|
||||
}
|
||||
|
||||
std::vector<float> get_sigmas(uint32_t n, float /*sigma_min*/, float /*sigma_max*/, t_to_sigma_t /*t_to_sigma*/) override {
|
||||
std::vector<float> sigmas;
|
||||
sigmas.reserve(n + 1);
|
||||
|
||||
float mu = compute_mu();
|
||||
LOG_DEBUG("Flux scheduler: image_seq_len=%d, steps=%u, mu=%.3f", image_seq_len, n, mu);
|
||||
|
||||
if (n == 0) {
|
||||
sigmas.push_back(1.0f);
|
||||
return sigmas;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i <= n; ++i) {
|
||||
float t = 1.0f - static_cast<float>(i) / static_cast<float>(n);
|
||||
if (t <= 0.0f) {
|
||||
sigmas.push_back(0.0f);
|
||||
} else {
|
||||
sigmas.push_back(flux_time_shift(mu, 1.0f, t));
|
||||
}
|
||||
}
|
||||
|
||||
sigmas[n] = 0.0f;
|
||||
return sigmas;
|
||||
}
|
||||
};
|
||||
|
||||
// https://github.com/black-forest-labs/flux2/blob/main/src/flux2/sampling.py#L244
|
||||
struct Flux2Scheduler : SigmaScheduler {
|
||||
int image_seq_len = 0;
|
||||
|
||||
explicit Flux2Scheduler(int image_seq_len)
|
||||
: image_seq_len(image_seq_len) {}
|
||||
|
||||
static float compute_empirical_mu(int image_seq_len, uint32_t num_steps) {
|
||||
const float a1 = 8.73809524e-05f;
|
||||
const float b1 = 1.89833333f;
|
||||
const float a2 = 0.00016927f;
|
||||
const float b2 = 0.45666666f;
|
||||
|
||||
if (image_seq_len > 4300) {
|
||||
return a2 * image_seq_len + b2;
|
||||
}
|
||||
|
||||
float m_200 = a2 * image_seq_len + b2;
|
||||
float m_10 = a1 * image_seq_len + b1;
|
||||
|
||||
float a = (m_200 - m_10) / 190.0f;
|
||||
float b = m_200 - 200.0f * a;
|
||||
return a * num_steps + b;
|
||||
}
|
||||
|
||||
std::vector<float> get_sigmas(uint32_t n, float /*sigma_min*/, float /*sigma_max*/, t_to_sigma_t /*t_to_sigma*/) override {
|
||||
std::vector<float> sigmas;
|
||||
sigmas.reserve(n + 1);
|
||||
|
||||
float mu = compute_empirical_mu(image_seq_len, n);
|
||||
LOG_DEBUG("Flux2 scheduler: image_seq_len=%d, steps=%u, mu=%.3f", image_seq_len, n, mu);
|
||||
|
||||
if (n == 0) {
|
||||
sigmas.push_back(1.0f);
|
||||
return sigmas;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i <= n; ++i) {
|
||||
float t = 1.0f - static_cast<float>(i) / static_cast<float>(n);
|
||||
if (t <= 0.0f) {
|
||||
sigmas.push_back(0.0f);
|
||||
} else if (t >= 1.0f) {
|
||||
sigmas.push_back(1.0f);
|
||||
} else {
|
||||
sigmas.push_back(flux_time_shift(mu, 1.0f, t));
|
||||
}
|
||||
}
|
||||
|
||||
sigmas[n] = 0.0f;
|
||||
return sigmas;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Logit-Normal Scheduler
|
||||
* Based on: https://github.com/ideogram-oss/ideogram4/blob/main/src/ideogram4/scheduler.py
|
||||
*/
|
||||
struct LogitNormalScheduler : SigmaScheduler {
|
||||
float mean = 0.0f;
|
||||
float std = 1.75f;
|
||||
float logsnr_min = -15.0f;
|
||||
float logsnr_max = 18.0f;
|
||||
|
||||
bool resolution_aware = true;
|
||||
|
||||
float one_minus_t_min, one_minus_t_max;
|
||||
|
||||
void parse_extra_sample_args(int image_seq_len = 0, const char* extra_sample_args = nullptr) {
|
||||
const int known_seq_len = (512 * 512) / (16 * 16);
|
||||
if (extra_sample_args) {
|
||||
for (const auto& [key, value] : parse_key_value_args(extra_sample_args, "logit-normal scheduler arg")) {
|
||||
if (key == "mu") {
|
||||
if (!parse_strict_float(value, mean)) {
|
||||
LOG_WARN("ignoring invalid logit-normal scheduler arg '%s=%s'", key.c_str(), value.c_str());
|
||||
}
|
||||
} else if (key == "std") {
|
||||
if (!parse_strict_float(value, std)) {
|
||||
LOG_WARN("ignoring invalid logit-normal scheduler arg '%s=%s'", key.c_str(), value.c_str());
|
||||
}
|
||||
}
|
||||
if (key == "logsnr_min") {
|
||||
if (!parse_strict_float(value, logsnr_min)) {
|
||||
LOG_WARN("ignoring invalid logit-normal scheduler arg '%s=%s'", key.c_str(), value.c_str());
|
||||
}
|
||||
} else if (key == "logsnr_max") {
|
||||
if (!parse_strict_float(value, logsnr_max)) {
|
||||
LOG_WARN("ignoring invalid logit-normal scheduler arg '%s=%s'", key.c_str(), value.c_str());
|
||||
}
|
||||
} else if (key == "resolution_aware") {
|
||||
if (!parse_strict_bool(value, resolution_aware)) {
|
||||
LOG_WARN("ignoring invalid logit-normal scheduler arg '%s=%s'", key.c_str(), value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (image_seq_len > 0 && resolution_aware) {
|
||||
mean += 0.5f * std::log(static_cast<float>(image_seq_len) / static_cast<float>(known_seq_len));
|
||||
}
|
||||
}
|
||||
|
||||
float sigmoid(float x) {
|
||||
return 1.0f / (1.0f + std::exp(-x));
|
||||
}
|
||||
|
||||
LogitNormalScheduler(float mean = 0.0f, float std = 1.75f, float logsnr_min = -18.0f, float logsnr_max = 15.0f)
|
||||
: mean(mean), std(std), logsnr_min(logsnr_min), logsnr_max(logsnr_max) {
|
||||
// t_min = 1.0f / (1.0f + std::exp(0.5f * logsnr_max));
|
||||
one_minus_t_min = sigmoid(0.5f * logsnr_max);
|
||||
// t_max = 1.0f / (1.0f + std::exp(0.5f * logsnr_min));
|
||||
one_minus_t_max = sigmoid(0.5f * logsnr_min);
|
||||
}
|
||||
|
||||
LogitNormalScheduler(int image_seq_len = 0, const char* extra_sample_args = nullptr) {
|
||||
mean = 0.0f;
|
||||
std = 1.75f;
|
||||
logsnr_min = -15.0f;
|
||||
logsnr_max = 18.0f;
|
||||
|
||||
parse_extra_sample_args(image_seq_len, extra_sample_args);
|
||||
// t_min = 1.0f / (1.0f + std::exp(0.5f * logsnr_max));
|
||||
one_minus_t_min = sigmoid(0.5f * logsnr_max);
|
||||
// t_max = 1.0f / (1.0f + std::exp(0.5f * logsnr_min));
|
||||
one_minus_t_max = sigmoid(0.5f * logsnr_min);
|
||||
}
|
||||
|
||||
// https://stackedboxes.org/2017/05/01/acklams-normal-quantile-function/
|
||||
double ndtri(double p) {
|
||||
if (p <= 0.0) {
|
||||
return -std::numeric_limits<double>::infinity();
|
||||
} else if (p >= 1.0) {
|
||||
return std::numeric_limits<double>::infinity();
|
||||
}
|
||||
|
||||
static const double p_low = 0.02425;
|
||||
static const double p_high = 1.0 - p_low;
|
||||
|
||||
static const double c[6] = {-7.784894002430293e-03,
|
||||
-3.223964580411365e-01,
|
||||
-2.400758277161838e+00,
|
||||
-2.549732539343734e+00,
|
||||
4.374664141464968e+00,
|
||||
2.938163982698783e+00};
|
||||
|
||||
static const double d[5] = {7.784695709041462e-03,
|
||||
3.224671290700398e-01,
|
||||
2.445134137142996e+00,
|
||||
3.754408661907416e+00,
|
||||
1.0};
|
||||
|
||||
// Coefficients for the central region
|
||||
static const double a[6] = {-3.969683028665376e+01,
|
||||
2.209460984245205e+02,
|
||||
-2.759285104469687e+02,
|
||||
1.383577518672690e+02,
|
||||
-3.066479806614716e+01,
|
||||
2.506628277459239e+00};
|
||||
|
||||
static const double b[6] = {-5.447609879822406e+01,
|
||||
1.615858368580409e+02,
|
||||
-1.556989798598866e+02,
|
||||
6.680131188771972e+01,
|
||||
-1.328068155288572e+01,
|
||||
1.0};
|
||||
|
||||
double x = 0.0;
|
||||
|
||||
if (p < p_low) {
|
||||
// Lower region
|
||||
double q = std::sqrt(-2.0 * std::log(p));
|
||||
|
||||
// Numerator: c[0]*q^5 + c[1]*q^4 + ... + c[5]
|
||||
double numerator = c[0];
|
||||
for (int i = 1; i < 6; ++i) {
|
||||
numerator = numerator * q + c[i];
|
||||
}
|
||||
|
||||
// Denominator: d[0]*q^4 + d[1]*q^3 + ... + d[3]*q + 1
|
||||
double denominator = d[0];
|
||||
for (int i = 1; i < 5; ++i) {
|
||||
denominator = denominator * q + d[i];
|
||||
}
|
||||
|
||||
x = numerator / denominator;
|
||||
} else if (p > p_high) {
|
||||
// Upper region
|
||||
double q = std::sqrt(-2.0 * std::log(1.0 - p));
|
||||
|
||||
double numerator = c[0];
|
||||
for (int i = 1; i < 6; ++i) {
|
||||
numerator = numerator * q + c[i];
|
||||
}
|
||||
|
||||
double denominator = d[0];
|
||||
for (int i = 1; i < 5; ++i) {
|
||||
denominator = denominator * q + d[i];
|
||||
}
|
||||
|
||||
x = -(numerator / denominator);
|
||||
} else {
|
||||
// Central region
|
||||
double q = p - 0.5;
|
||||
double r = q * q;
|
||||
|
||||
// Numerator: (a[0]*r^5 + a[1]*r^4 + ... + a[5])*q
|
||||
double numerator = a[0];
|
||||
for (int i = 1; i < 6; ++i) {
|
||||
numerator = numerator * r + a[i];
|
||||
}
|
||||
numerator *= q;
|
||||
|
||||
// Denominator: b[0]*r^4 + b[1]*r^3 + ... + b[4]*r + 1
|
||||
double denominator = b[0];
|
||||
for (int i = 1; i < 6; ++i) {
|
||||
denominator = denominator * r + b[i];
|
||||
}
|
||||
|
||||
x = numerator / denominator;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
std::vector<float> get_sigmas(uint32_t n, float /*sigma_min*/, float /*sigma_max*/, t_to_sigma_t /*t_to_sigma*/) override {
|
||||
std::vector<float> sigmas;
|
||||
LOG_INFO("LOGIT_NORMAL_SCHEDULER using mean=%.4f, std=%.4f, logsnr_min=%.4f, logsnr_max=%.4f", mean, std, logsnr_min, logsnr_max);
|
||||
sigmas.reserve(n + 1);
|
||||
for (uint32_t i = 0; i <= n; ++i) {
|
||||
float t = static_cast<float>(i) / static_cast<float>(n);
|
||||
|
||||
// ndtri(1-t) == -ndtri(t)
|
||||
float z = static_cast<float>(-ndtri(t));
|
||||
|
||||
float y = mean + std * z;
|
||||
|
||||
float timestep = sigmoid(y);
|
||||
|
||||
if (timestep > one_minus_t_min)
|
||||
timestep = one_minus_t_min;
|
||||
if (timestep < one_minus_t_max)
|
||||
timestep = one_minus_t_max;
|
||||
|
||||
float sigma = timestep;
|
||||
|
||||
sigmas.push_back(sigma);
|
||||
}
|
||||
sigmas[n] = 0.0f;
|
||||
return sigmas;
|
||||
}
|
||||
};
|
||||
|
||||
struct Denoiser {
|
||||
virtual float sigma_min() = 0;
|
||||
virtual float sigma_max() = 0;
|
||||
@@ -583,6 +1026,10 @@ struct Denoiser {
|
||||
LOG_INFO("get_sigmas with Karras scheduler");
|
||||
scheduler = std::make_shared<KarrasScheduler>();
|
||||
break;
|
||||
case BETA_SCHEDULER:
|
||||
LOG_INFO("get_sigmas with Beta scheduler");
|
||||
scheduler = std::make_shared<BetaScheduler>();
|
||||
break;
|
||||
case EXPONENTIAL_SCHEDULER:
|
||||
LOG_INFO("get_sigmas exponential scheduler");
|
||||
scheduler = std::make_shared<ExponentialScheduler>();
|
||||
@@ -623,6 +1070,21 @@ struct Denoiser {
|
||||
LOG_INFO("get_sigmas with LTX2 scheduler");
|
||||
scheduler = std::make_shared<LTX2Scheduler>(image_seq_len, extra_sample_args);
|
||||
break;
|
||||
case LOGIT_NORMAL_SCHEDULER: {
|
||||
LOG_INFO("get_sigmas with Logit-Normal scheduler");
|
||||
scheduler = std::make_shared<LogitNormalScheduler>(image_seq_len, extra_sample_args);
|
||||
break;
|
||||
}
|
||||
case FLUX2_SCHEDULER: {
|
||||
LOG_INFO("get_sigmas with Flux2 scheduler");
|
||||
scheduler = std::make_shared<Flux2Scheduler>(image_seq_len);
|
||||
break;
|
||||
}
|
||||
case FLUX_SCHEDULER: {
|
||||
LOG_INFO("get_sigmas with Flux scheduler");
|
||||
scheduler = std::make_shared<FluxScheduler>(image_seq_len, extra_sample_args);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_INFO("get_sigmas with discrete scheduler (default)");
|
||||
scheduler = std::make_shared<DiscreteScheduler>();
|
||||
@@ -787,10 +1249,6 @@ struct DiscreteFlowDenoiser : public Denoiser {
|
||||
}
|
||||
};
|
||||
|
||||
inline float flux_time_shift(float mu, float sigma, float t) {
|
||||
return ::expf(mu) / (::expf(mu) + ::powf((1.0f / t - 1.0f), sigma));
|
||||
}
|
||||
|
||||
struct FluxFlowDenoiser : public DiscreteFlowDenoiser {
|
||||
FluxFlowDenoiser() = default;
|
||||
|
||||
@@ -804,35 +1262,79 @@ struct FluxFlowDenoiser : public DiscreteFlowDenoiser {
|
||||
}
|
||||
};
|
||||
|
||||
struct Flux2FlowDenoiser : public FluxFlowDenoiser {
|
||||
Flux2FlowDenoiser() = default;
|
||||
struct SefiFlowDenoiser;
|
||||
|
||||
float compute_empirical_mu(uint32_t n, int image_seq_len) {
|
||||
const float a1 = 8.73809524e-05f;
|
||||
const float b1 = 1.89833333f;
|
||||
const float a2 = 0.00016927f;
|
||||
const float b2 = 0.45666666f;
|
||||
struct SefiFlowDenoiser : public FluxFlowDenoiser {
|
||||
static constexpr int kNumTrainTimesteps = 1000;
|
||||
static constexpr int kSemChannels = 16;
|
||||
static constexpr int kTotalChannels = 144;
|
||||
|
||||
if (image_seq_len > 4300) {
|
||||
float mu = a2 * image_seq_len + b2;
|
||||
return mu;
|
||||
float delta_t = 0.1f;
|
||||
float timestep_shift_alpha = 1.0f;
|
||||
|
||||
std::vector<float> sem_sigmas;
|
||||
std::vector<float> tex_sigmas;
|
||||
std::vector<float> sem_timesteps;
|
||||
std::vector<float> tex_timesteps;
|
||||
|
||||
SefiFlowDenoiser() = default;
|
||||
|
||||
static float apply_alpha_shift(float u_unit, float alpha) {
|
||||
if (alpha == 1.0f) {
|
||||
return u_unit;
|
||||
}
|
||||
|
||||
float m_200 = a2 * image_seq_len + b2;
|
||||
float m_10 = a1 * image_seq_len + b1;
|
||||
|
||||
float a = (m_200 - m_10) / 190.0f;
|
||||
float b = m_200 - 200.0f * a;
|
||||
float mu = a * n + b;
|
||||
|
||||
return mu;
|
||||
float denom = 1.0f + (alpha - 1.0f) * u_unit;
|
||||
return (alpha * u_unit) / denom;
|
||||
}
|
||||
|
||||
std::vector<float> get_sigmas(uint32_t n, int image_seq_len, scheduler_t scheduler_type, SDVersion version, const char* extra_sample_args = nullptr) override {
|
||||
float mu = compute_empirical_mu(n, image_seq_len);
|
||||
LOG_DEBUG("Flux2FlowDenoiser: set shift to %.3f", mu);
|
||||
set_shift(mu);
|
||||
return Denoiser::get_sigmas(n, image_seq_len, scheduler_type, version, extra_sample_args);
|
||||
std::vector<float> get_sigmas(uint32_t n,
|
||||
int image_seq_len,
|
||||
scheduler_t scheduler_type,
|
||||
SDVersion version,
|
||||
const char* extra_sample_args = nullptr) override {
|
||||
sem_sigmas.clear();
|
||||
tex_sigmas.clear();
|
||||
sem_timesteps.clear();
|
||||
tex_timesteps.clear();
|
||||
|
||||
for (const auto& [key, value] : parse_key_value_args(extra_sample_args, "sefi scheduler arg")) {
|
||||
if (key == "sefi_alpha") {
|
||||
if (!parse_strict_float(value, timestep_shift_alpha)) {
|
||||
LOG_WARN("ignoring invalid sefi scheduler arg '%s=%s'", key.c_str(), value.c_str());
|
||||
}
|
||||
} else if (key == "sefi_delta_t") {
|
||||
if (!parse_strict_float(value, delta_t)) {
|
||||
LOG_WARN("ignoring invalid sefi scheduler arg '%s=%s'", key.c_str(), value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i <= n; ++i) {
|
||||
float u_base = static_cast<float>(i) / static_cast<float>(n);
|
||||
float u_shifted = apply_alpha_shift(u_base, timestep_shift_alpha);
|
||||
float u_sem_raw = u_shifted * (1.0f + delta_t);
|
||||
|
||||
float u_sem = std::min(u_sem_raw, 1.0f);
|
||||
float u_tex = std::max(0.0f, std::min(u_sem_raw - delta_t, 1.0f));
|
||||
|
||||
int idx_sem = std::min(kNumTrainTimesteps - 1,
|
||||
std::max(0, static_cast<int>(u_sem * (kNumTrainTimesteps - 1))));
|
||||
int idx_tex = std::min(kNumTrainTimesteps - 1,
|
||||
std::max(0, static_cast<int>(u_tex * (kNumTrainTimesteps - 1))));
|
||||
|
||||
float t_sem = static_cast<float>(kNumTrainTimesteps - idx_sem);
|
||||
float t_tex = static_cast<float>(kNumTrainTimesteps - idx_tex);
|
||||
float sigma_sem = t_sem / static_cast<float>(kNumTrainTimesteps);
|
||||
float sigma_tex = t_tex / static_cast<float>(kNumTrainTimesteps);
|
||||
|
||||
sem_timesteps.push_back(t_sem);
|
||||
tex_timesteps.push_back(t_tex);
|
||||
sem_sigmas.push_back(sigma_sem);
|
||||
tex_sigmas.push_back(sigma_tex);
|
||||
}
|
||||
LOG_DEBUG("SefiFlowDenoiser: built %u-step dual schedule (alpha=%.2f delta_t=%.2f)",
|
||||
n, timestep_shift_alpha, delta_t);
|
||||
return tex_sigmas;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -939,6 +1441,40 @@ static sd::Tensor<float> sample_euler_ancestral(denoise_cb_t model,
|
||||
return x;
|
||||
}
|
||||
|
||||
static sd::Tensor<float> sample_sefi_euler(SefiFlowDenoiser* sefi,
|
||||
denoise_cb_t model,
|
||||
sd::Tensor<float> x) {
|
||||
const std::vector<float>& sigma_tex_vec = sefi->tex_sigmas;
|
||||
const std::vector<float>& sigma_sem_vec = sefi->sem_sigmas;
|
||||
int steps = static_cast<int>(sigma_tex_vec.size()) - 1;
|
||||
for (int i = 0; i < steps; i++) {
|
||||
float sigma_tex_cur = sigma_tex_vec[i];
|
||||
float sigma_tex_next = sigma_tex_vec[i + 1];
|
||||
float sigma_sem_cur = sigma_sem_vec[i];
|
||||
float sigma_sem_next = sigma_sem_vec[i + 1];
|
||||
if (sigma_tex_cur <= 1e-9f) {
|
||||
continue;
|
||||
}
|
||||
auto denoised_opt = model(x, sigma_tex_cur, i + 1);
|
||||
if (denoised_opt.pred.empty()) {
|
||||
return {};
|
||||
}
|
||||
sd::Tensor<float> denoised = std::move(denoised_opt.pred);
|
||||
sd::Tensor<float> velocity = (x - denoised) / sigma_tex_cur;
|
||||
|
||||
auto x_sem = sd::ops::slice(x, 2, 0, SefiFlowDenoiser::kSemChannels);
|
||||
auto x_tex = sd::ops::slice(x, 2, SefiFlowDenoiser::kSemChannels, SefiFlowDenoiser::kTotalChannels);
|
||||
auto vel_sem = sd::ops::slice(velocity, 2, 0, SefiFlowDenoiser::kSemChannels);
|
||||
auto vel_tex = sd::ops::slice(velocity, 2, SefiFlowDenoiser::kSemChannels, SefiFlowDenoiser::kTotalChannels);
|
||||
auto x_sem_next = x_sem + vel_sem * (sigma_sem_next - sigma_sem_cur);
|
||||
auto x_tex_next = x_tex + vel_tex * (sigma_tex_next - sigma_tex_cur);
|
||||
|
||||
sd::ops::slice_assign(&x, 2, 0, SefiFlowDenoiser::kSemChannels, x_sem_next);
|
||||
sd::ops::slice_assign(&x, 2, SefiFlowDenoiser::kSemChannels, SefiFlowDenoiser::kTotalChannels, x_tex_next);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
static sd::Tensor<float> sample_euler(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
const std::vector<float>& sigmas) {
|
||||
@@ -1854,7 +2390,13 @@ static sd::Tensor<float> sample_k_diffusion(sample_method_t method,
|
||||
std::shared_ptr<RNG> rng,
|
||||
float eta,
|
||||
bool is_flow_denoiser,
|
||||
const char* extra_sample_args) {
|
||||
const char* extra_sample_args,
|
||||
std::shared_ptr<Denoiser> denoiser_for_dispatch = nullptr) {
|
||||
if (denoiser_for_dispatch) {
|
||||
if (auto sefi = std::dynamic_pointer_cast<SefiFlowDenoiser>(denoiser_for_dispatch)) {
|
||||
return sample_sefi_euler(sefi.get(), model, std::move(x));
|
||||
}
|
||||
}
|
||||
SamplerExtraArgs extra_args = parse_key_value_args(extra_sample_args, "extra sample arg");
|
||||
switch (method) {
|
||||
case EULER_A_SAMPLE_METHOD:
|
||||
|
||||
+92
-10
@@ -3,6 +3,7 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
@@ -63,6 +64,82 @@ namespace sd::guidance {
|
||||
return uncond;
|
||||
}
|
||||
|
||||
std::vector<float> parse_guidance_schedule_from_spec(std::string spec) {
|
||||
std::vector<float> schedule;
|
||||
|
||||
while (!spec.empty()) {
|
||||
auto sep = spec.find('+');
|
||||
auto segment = spec.substr(0, sep);
|
||||
|
||||
auto x = segment.find('x');
|
||||
if (x == std::string::npos) {
|
||||
LOG_ERROR("Invalid guidance schedule segment: '%s' (expected <guidance>x<count>)", segment.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
float guidance;
|
||||
int count;
|
||||
|
||||
auto guidance_str = segment.substr(0, x);
|
||||
auto count_str = segment.substr(x + 1);
|
||||
|
||||
try {
|
||||
size_t idx = 0;
|
||||
guidance = std::stof(guidance_str, &idx);
|
||||
if (idx != guidance_str.size()) {
|
||||
LOG_ERROR("Invalid guidance value in guidance schedule: '%s'", guidance_str.c_str());
|
||||
return {};
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
LOG_ERROR("Invalid guidance value in guidance schedule: '%s'", guidance_str.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
size_t idx = 0;
|
||||
count = std::stoi(count_str, &idx);
|
||||
if (idx != count_str.size()) {
|
||||
LOG_ERROR("Invalid count in guidance schedule: '%s'", count_str.c_str());
|
||||
return {};
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
LOG_ERROR("Invalid count in guidance schedule: '%s'", count_str.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
if (count <= 0) {
|
||||
LOG_ERROR("Guidance schedule count must be positive");
|
||||
return {};
|
||||
}
|
||||
|
||||
schedule.insert(schedule.end(), count, guidance);
|
||||
|
||||
if (sep == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
|
||||
spec = spec.substr(sep + 1);
|
||||
}
|
||||
|
||||
return schedule;
|
||||
}
|
||||
|
||||
std::vector<float> parse_guidance_schedule(const char* extra_sample_args) {
|
||||
std::vector<float> guidance_schedule;
|
||||
std::string guidance_schedule_str = "";
|
||||
for (const auto& [key, value] : parse_key_value_args(extra_sample_args, "extra sample arg")) {
|
||||
float parsed = 0.0f;
|
||||
if (key == "guidance_schedule") {
|
||||
guidance_schedule_str = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!guidance_schedule_str.empty()) {
|
||||
guidance_schedule = parse_guidance_schedule_from_spec(guidance_schedule_str);
|
||||
}
|
||||
return guidance_schedule;
|
||||
}
|
||||
|
||||
ClassifierFreeGuidance::ClassifierFreeGuidance(float guidance_scale,
|
||||
float image_guidance_scale)
|
||||
: guidance_scale_(guidance_scale),
|
||||
@@ -70,8 +147,10 @@ namespace sd::guidance {
|
||||
}
|
||||
|
||||
GuiderOutput ClassifierFreeGuidance::forward(const GuidanceInput& input,
|
||||
GuiderOutput previous) const {
|
||||
GuiderOutput previous,
|
||||
std::optional<float> scale_override) const {
|
||||
(void)previous;
|
||||
float guidance_scale = scale_override.value_or(guidance_scale_);
|
||||
|
||||
GuiderOutput output;
|
||||
if (!has_tensor(input.pred_cond)) {
|
||||
@@ -86,14 +165,14 @@ namespace sd::guidance {
|
||||
const sd::Tensor<float>& pred_img_uncond = *input.pred_img_uncond;
|
||||
output.pred = pred_img_uncond +
|
||||
image_guidance_scale_ * (pred_uncond - pred_img_uncond) +
|
||||
guidance_scale_ * (pred_cond - pred_uncond);
|
||||
guidance_scale * (pred_cond - pred_uncond);
|
||||
|
||||
} else {
|
||||
output.pred = pred_uncond + guidance_scale_ * (pred_cond - pred_uncond);
|
||||
output.pred = pred_uncond + guidance_scale * (pred_cond - pred_uncond);
|
||||
}
|
||||
} else if (has_tensor(input.pred_img_uncond)) {
|
||||
const sd::Tensor<float>& pred_img_uncond = *input.pred_img_uncond;
|
||||
output.pred = pred_img_uncond + guidance_scale_ * (pred_cond - pred_img_uncond);
|
||||
output.pred = pred_img_uncond + guidance_scale * (pred_cond - pred_img_uncond);
|
||||
}
|
||||
|
||||
return output;
|
||||
@@ -128,8 +207,10 @@ namespace sd::guidance {
|
||||
}
|
||||
|
||||
GuiderOutput AdaptiveProjectedGuidance::forward(const GuidanceInput& input,
|
||||
GuiderOutput previous) const {
|
||||
GuiderOutput previous,
|
||||
std::optional<float> scale_override) const {
|
||||
(void)previous;
|
||||
float guidance_scale = scale_override.value_or(guidance_scale_);
|
||||
|
||||
GuiderOutput output;
|
||||
if (!has_tensor(input.pred_cond)) {
|
||||
@@ -144,13 +225,13 @@ namespace sd::guidance {
|
||||
const sd::Tensor<float>& pred_img_uncond = *input.pred_img_uncond;
|
||||
output.pred = pred_img_uncond +
|
||||
image_guidance_scale_ * (pred_uncond - pred_img_uncond) +
|
||||
guidance_scale_ * (pred_cond - pred_uncond);
|
||||
guidance_scale * (pred_cond - pred_uncond);
|
||||
} else {
|
||||
output.pred = pred_uncond + guidance_scale_ * (pred_cond - pred_uncond);
|
||||
output.pred = pred_uncond + guidance_scale * (pred_cond - pred_uncond);
|
||||
}
|
||||
} else if (has_tensor(input.pred_img_uncond)) {
|
||||
const sd::Tensor<float>& pred_img_uncond = *input.pred_img_uncond;
|
||||
output.pred = pred_img_uncond + guidance_scale_ * (pred_cond - pred_img_uncond);
|
||||
output.pred = pred_img_uncond + guidance_scale * (pred_cond - pred_img_uncond);
|
||||
}
|
||||
if (!has_tensor(input.pred_uncond) && !has_tensor(input.pred_img_uncond)) {
|
||||
return output;
|
||||
@@ -162,7 +243,7 @@ namespace sd::guidance {
|
||||
sd::Tensor<float> deltas = calculate_guidance_delta(pred_cond,
|
||||
pred_uncond,
|
||||
pred_img_uncond,
|
||||
guidance_scale_,
|
||||
guidance_scale,
|
||||
image_guidance_scale_);
|
||||
if (params_.momentum != 0.0f) {
|
||||
if (momentum_buffer_.shape() != deltas.shape()) {
|
||||
@@ -239,7 +320,8 @@ namespace sd::guidance {
|
||||
}
|
||||
|
||||
GuiderOutput SkipLayerGuidance::forward(const GuidanceInput& input,
|
||||
GuiderOutput output) const {
|
||||
GuiderOutput output,
|
||||
std::optional<float> /*scale_override*/) const {
|
||||
if (scale_ == 0.0f || !is_enabled_for_step(input) || !input.predict_skip_layer) {
|
||||
return output;
|
||||
}
|
||||
|
||||
+11
-5
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "core/tensor.hpp"
|
||||
@@ -27,6 +28,7 @@ namespace sd::guidance {
|
||||
AdaptiveProjectedGuidanceParams parse_adaptive_projected_guidance_args(const char* extra_sample_args);
|
||||
bool is_adaptive_projected_guidance_enabled(const AdaptiveProjectedGuidanceParams& params);
|
||||
bool parse_skip_layer_guidance_uncond_arg(const char* extra_sample_args);
|
||||
std::vector<float> parse_guidance_schedule(const char* extra_sample_args);
|
||||
|
||||
struct GuidanceInput {
|
||||
int step = 0;
|
||||
@@ -40,9 +42,10 @@ namespace sd::guidance {
|
||||
|
||||
class BaseGuidance {
|
||||
public:
|
||||
virtual ~BaseGuidance() = default;
|
||||
virtual ~BaseGuidance() = default;
|
||||
virtual GuiderOutput forward(const GuidanceInput& input,
|
||||
GuiderOutput previous) const = 0;
|
||||
GuiderOutput previous,
|
||||
std::optional<float> scale_override = std::nullopt) const = 0;
|
||||
};
|
||||
|
||||
class ClassifierFreeGuidance : public BaseGuidance {
|
||||
@@ -54,7 +57,8 @@ namespace sd::guidance {
|
||||
float image_guidance_scale);
|
||||
|
||||
GuiderOutput forward(const GuidanceInput& input,
|
||||
GuiderOutput previous) const override;
|
||||
GuiderOutput previous,
|
||||
std::optional<float> scale_override = std::nullopt) const override;
|
||||
};
|
||||
|
||||
class AdaptiveProjectedGuidance : public BaseGuidance {
|
||||
@@ -69,7 +73,8 @@ namespace sd::guidance {
|
||||
AdaptiveProjectedGuidanceParams params);
|
||||
|
||||
GuiderOutput forward(const GuidanceInput& input,
|
||||
GuiderOutput previous) const override;
|
||||
GuiderOutput previous,
|
||||
std::optional<float> scale_override = std::nullopt) const override;
|
||||
};
|
||||
|
||||
class SkipLayerGuidance : public BaseGuidance {
|
||||
@@ -88,7 +93,8 @@ namespace sd::guidance {
|
||||
const std::vector<int>& layers() const;
|
||||
|
||||
GuiderOutput forward(const GuidanceInput& input,
|
||||
GuiderOutput previous) const override;
|
||||
GuiderOutput previous,
|
||||
std::optional<float> scale_override = std::nullopt) const override;
|
||||
};
|
||||
|
||||
} // namespace sd::guidance
|
||||
|
||||
+404
-183
File diff suppressed because it is too large
Load Diff
@@ -134,7 +134,8 @@ std::vector<int> BPETokenizer::encode(const std::string& text, on_new_token_cb_t
|
||||
std::vector<int32_t> bpe_tokens;
|
||||
std::vector<std::string> token_strs;
|
||||
|
||||
auto splited_texts = split_with_special_tokens(text, special_tokens);
|
||||
std::string normalized_text = normalize_before_split ? normalize(text) : text;
|
||||
auto splited_texts = split_with_special_tokens(normalized_text, special_tokens);
|
||||
|
||||
for (auto& splited_text : splited_texts) {
|
||||
if (is_special_token(splited_text)) {
|
||||
@@ -159,7 +160,7 @@ std::vector<int> BPETokenizer::encode(const std::string& text, on_new_token_cb_t
|
||||
}
|
||||
}
|
||||
|
||||
std::string token_str = normalize(token);
|
||||
std::string token_str = normalize_before_split ? token : normalize(token);
|
||||
std::u32string utf32_token;
|
||||
if (byte_level_bpe) {
|
||||
for (int i = 0; i < token_str.length(); i++) {
|
||||
|
||||
@@ -22,9 +22,10 @@ CLIPTokenizer::CLIPTokenizer(int pad_token_id, const std::string& merges_utf8_st
|
||||
EOS_TOKEN_ID = 49407;
|
||||
PAD_TOKEN_ID = pad_token_id;
|
||||
|
||||
end_of_word_suffix = "</w>";
|
||||
add_bos_token = true;
|
||||
add_eos_token = true;
|
||||
end_of_word_suffix = "</w>";
|
||||
add_bos_token = true;
|
||||
add_eos_token = true;
|
||||
normalize_before_split = true;
|
||||
|
||||
if (merges_utf8_str.size() > 0) {
|
||||
load_from_merges(merges_utf8_str);
|
||||
|
||||
@@ -12,9 +12,10 @@ using on_new_token_cb_t = std::function<bool(std::string&, std::vector<int32_t>&
|
||||
class Tokenizer {
|
||||
protected:
|
||||
std::vector<std::string> special_tokens;
|
||||
bool add_bos_token = false;
|
||||
bool add_eos_token = false;
|
||||
bool pad_left = false;
|
||||
bool add_bos_token = false;
|
||||
bool add_eos_token = false;
|
||||
bool pad_left = false;
|
||||
bool normalize_before_split = false;
|
||||
std::string end_of_word_suffix;
|
||||
|
||||
virtual std::string decode_token(int token_id) const = 0;
|
||||
|
||||
+38
-10
@@ -4,6 +4,7 @@
|
||||
#include "model_loader.h"
|
||||
#include "stable-diffusion.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
|
||||
UpscalerGGML::UpscalerGGML(int n_threads,
|
||||
@@ -39,17 +40,12 @@ void UpscalerGGML::set_stream_layers_enabled(bool enabled) {
|
||||
}
|
||||
|
||||
bool UpscalerGGML::load_from_file(const std::string& esrgan_path,
|
||||
bool offload_params_to_cpu,
|
||||
int n_threads) {
|
||||
ggml_log_set(ggml_log_callback_default, nullptr);
|
||||
|
||||
std::string error;
|
||||
if (!backend_manager.init(backend_spec.c_str(),
|
||||
params_backend_spec.c_str(),
|
||||
offload_params_to_cpu,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
&error)) {
|
||||
LOG_ERROR("upscaler backend config failed: %s", error.c_str());
|
||||
return false;
|
||||
@@ -106,7 +102,7 @@ bool UpscalerGGML::load_from_file(const std::string& esrgan_path,
|
||||
esrgan_upscaler->get_param_tensors(tensors);
|
||||
if (!model_manager->register_param_tensors("ESRGAN",
|
||||
std::move(tensors),
|
||||
ModelManager::ResidencyMode::Resident,
|
||||
backend_manager.params_backend_is_disk(SDBackendModule::UPSCALER) ? ModelManager::ResidencyMode::Disk : ModelManager::ResidencyMode::ParamBackend,
|
||||
backend_for(SDBackendModule::UPSCALER),
|
||||
params_backend_for(SDBackendModule::UPSCALER)) ||
|
||||
!model_manager->validate_registered_tensors()) {
|
||||
@@ -178,7 +174,6 @@ struct upscaler_ctx_t {
|
||||
};
|
||||
|
||||
upscaler_ctx_t* new_upscaler_ctx(const char* esrgan_path_c_str,
|
||||
bool offload_params_to_cpu,
|
||||
bool direct,
|
||||
int n_threads,
|
||||
int tile_size,
|
||||
@@ -195,7 +190,7 @@ upscaler_ctx_t* new_upscaler_ctx(const char* esrgan_path_c_str,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!upscaler_ctx->upscaler->load_from_file(esrgan_path, offload_params_to_cpu, n_threads)) {
|
||||
if (!upscaler_ctx->upscaler->load_from_file(esrgan_path, n_threads)) {
|
||||
delete upscaler_ctx->upscaler;
|
||||
upscaler_ctx->upscaler = nullptr;
|
||||
free(upscaler_ctx);
|
||||
@@ -204,8 +199,41 @@ upscaler_ctx_t* new_upscaler_ctx(const char* esrgan_path_c_str,
|
||||
return upscaler_ctx;
|
||||
}
|
||||
|
||||
sd_image_t upscale(upscaler_ctx_t* upscaler_ctx, sd_image_t input_image, uint32_t upscale_factor) {
|
||||
return upscaler_ctx->upscaler->upscale(input_image, upscale_factor);
|
||||
bool upscale(upscaler_ctx_t* upscaler_ctx,
|
||||
sd_image_t input_image,
|
||||
uint32_t upscale_factor,
|
||||
sd_image_t** images_out,
|
||||
int* num_images_out) {
|
||||
if (images_out != nullptr) {
|
||||
*images_out = nullptr;
|
||||
}
|
||||
if (num_images_out != nullptr) {
|
||||
*num_images_out = 0;
|
||||
}
|
||||
if (upscaler_ctx == nullptr || upscaler_ctx->upscaler == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sd_image_t* result_images = (sd_image_t*)calloc(1, sizeof(sd_image_t));
|
||||
if (result_images == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result_images[0] = upscaler_ctx->upscaler->upscale(input_image, upscale_factor);
|
||||
if (result_images[0].data == nullptr) {
|
||||
free(result_images);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (num_images_out != nullptr) {
|
||||
*num_images_out = 1;
|
||||
}
|
||||
if (images_out != nullptr) {
|
||||
*images_out = result_images;
|
||||
} else {
|
||||
free_sd_images(result_images, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int get_upscale_factor(upscaler_ctx_t* upscaler_ctx) {
|
||||
|
||||
@@ -32,7 +32,6 @@ struct UpscalerGGML {
|
||||
~UpscalerGGML();
|
||||
|
||||
bool load_from_file(const std::string& esrgan_path,
|
||||
bool offload_params_to_cpu,
|
||||
int n_threads);
|
||||
void set_max_graph_vram_bytes(size_t max_vram_bytes);
|
||||
void set_stream_layers_enabled(bool enabled);
|
||||
|
||||
Reference in New Issue
Block a user