mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-09-21 21:47:49 -05:00
Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8248a869c | ||
|
|
008ca5b492 | ||
|
|
137f7409bb | ||
|
|
1330cebae8 | ||
|
|
17860c0e45 | ||
|
|
275ab58e01 | ||
|
|
d32b4e893b | ||
|
|
9982c9caae | ||
|
|
3e037a81e4 | ||
|
|
2ea8aff7ef | ||
|
|
adcac69650 | ||
|
|
656a1354c3 | ||
|
|
269e726015 | ||
|
|
cc515a01f9 | ||
|
|
3161505fe8 | ||
|
|
59c23bce0d | ||
|
|
07a85c74cb | ||
|
|
f9ddc0f388 |
@@ -118,7 +118,8 @@ documentation.
|
||||
6. Run the narrowest useful build, test, or inspection command available.
|
||||
|
||||
Follow `CONTRIBUTING.md` for formatting, naming, PR expectations, dependency
|
||||
update policy, and security rules.
|
||||
update policy, and security rules. For tokenizer additions, follow its embedded-data
|
||||
allowlist and default to an external `tokenizer.json`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+11
-12
@@ -95,6 +95,8 @@ option(SD_MUSA "sd: musa backend" OFF)
|
||||
option(SD_BUILD_SHARED_LIBS "sd: build shared libs" OFF)
|
||||
option(SD_BUILD_SHARED_GGML_LIB "sd: build ggml as a separate shared lib" OFF)
|
||||
option(SD_USE_SYSTEM_GGML "sd: use system-installed GGML library" OFF)
|
||||
option(SD_USE_UPSTREAM_GGML "sd: build with upstream GGML instead of the patched GGML extensions" OFF)
|
||||
set(SD_GGML_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ggml" CACHE PATH "sd: ggml source directory (also supplies private headers for system ggml)")
|
||||
#option(SD_BUILD_SERVER "sd: build server example" ON)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
@@ -325,24 +327,21 @@ if (NOT SD_USE_SYSTEM_GGML)
|
||||
endif()
|
||||
|
||||
# deps
|
||||
# Only add ggml if it hasn't been added yet
|
||||
if (NOT TARGET ggml)
|
||||
if (SD_USE_SYSTEM_GGML)
|
||||
find_package(ggml REQUIRED)
|
||||
if (NOT ggml_FOUND)
|
||||
message(FATAL_ERROR "System-installed GGML library not found.")
|
||||
endif()
|
||||
add_library(ggml ALIAS ggml::ggml)
|
||||
else()
|
||||
add_subdirectory(ggml)
|
||||
endif()
|
||||
endif()
|
||||
include(cmake/ggml.cmake)
|
||||
|
||||
add_subdirectory(thirdparty)
|
||||
|
||||
target_sources(${SD_LIB} PRIVATE $<TARGET_OBJECTS:zip>)
|
||||
target_link_libraries(${SD_LIB} PUBLIC ggml)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(${SD_LIB} PRIVATE Threads::Threads)
|
||||
target_link_libraries(${SD_LIB} PRIVATE onig sd-utf8proc)
|
||||
if (SD_CUDA)
|
||||
find_package(CUDAToolkit REQUIRED)
|
||||
# Keep the driver stub on downstream link lines when no driver is installed.
|
||||
target_link_libraries(${SD_LIB} PUBLIC CUDA::cuda_driver)
|
||||
set_property(SOURCE src/core/ggml_extend_backend.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_USE_CUDA)
|
||||
endif()
|
||||
target_include_directories(${SD_LIB} PUBLIC . src include)
|
||||
target_include_directories(${SD_LIB} PRIVATE src/core)
|
||||
target_include_directories(${SD_LIB} PUBLIC . thirdparty)
|
||||
|
||||
@@ -46,6 +46,21 @@ Some older code in the project may not fully follow the current conventions. Ple
|
||||
|
||||
When adding or modifying model implementations, follow the model config and weight detection conventions in [docs/model_config.md](docs/model_config.md).
|
||||
|
||||
## Tokenizer Data
|
||||
|
||||
New model integrations must use an external `tokenizer.json` by default. Do not
|
||||
embed new vocabularies or merge tables solely for less widely used models;
|
||||
these tables increase the binary size for every user.
|
||||
|
||||
The embedded-data allowlist is CLIP, T5/UMT5, Qwen 2/3, Mistral, and Gemma 3/4.
|
||||
Models may reuse an existing embedded tokenizer when its vocabulary and behavior
|
||||
match their text encoder. Gemma 2 and GPT-OSS require external JSON files.
|
||||
|
||||
Adding to this allowlist requires maintainer approval, supported by the model's
|
||||
usage, reuse across models, and measured binary-size cost. Document the matching
|
||||
JSON and CLI option for models that require an external tokenizer, and fail
|
||||
initialization clearly when it is missing.
|
||||
|
||||
## AI-Assisted Contributions
|
||||
|
||||
AI tools may be used to assist development, but contributors are responsible for the quality and correctness of the submitted code.
|
||||
|
||||
@@ -15,6 +15,7 @@ API and command-line option may change frequently.***
|
||||
|
||||
## 🔥Important News
|
||||
|
||||
* **2026/09/20** 🚀 stable-diffusion.cpp adds **Day-0 support for Qwen-Image-2.1**
|
||||
* **2026/08/20** 🚀 stable-diffusion.cpp now supports **LTX-2.5**
|
||||
* **2026/08/04** 🚀 stable-diffusion.cpp adds **Day-1 support for MiniMax-H3**
|
||||
* **2026/06/25** 🚀 stable-diffusion.cpp now supports **Krea2**
|
||||
@@ -47,6 +48,7 @@ API and command-line option may change frequently.***
|
||||
- [Chroma](./docs/chroma.md)
|
||||
- [Chroma1-Radiance](./docs/chroma_radiance.md)
|
||||
- [Qwen Image](./docs/qwen_image.md)
|
||||
- [Qwen Image 2.1](./docs/qwen_image_2.1.md)
|
||||
- [PiD](./docs/pid.md)
|
||||
- [LongCat Image](./docs/longcat_image.md)
|
||||
- [Z-Image](./docs/z_image.md)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 634 KiB |
@@ -0,0 +1,27 @@
|
||||
if(NOT TARGET ggml AND NOT TARGET ggml::ggml)
|
||||
if(SD_USE_SYSTEM_GGML)
|
||||
find_package(ggml REQUIRED)
|
||||
else()
|
||||
add_subdirectory("${SD_GGML_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/ggml")
|
||||
endif()
|
||||
endif()
|
||||
if(NOT TARGET ggml)
|
||||
add_library(ggml ALIAS ggml::ggml)
|
||||
endif()
|
||||
|
||||
get_target_property(sd_ggml_imported ggml IMPORTED)
|
||||
if(sd_ggml_imported)
|
||||
set(sd_ggml_private_include "${SD_GGML_SOURCE_DIR}/src")
|
||||
else()
|
||||
get_target_property(sd_ggml_private_include ggml SOURCE_DIR)
|
||||
endif()
|
||||
if(NOT EXISTS "${sd_ggml_private_include}/ggml-impl.h")
|
||||
message(FATAL_ERROR "Set SD_GGML_SOURCE_DIR to the source tree matching the selected ggml library (ggml-impl.h is required).")
|
||||
endif()
|
||||
target_include_directories(${SD_LIB} PRIVATE "${sd_ggml_private_include}")
|
||||
set_property(TARGET ${SD_LIB} PROPERTY SD_GGML_PRIVATE_INCLUDE_DIR "${sd_ggml_private_include}")
|
||||
|
||||
if(SD_USE_UPSTREAM_GGML)
|
||||
target_compile_definitions(${SD_LIB} PUBLIC SD_USE_UPSTREAM_GGML)
|
||||
message(WARNING "Using upstream GGML: INT8 tensorwise/convrot is disabled and FP8 weights are converted to F16 at load time. Some operators may be unsupported and performance may be lower than with patched GGML.")
|
||||
endif()
|
||||
@@ -10,6 +10,10 @@ set(SD_BIN_DIR "@PACKAGE_SD_BIN_INSTALL_DIR@")
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
find_dependency(ggml REQUIRED HINTS "${SD_LIB_DIR}/cmake")
|
||||
find_dependency(Threads REQUIRED)
|
||||
if(@SD_CUDA@)
|
||||
find_dependency(CUDAToolkit REQUIRED)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET stable-diffusion)
|
||||
find_library(stable-diffusion_LIBRARY stable-diffusion
|
||||
@@ -22,12 +26,16 @@ if(NOT TARGET stable-diffusion)
|
||||
set_target_properties(stable-diffusion
|
||||
PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${SD_INCLUDE_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "ggml::ggml"
|
||||
INTERFACE_LINK_LIBRARIES "ggml::ggml;Threads::Threads"
|
||||
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_CUDA@)
|
||||
set_property(TARGET stable-diffusion APPEND PROPERTY INTERFACE_LINK_LIBRARIES CUDA::cuda_driver)
|
||||
endif()
|
||||
|
||||
if(SD_SHARED_LIB)
|
||||
target_compile_definitions(stable-diffusion
|
||||
INTERFACE SD_BUILD_SHARED_LIB)
|
||||
|
||||
@@ -7,5 +7,5 @@ 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
|
||||
Libs.private: -lggml -lggml-base @CMAKE_THREAD_LIBS_INIT@
|
||||
Cflags: -I${includedir}
|
||||
|
||||
@@ -16,6 +16,40 @@ git submodule init
|
||||
git submodule update
|
||||
```
|
||||
|
||||
## Selecting a GGML source tree
|
||||
|
||||
By default, sd.cpp builds the patched GGML submodule in `ggml/`. To build with
|
||||
an upstream GGML checkout instead, enable `SD_USE_UPSTREAM_GGML` and set
|
||||
`SD_GGML_SOURCE_DIR`:
|
||||
|
||||
```shell
|
||||
cmake -S . -B build-upstream -DSD_USE_UPSTREAM_GGML=ON -DSD_GGML_SOURCE_DIR=../ggml-upstream
|
||||
cmake --build build-upstream --config Release
|
||||
```
|
||||
|
||||
The selected source tree supplies both the library and its private headers.
|
||||
Backend options such as `-DSD_CUDA=ON` apply to the selected tree as usual.
|
||||
|
||||
`SD_USE_UPSTREAM_GGML` defaults to `OFF`, which enables the patched GGML
|
||||
extensions. Set it to `ON` when using upstream GGML; it selects the compatibility
|
||||
mode and does not download or replace the GGML source tree. Upstream mode keeps
|
||||
the original FP8 safetensors handling: FP8 tensors are converted to F16 at load
|
||||
time (one byte per element in the file, two in RAM and VRAM). INT8
|
||||
tensorwise/convrot is disabled and its model files are rejected with an explicit
|
||||
error. FP8 GGUF files, FP8 weight type requests and tensor type rules are also
|
||||
rejected; no automatic conversion is performed.
|
||||
|
||||
Upstream GGML may lack some operators and performance optimizations provided by
|
||||
the patched version. A warning is emitted during CMake configuration and when
|
||||
creating an inference context. Ordinary floating-point and shared GGML
|
||||
quantization types remain available, subject to backend operator support.
|
||||
|
||||
`SD_USE_SYSTEM_GGML=ON` instead links an installed GGML CMake package, located
|
||||
with `ggml_DIR` or `CMAKE_PREFIX_PATH`. In that mode, `SD_GGML_SOURCE_DIR` must
|
||||
point to the matching source tree for private headers. The installed library
|
||||
must use the same ABI settings as sd.cpp, including `GGML_MAX_NAME`.
|
||||
Set `SD_USE_UPSTREAM_GGML=ON` as well if the installed package is upstream GGML.
|
||||
|
||||
## WebP and WebM Support in Examples
|
||||
|
||||
The example applications (`examples/cli` and `examples/server`) use `libwebp` to support WebP image I/O, and `examples/cli` can also use `libwebm` for `.webm` video output. Both are enabled by default. WebM output currently reuses `libwebp` to encode each frame as VP8 before muxing with `libwebm`.
|
||||
|
||||
@@ -18,6 +18,9 @@ at one byte per element in RAM and VRAM. Backends that cannot multiply FP8
|
||||
weights directly cast only the active layer to a temporary BF16 tensor during
|
||||
execution; the loader does not expand the entire checkpoint to BF16.
|
||||
|
||||
With `SD_USE_UPSTREAM_GGML=ON`, FP8 tensors are converted to F16 at load time
|
||||
instead (two bytes per element in RAM and VRAM).
|
||||
|
||||
Use `ideogram4_fp8.safetensors` and `ideogram4_uncond_fp8.safetensors` directly
|
||||
with `--diffusion-model` and `--uncond-diffusion-model`, respectively.
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
sd.cpp can load and execute ComfyUI `int8_tensorwise` safetensors with `convrot` metadata directly. The stored INT8 weights are not converted to another weight type at load time.
|
||||
|
||||
This requires the INT8 tensorwise/convrot extensions in the patched GGML.
|
||||
Builds with `SD_USE_UPSTREAM_GGML=ON` reject these files during loading.
|
||||
|
||||
## Checkpoint format
|
||||
|
||||
Each quantized linear module contains the following tensors:
|
||||
|
||||
+6
-2
@@ -12,13 +12,17 @@ Lens uses a Lens diffusion transformer, the FLUX.2 VAE, and GPT-OSS-20B as the L
|
||||
- safetensors: https://huggingface.co/black-forest-labs/FLUX.2-dev/tree/main
|
||||
- Download GPT-OSS-20B
|
||||
- gguf: https://huggingface.co/unsloth/gpt-oss-20b-GGUF/tree/main
|
||||
- Download GPT-OSS-20B tokenizer.json
|
||||
- https://huggingface.co/openai/gpt-oss-20b/tree/main
|
||||
|
||||
Lens and Lens Turbo require an external GPT-OSS `tokenizer.json` matching the text encoder checkpoint. Save it as `tokenizer_gpt_oss.json` and pass it with `--tokenizer`; the tokenizer is not embedded in sd.cpp. See [JSON tokenizers](tokenizers.md) for CLI and C API usage.
|
||||
|
||||
## Examples
|
||||
|
||||
### Lens
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\lens_bf16.safetensors --llm "..\models\text_encoders\gpt-oss-20b-UD-Q8_K_XL.gguf" --vae ..\models\vae\flux2_ae.safetensors --cfg-scale 5.0 -p "A crystal dragon soaring through an aurora borealis sky, its entire body made of transparent faceted crystal refracting the green and purple aurora light into rainbow spectra, ice particles trailing from its wings, high fantasy digital art" --diffusion-fa -v
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\lens_bf16.safetensors --llm "..\models\text_encoders\gpt-oss-20b-UD-Q8_K_XL.gguf" --tokenizer ..\models\tokenizers\tokenizer_gpt_oss.json --vae ..\models\vae\flux2_ae.safetensors --cfg-scale 5.0 -p "A crystal dragon soaring through an aurora borealis sky, its entire body made of transparent faceted crystal refracting the green and purple aurora light into rainbow spectra, ice particles trailing from its wings, high fantasy digital art" --diffusion-fa -v
|
||||
```
|
||||
|
||||
<img width="256" alt="Lens example" src="../assets/lens/example.png" />
|
||||
@@ -26,7 +30,7 @@ Lens uses a Lens diffusion transformer, the FLUX.2 VAE, and GPT-OSS-20B as the L
|
||||
### Lens Turbo
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\lens_turbo_bf16.safetensors --llm "..\models\text_encoders\gpt-oss-20b-UD-Q8_K_XL.gguf" --vae ..\models\vae\flux2_ae.safetensors --cfg-scale 1.0 -p "A crystal dragon soaring through an aurora borealis sky, its entire body made of transparent faceted crystal refracting the green and purple aurora light into rainbow spectra, ice particles trailing from its wings, high fantasy digital art" --diffusion-fa -v --steps 4
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\lens_turbo_bf16.safetensors --llm "..\models\text_encoders\gpt-oss-20b-UD-Q8_K_XL.gguf" --tokenizer ..\models\tokenizers\tokenizer_gpt_oss.json --vae ..\models\vae\flux2_ae.safetensors --cfg-scale 1.0 -p "A crystal dragon soaring through an aurora borealis sky, its entire body made of transparent faceted crystal refracting the green and purple aurora light into rainbow spectra, ice particles trailing from its wings, high fantasy digital art" --diffusion-fa -v --steps 4
|
||||
```
|
||||
|
||||
<img width="256" alt="Lens Turbo example" src="../assets/lens/turbo_example.png" />
|
||||
|
||||
+5
-1
@@ -11,6 +11,8 @@ In stable-diffusion.cpp, PiD currently runs as an image edit pipeline: provide a
|
||||
- safetensors: https://huggingface.co/Comfy-Org/PixelDiT/tree/main/diffusion_models
|
||||
- Download Gemma 2 2B
|
||||
- safetensors: https://huggingface.co/Comfy-Org/PixelDiT/tree/main/text_encoders
|
||||
- Download Gemma 2 2B tokenizer.json
|
||||
- https://huggingface.co/google/gemma-2-2b/tree/main
|
||||
- Download the VAE that matches the PiD checkpoint backbone
|
||||
- safetensors: https://huggingface.co/nvidia/PiD/tree/main/checkpoints
|
||||
- Flux / Z-Image PiD: use the Flux VAE and pass `--vae-format flux`
|
||||
@@ -20,10 +22,12 @@ In stable-diffusion.cpp, PiD currently runs as an image edit pipeline: provide a
|
||||
|
||||
The official PiD model card should be checked before use. At the time of the initial PiD release, the official weights are under the NSCLv1 non-commercial license.
|
||||
|
||||
PiD and PiD 1.5 require an external Gemma 2 `tokenizer.json` matching the text encoder checkpoint. Save it as `tokenizer_gemma2.json` and pass it with `--tokenizer`; the tokenizer is not embedded in sd.cpp. See [JSON tokenizers](tokenizers.md) for CLI and C API usage.
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\pid_flux1_512_to_2048_4step_bf16.safetensors --llm "..\models\text_encoders\gemma_2_2b_it_elm_bf16.safetensors" --vae ..\models\vae\ae.sft --vae-format flux --cfg-scale 1.0 -p "a lovely cat" -r ..\assets\ernie_image\turbo_example.png --diffusion-fa -v --steps 4 -H 2048 -W 2048 --rng cpu
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\pid_flux1_512_to_2048_4step_bf16.safetensors --llm "..\models\text_encoders\gemma_2_2b_it_elm_bf16.safetensors" --tokenizer ..\models\tokenizers\tokenizer_gemma2.json --vae ..\models\vae\ae.sft --vae-format flux --cfg-scale 1.0 -p "a lovely cat" -r ..\assets\ernie_image\turbo_example.png --diffusion-fa -v --steps 4 -H 2048 -W 2048 --rng cpu
|
||||
```
|
||||
|
||||
Before:
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# How to Use
|
||||
|
||||
Qwen Image 2.1 supports text-to-image generation and image editing, using Qwen3-VL-8B as the text encoder and its own VAE.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download Qwen Image 2.1
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Qwen-Image-2.1/tree/main/diffusion_models
|
||||
- gguf: https://huggingface.co/leejet/Qwen-Image-2.1-GGUF/tree/main
|
||||
- Download vae
|
||||
- safetensors: https://huggingface.co/Comfy-Org/Qwen-Image-2.1/tree/main/vae
|
||||
- Download Qwen3-VL-8B-Instruct
|
||||
- safetensors (BF16 or INT8 convrot): https://huggingface.co/Comfy-Org/Qwen-Image-2.1/tree/main/text_encoders
|
||||
- gguf: https://huggingface.co/Qwen/Qwen3-VL-8B-Instruct-GGUF/tree/main
|
||||
- For image editing with a GGUF text encoder, also download `mmproj-Qwen3VL-8B-Instruct-F16.gguf` from the same repository and pass it with `--llm_vision`.
|
||||
|
||||
Use `qwen_image_2.1_vae_bf16.safetensors` with this model. The earlier Qwen Image and Wan 2.2 VAE weights are not interchangeable with the Qwen Image 2.1 VAE weights.
|
||||
|
||||
## Examples
|
||||
|
||||
Run the following commands from the build directory. Use image dimensions divisible by 32. The resolution-dependent flow schedule is selected automatically.
|
||||
|
||||
### Text to image
|
||||
|
||||
```powershell
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen_image_2.1_int8_convrot.safetensors --vae ..\models\vae\qwen_image_2.1_vae_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf -p "a lovely cat holding a sign says 'qwen2.1.cpp'" --cfg-scale 6.0 --sampling-method euler -v --offload-to-cpu -o qwen_image_2.1.png
|
||||
```
|
||||
|
||||
<img alt="Qwen Image 2.1 example" src="../assets/qwen/qwen_image_2.1.png" />
|
||||
|
||||
To use GGUF diffusion weights, set `--diffusion-model` to the path of a file such as `qwen_image_2.1-Q4_K.gguf`.
|
||||
|
||||
### Image editing
|
||||
|
||||
Pass the reference image with `-r` and describe the edit in `-p`. Vision weights are required; the example below loads them separately with `--llm_vision`.
|
||||
|
||||
```powershell
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen_image_2.1_int8_convrot.safetensors --vae ..\models\vae\qwen_image_2.1_vae_bf16.safetensors --llm ..\models\text_encoders\Qwen3VL-8B-Instruct-Q4_K_M.gguf --llm_vision ..\models\text_encoders\Qwen3VL-8B-Instruct-mmproj-BF16.gguf -r ..\assets\qwen\qwen_image_2.1.png -p "change 'qwen2.1.cpp' to 'sd.cpp'" --cfg-scale 6.0 --sampling-method euler -v --offload-to-cpu -o qwen_image_2.1_edit.png
|
||||
```
|
||||
|
||||
For multiple reference images, repeat `-r` in the desired order, for example `-r first.png -r second.png`.
|
||||
+13
-3
@@ -1,8 +1,10 @@
|
||||
# JSON tokenizers
|
||||
|
||||
Use a Hugging Face `tokenizer.json` to supply the tokenizer vocabulary, merges,
|
||||
added tokens, and processing stages. Without this option, sd.cpp keeps its
|
||||
embedded tokenizer for the selected model.
|
||||
added tokens, and processing stages. **PiD (including PiD 1.5) and Lens (including
|
||||
Lens Turbo) require an external JSON**; their Gemma 2 and GPT-OSS tokenizers are
|
||||
not embedded. Initialization fails if the main tokenizer is missing. Other
|
||||
models keep their embedded tokenizer when this option is omitted.
|
||||
|
||||
```shell
|
||||
sd-cli --diffusion-model model.gguf --llm text_encoder.gguf \
|
||||
@@ -14,6 +16,13 @@ the embedding table does not establish that two vocabularies have the same
|
||||
meaning. The JSON file is loaded when the text encoder is created; its embedded
|
||||
vocabulary is not loaded in this case.
|
||||
|
||||
| Model | Required text encoder tokenizer | Example |
|
||||
| --- | --- | --- |
|
||||
| PiD / PiD 1.5 | Gemma 2 matching the text encoder checkpoint | `--tokenizer tokenizer_gemma2.json` |
|
||||
| Lens / Lens Turbo | GPT-OSS matching the text encoder checkpoint | `--tokenizer tokenizer_gpt_oss.json` |
|
||||
|
||||
The Gemma 3/4 tokenizer used by LTX-2 remains embedded.
|
||||
|
||||
| Option | Encoder |
|
||||
| --- | --- |
|
||||
| `--tokenizer FILE` | Main LLM/BPE encoder: Gemma 2, Gemma 3, Qwen 2/3, Mistral, GPT-OSS; also Anima and HiDream-O1 |
|
||||
@@ -44,7 +53,8 @@ sd-cli --diffusion-model sd3.gguf --clip_l clip_l.safetensors \
|
||||
```
|
||||
|
||||
The C API accepts the same string in `sd_ctx_params_t::tokenizer`. A null or
|
||||
empty value keeps the embedded tokenizers. The CLI passes the string through;
|
||||
empty value keeps an embedded tokenizer where available; PiD and Lens require
|
||||
a nonempty main tokenizer path. The CLI passes the string through;
|
||||
`TokenizerConfig` parses and validates it when text encoders are initialized.
|
||||
|
||||
```c
|
||||
|
||||
@@ -412,7 +412,7 @@ ArgOptions SDContextParams::get_options() {
|
||||
&llm_path},
|
||||
{"",
|
||||
"--tokenizer",
|
||||
"tokenizer.json path, or comma-separated main=FILE,clip-l=FILE,clip-g=FILE assignments",
|
||||
"tokenizer.json path, or comma-separated main=FILE,clip-l=FILE,clip-g=FILE assignments; required for PiD and Lens",
|
||||
(int)',',
|
||||
&tokenizer},
|
||||
{"",
|
||||
@@ -624,7 +624,7 @@ ArgOptions SDContextParams::get_options() {
|
||||
true, &diffusion_conv_direct},
|
||||
{"",
|
||||
"--vae-conv-direct",
|
||||
"use ggml_conv2d_direct in the vae model",
|
||||
"use direct 2D and 3D convolutions in the vae model",
|
||||
true, &vae_conv_direct},
|
||||
};
|
||||
|
||||
@@ -1109,7 +1109,7 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
&hires_upscaler},
|
||||
{"",
|
||||
"--extra-sample-args",
|
||||
"extra sampler/scheduler/guidance args, key=value list. CFG supports guidance_schedule; APG supports apg_eta, apg_momentum, apg_norm_threshold, apg_norm_threshold_smoothing; SLG supports slg_uncond; lcm supports noise_clip_std, noise_scale_start, noise_scale_end; flux supports base_shift, max_shift; ltx2 supports max_shift, base_shift, stretch, terminal; euler_ge supports gamma; beta scheduler supports alpha, beta; logit_normal supports mu, std, logsnr_min, logsnr_max, resolution_aware; lms supports lms_max_order, lms_shift, lms_divisions",
|
||||
"extra sampler/scheduler/guidance args, key=value list. CFG supports guidance_schedule; APG supports apg_eta, apg_momentum, apg_norm_threshold, apg_norm_threshold_smoothing; SLG supports slg_uncond; lcm supports noise_clip_std, noise_scale_start, noise_scale_end; flux supports base_shift, max_shift; ltx2 supports max_shift, base_shift, stretch, terminal; euler_ge supports gamma; beta scheduler supports alpha, beta; logit_normal supports mu, std, logsnr_min, logsnr_max, resolution_aware; lms supports lms_max_order, lms_shift, lms_divisions; noise-injecting samplers support noise_sampler with value iid (default except for dpm++2m_sde_bt) or brownian_tree; brownian_tree_rng supports cpu (default), cuda, std_default or sampler_rng",
|
||||
(int)',',
|
||||
&extra_sample_args},
|
||||
{"",
|
||||
|
||||
+1
-1
Submodule ggml updated: e20c3a14aa...c6632cd905
@@ -244,7 +244,7 @@ typedef struct {
|
||||
bool disable_segmented_compute; // Force monolithic graph execution even when automatic graph cutting would fit memory better
|
||||
float linear_scale; // Override linear input scaling; 0 keeps the model default
|
||||
float attn_scale; // Override flash-attention K/V scaling; 0 keeps the model default
|
||||
const char* tokenizer; // Optional tokenizer.json path or main=FILE,clip-l=FILE,clip-g=FILE assignments
|
||||
const char* tokenizer; // tokenizer.json path or main=FILE,clip-l=FILE,clip-g=FILE assignments; required for PiD and Lens
|
||||
} sd_ctx_params_t;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include "core/ggml_tensor_utils.h"
|
||||
|
||||
#include "core/tensor_ggml.hpp"
|
||||
@@ -1960,6 +1961,14 @@ struct LLMEmbedder : public Conditioner {
|
||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr,
|
||||
const TokenizerConfig& tokenizers = {})
|
||||
: version(version) {
|
||||
if (!tokenizers.has(TokenizerConfig::MAIN)) {
|
||||
if (sd_version_is_lens(version)) {
|
||||
throw std::runtime_error("Lens requires an external GPT-OSS tokenizer.json; pass --tokenizer FILE or set sd_ctx_params_t::tokenizer");
|
||||
}
|
||||
if (sd_version_is_pid(version)) {
|
||||
throw std::runtime_error("PiD requires an external Gemma 2 tokenizer.json; pass --tokenizer FILE or set sd_ctx_params_t::tokenizer");
|
||||
}
|
||||
}
|
||||
LLM::LLMArch arch = LLM::LLMArch::QWEN2_5_VL;
|
||||
if (version == VERSION_FLUX2) {
|
||||
arch = LLM::LLMArch::MISTRAL_SMALL_3_2;
|
||||
@@ -1969,7 +1978,8 @@ 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_lingbot_video(version) ||
|
||||
} else if (version == VERSION_QWEN_IMAGE_2_1 ||
|
||||
sd_version_is_lingbot_video(version) ||
|
||||
sd_version_is_ideogram4(version) ||
|
||||
sd_version_is_boogu_image(version) ||
|
||||
sd_version_is_sefi_image(version) ||
|
||||
@@ -1998,10 +2008,6 @@ struct LLMEmbedder : public Conditioner {
|
||||
if (!tokenizer) {
|
||||
if (arch == LLM::LLMArch::MISTRAL_SMALL_3_2 || arch == LLM::LLMArch::MINISTRAL_3_3B) {
|
||||
tokenizer = std::make_shared<MistralTokenizer>();
|
||||
} else if (arch == LLM::LLMArch::GPT_OSS_20B) {
|
||||
tokenizer = std::make_shared<GPTOSSTokenizer>();
|
||||
} else if (arch == LLM::LLMArch::GEMMA2_2B) {
|
||||
tokenizer = std::make_shared<Gemma2Tokenizer>();
|
||||
} else {
|
||||
tokenizer = std::make_shared<Qwen2Tokenizer>();
|
||||
}
|
||||
@@ -2542,6 +2548,67 @@ struct LLMEmbedder : public Conditioner {
|
||||
prompt += conditioner_params.text;
|
||||
prompt_attn_range = {0, 0};
|
||||
prompt += "<|im_end|>\n<|im_start|>assistant\n";
|
||||
} else if (version == VERSION_QWEN_IMAGE_2_1) {
|
||||
if (!llm->enable_vision && conditioner_params.ref_images != nullptr && !conditioner_params.ref_images->empty()) {
|
||||
LOG_ERROR("Qwen Image 2.1 editing requires Qwen3-VL vision weights; provide --llm_vision or a combined encoder");
|
||||
return {};
|
||||
}
|
||||
prompt = "<|im_start|>system\nComprehend and analyze the provided prompt.<|im_end|>\n";
|
||||
std::vector<int> system_tokens;
|
||||
if (!tokenizer->encode(prompt, system_tokens, nullptr)) {
|
||||
return {};
|
||||
}
|
||||
prompt_template_encode_start_idx = static_cast<int>(system_tokens.size());
|
||||
out_layers = {static_cast<int>(llm->config.num_layers)};
|
||||
prompt += "<|im_start|>user\n";
|
||||
if (llm->enable_vision && conditioner_params.ref_images != nullptr) {
|
||||
for (size_t i = 0; i < conditioner_params.ref_images->size(); ++i) {
|
||||
const auto& image = (*conditioner_params.ref_images)[i];
|
||||
int64_t width = image.shape()[0];
|
||||
int64_t height = image.shape()[1];
|
||||
int64_t pixels = width * height;
|
||||
if (width % 32 != 0 || height % 32 != 0) {
|
||||
LOG_ERROR("Qwen Image 2.1 reference dimensions must be multiples of 32");
|
||||
return {};
|
||||
}
|
||||
auto rgb = sd::Tensor<float>({width, height, 3, 1});
|
||||
for (int64_t p = 0; p < pixels; ++p) {
|
||||
float alpha = image.shape()[2] == 4 ? image[p + 3 * pixels] : 1.f;
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
rgb[p + c * pixels] = 2.f * (image[p + c * pixels] * alpha + 1.f - alpha) - 1.f;
|
||||
}
|
||||
}
|
||||
auto outputs = llm->encode_image_outputs(n_threads, rgb, false);
|
||||
if (outputs.empty()) {
|
||||
return {};
|
||||
}
|
||||
prompt += (i == 0 ? "" : " ") + std::string("<image") + std::to_string(i + 1) + "><|vision_start|>";
|
||||
std::vector<int> prefix_tokens;
|
||||
if (!tokenizer->encode(prompt, prefix_tokens, nullptr)) {
|
||||
return {};
|
||||
}
|
||||
int index = static_cast<int>(prefix_tokens.size());
|
||||
int count = static_cast<int>(outputs[0].shape()[1]);
|
||||
image_embeds.emplace_back(index, std::move(outputs[0]));
|
||||
if (deepstack_image_embeds.empty()) {
|
||||
deepstack_image_embeds.resize(outputs.size() - 1);
|
||||
}
|
||||
for (size_t layer = 1; layer < outputs.size(); ++layer) {
|
||||
deepstack_image_embeds[layer - 1].emplace_back(index, std::move(outputs[layer]));
|
||||
}
|
||||
image_grids.push_back({index, count,
|
||||
static_cast<int>(height) / llm->config.vision.patch_size,
|
||||
static_cast<int>(width) / llm->config.vision.patch_size});
|
||||
for (int j = 0; j < count; ++j) {
|
||||
prompt += "<|image_pad|>";
|
||||
}
|
||||
prompt += "<|vision_end|>";
|
||||
}
|
||||
}
|
||||
prompt_attn_range.first = static_cast<int>(prompt.size());
|
||||
prompt += conditioner_params.text.empty() ? " " : 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_qwen_image(version) || sd_version_is_mage_flow(version)) {
|
||||
if (llm->enable_vision && conditioner_params.ref_images != nullptr && !conditioner_params.ref_images->empty()) {
|
||||
LOG_INFO("%s", sd_version_is_mage_flow(version) ? "MageFlowEditPipeline" : "QwenImageEditPlusPipeline");
|
||||
@@ -3069,6 +3136,21 @@ struct LLMEmbedder : public Conditioner {
|
||||
SDCondition result;
|
||||
result.c_crossattn = std::move(hidden_states);
|
||||
result.extra_c_crossattns = std::move(extra_hidden_states_vec);
|
||||
if (version == VERSION_QWEN_IMAGE_2_1) {
|
||||
auto slots = sd::Tensor<int32_t>::zeros({result.c_crossattn.shape()[1]});
|
||||
for (size_t i = 0; i < image_embeds.size(); ++i) {
|
||||
int64_t begin = image_embeds[i].first - prompt_template_encode_start_idx;
|
||||
int64_t end = begin + image_embeds[i].second.shape()[1];
|
||||
if (begin < 0 || end > slots.numel()) {
|
||||
LOG_ERROR("Qwen Image 2.1 image slots exceed the encoded prompt");
|
||||
return {};
|
||||
}
|
||||
for (int64_t j = begin; j < end; ++j) {
|
||||
slots[j] = static_cast<int32_t>(i + 1);
|
||||
}
|
||||
}
|
||||
result.c_token_types = std::move(slots);
|
||||
}
|
||||
if (sd_version_is_minimax_h3(version)) {
|
||||
std::vector<int32_t> tags(static_cast<size_t>(result.c_crossattn.shape()[1]), 1);
|
||||
for (const auto& [index, image_embed] : image_embeds) {
|
||||
|
||||
@@ -362,6 +362,9 @@ bool convert_with_components(const char* model_path,
|
||||
const char* tensor_type_rules,
|
||||
bool convert_name,
|
||||
int n_threads) {
|
||||
if (!validate_tensor_types(output_type, tensor_type_rules)) {
|
||||
return false;
|
||||
}
|
||||
ModelLoader model_loader;
|
||||
bool loaded_any = false;
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace sd::backend_fit {
|
||||
for (const auto& [name, stored_tensor] : loader.get_tensor_storage_map()) {
|
||||
TensorStorage ts = stored_tensor;
|
||||
ComponentKind kind;
|
||||
if (is_unused_tensor(ts.name) || !classify_tensor(ts.name, kind)) {
|
||||
if (!classify_tensor(ts.name, kind)) {
|
||||
continue;
|
||||
}
|
||||
if (ts.expected_type != GGML_TYPE_COUNT) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "core/ggml_graph_cut.h"
|
||||
#include "core/util.h"
|
||||
#include "ggml-cpu.h"
|
||||
#include "ggml/src/ggml-impl.h"
|
||||
#include "ggml-impl.h"
|
||||
|
||||
namespace sd {
|
||||
ComputeWorkspace::~ComputeWorkspace() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "core/ggml_extend.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "core/ggml_extend_backend.h"
|
||||
@@ -247,6 +248,7 @@ ggml_tensor* ggml_ext_linear_i8_tensorwise(ggml_context* ctx,
|
||||
ggml_tensor* b,
|
||||
int convrot_group_size,
|
||||
float scale) {
|
||||
#ifndef SD_USE_UPSTREAM_GGML
|
||||
GGML_ASSERT(x->type == GGML_TYPE_F32 || (x->type == GGML_TYPE_I8 && scale == 1.f));
|
||||
if (scale != 1.f) {
|
||||
x = ggml_ext_scale(ctx, x, scale);
|
||||
@@ -270,6 +272,16 @@ ggml_tensor* ggml_ext_linear_i8_tensorwise(ggml_context* ctx,
|
||||
}
|
||||
}
|
||||
return x;
|
||||
#else
|
||||
GGML_UNUSED(ctx);
|
||||
GGML_UNUSED(x);
|
||||
GGML_UNUSED(w);
|
||||
GGML_UNUSED(weight_scale);
|
||||
GGML_UNUSED(b);
|
||||
GGML_UNUSED(convrot_group_size);
|
||||
GGML_UNUSED(scale);
|
||||
throw std::runtime_error("INT8 tensorwise/convrot is not supported by this ggml build");
|
||||
#endif
|
||||
}
|
||||
|
||||
ggml_tensor* ggml_ext_pad_ext(ggml_context* ctx,
|
||||
@@ -452,8 +464,13 @@ ggml_tensor* ggml_ext_conv_3d(ggml_context* ctx,
|
||||
int d0,
|
||||
int d1,
|
||||
int d2,
|
||||
bool force_prec_f32) {
|
||||
if (force_prec_f32) {
|
||||
bool force_prec_f32,
|
||||
bool direct) {
|
||||
if (direct) {
|
||||
int64_t OC = w->ne[3] / IC;
|
||||
int64_t N = x->ne[3] / IC;
|
||||
x = ggml_conv_3d_direct(ctx, w, x, s0, s1, s2, p0, p1, p2, d0, d1, d2, (int)IC, (int)N, (int)OC);
|
||||
} else if (force_prec_f32) {
|
||||
ggml_tensor* im2col = ggml_im2col_3d(ctx, w, x, IC, s0, s1, s2, p0, p1, p2, d0, d1, d2, w->type);
|
||||
|
||||
int64_t OC = w->ne[3] / IC;
|
||||
@@ -643,6 +660,14 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
ggml_tensor* kqv = nullptr;
|
||||
|
||||
auto build_kqv = [&](ggml_tensor* q_in, ggml_tensor* k_in, ggml_tensor* v_in, ggml_tensor* mask_in) -> ggml_tensor* {
|
||||
const bool pad_head = d_head > 0 && d_head < 64 && q_in->ne[0] == d_head && k_in->ne[0] == d_head &&
|
||||
q_in->type == GGML_TYPE_F32 && k_in->type == GGML_TYPE_F32 &&
|
||||
v_in->type == GGML_TYPE_F32 && sd_backend_supports_cuda_mma(backend);
|
||||
if (pad_head) {
|
||||
// CUDA FA MMA starts at 64 channels; keep the original head's attention scale.
|
||||
q_in = ggml_pad(ctx, q_in, 64 - d_head, 0, 0, 0);
|
||||
k_in = ggml_pad(ctx, k_in, 64 - d_head, 0, 0, 0);
|
||||
}
|
||||
if (kv_scale != 1.0f) {
|
||||
k_in = ggml_ext_scale(ctx, k_in, kv_scale);
|
||||
}
|
||||
@@ -650,6 +675,9 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
|
||||
v_in = ggml_ext_cont(ctx, ggml_permute(ctx, v_in, 0, 2, 1, 3));
|
||||
v_in = ggml_reshape_3d(ctx, v_in, d_head, L_k, n_kv_head * N);
|
||||
if (pad_head) {
|
||||
v_in = ggml_pad(ctx, v_in, 64 - d_head, 0, 0, 0);
|
||||
}
|
||||
if (kv_scale != 1.0f) {
|
||||
v_in = ggml_ext_scale(ctx, v_in, kv_scale);
|
||||
}
|
||||
@@ -679,6 +707,9 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
if (kv_scale != 1.0f) {
|
||||
out = ggml_ext_scale(ctx, out, 1.0f / kv_scale);
|
||||
}
|
||||
if (pad_head) {
|
||||
out = ggml_ext_slice(ctx, out, 0, 0, d_head);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
|
||||
@@ -153,7 +153,8 @@ ggml_tensor* ggml_ext_conv_3d(ggml_context* ctx,
|
||||
int d0 = 1,
|
||||
int d1 = 1,
|
||||
int d2 = 1,
|
||||
bool force_prec_f32 = false);
|
||||
bool force_prec_f32 = false,
|
||||
bool direct = false);
|
||||
|
||||
// w: [OC,IC, KD, 1 * 1]
|
||||
// x: [N, IC, ID, IH*IW]
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#ifdef SD_USE_CUDA
|
||||
#include <cuda.h>
|
||||
#endif
|
||||
|
||||
#include "core/util.h"
|
||||
#include "ggml/src/ggml-impl.h"
|
||||
#include "ggml-impl.h"
|
||||
#include "stable-diffusion.h"
|
||||
|
||||
static std::string trim_copy(const std::string& value) {
|
||||
@@ -429,6 +433,70 @@ bool sd_backend_is_cpu(ggml_backend_t backend) {
|
||||
return dev != nullptr && ggml_backend_dev_type(dev) == GGML_BACKEND_DEVICE_TYPE_CPU;
|
||||
}
|
||||
|
||||
bool sd_backend_supports_cuda_mma(ggml_backend_t backend) {
|
||||
#ifdef SD_USE_CUDA
|
||||
if (!sd_backend_is(backend, "CUDA")) {
|
||||
return false;
|
||||
}
|
||||
auto dev = ggml_backend_get_device(backend);
|
||||
if (dev == nullptr) {
|
||||
return false;
|
||||
}
|
||||
static std::mutex mutex;
|
||||
static std::unordered_map<ggml_backend_dev_t, bool> cache;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
auto it = cache.find(dev);
|
||||
if (it != cache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
const bool supported = [&]() {
|
||||
ggml_backend_dev_props props{};
|
||||
ggml_backend_dev_get_props(dev, &props);
|
||||
CUdevice device;
|
||||
int major = 0, minor = 0;
|
||||
if (props.device_id == nullptr || cuInit(0) != CUDA_SUCCESS ||
|
||||
cuDeviceGetByPCIBusId(&device, props.device_id) != CUDA_SUCCESS ||
|
||||
cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, device) != CUDA_SUCCESS ||
|
||||
cuDeviceGetAttribute(&minor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, device) != CUDA_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
auto reg = ggml_backend_dev_backend_reg(dev);
|
||||
auto get_features = reinterpret_cast<ggml_backend_get_features_t>(
|
||||
ggml_backend_reg_get_proc_address(reg, "ggml_backend_get_features"));
|
||||
if (get_features == nullptr) {
|
||||
return false;
|
||||
}
|
||||
// Match ggml's highest compiled architecture for this device, including PTX fallback.
|
||||
const int cc = 100 * major + 10 * minor;
|
||||
int compiled_arch = 0;
|
||||
for (auto feature = get_features(reg); feature != nullptr && feature->name != nullptr; ++feature) {
|
||||
if (std::strcmp(feature->name, "ARCHS") != 0 || feature->value == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const char* arch = feature->value;
|
||||
while (*arch != '\0') {
|
||||
char* end = nullptr;
|
||||
const long value = std::strtol(arch, &end, 10);
|
||||
if (end == arch) {
|
||||
++arch;
|
||||
continue;
|
||||
}
|
||||
if (value <= cc && value > compiled_arch) {
|
||||
compiled_arch = static_cast<int>(value);
|
||||
}
|
||||
arch = end;
|
||||
}
|
||||
}
|
||||
return compiled_arch == 700 || compiled_arch >= 750;
|
||||
}();
|
||||
cache.emplace(dev, supported);
|
||||
return supported;
|
||||
#else
|
||||
(void)backend;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
ggml_backend_t sd_backend_cpu_init() {
|
||||
ggml_backend_load_all_once();
|
||||
return ggml_backend_init_by_type(GGML_BACKEND_DEVICE_TYPE_CPU, nullptr);
|
||||
|
||||
@@ -87,6 +87,7 @@ private:
|
||||
|
||||
bool sd_backend_is(ggml_backend_t backend, const std::string& name);
|
||||
bool sd_backend_is_cpu(ggml_backend_t backend);
|
||||
bool sd_backend_supports_cuda_mma(ggml_backend_t backend);
|
||||
ggml_backend_t sd_backend_cpu_init();
|
||||
bool sd_backend_cpu_set_n_threads(ggml_backend_t backend_cpu, int n_threads);
|
||||
ggml_status sd_backend_graph_compute_with_eval_callback(ggml_backend_t backend,
|
||||
|
||||
+86
-29
@@ -16,7 +16,7 @@
|
||||
#include "ggml-alloc.h"
|
||||
#include "ggml-backend.h"
|
||||
|
||||
#include "ggml/src/ggml-impl.h"
|
||||
#include "ggml-impl.h"
|
||||
|
||||
namespace sd::ggml_graph_cut {
|
||||
|
||||
@@ -426,8 +426,8 @@ namespace sd::ggml_graph_cut {
|
||||
if (tensor == nullptr || tensor->name[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
return starts_with(tensor->name, GGML_RUNNER_CUT_PREFIX) &&
|
||||
ends_with(tensor->name, GGML_RUNNER_CUT_SUFFIX);
|
||||
return std::strncmp(tensor->name, GGML_RUNNER_CUT_PREFIX, std::strlen(GGML_RUNNER_CUT_PREFIX)) == 0 &&
|
||||
tensor->name[std::strlen(tensor->name) - 1] == GGML_RUNNER_CUT_SUFFIX[0];
|
||||
}
|
||||
|
||||
std::string make_graph_cut_name(const std::string& group, const std::string& output) {
|
||||
@@ -492,35 +492,88 @@ namespace sd::ggml_graph_cut {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint64_t> graph_layout(ggml_cgraph* graph, bool include_bindings) {
|
||||
std::vector<const ggml_tensor*> tensors;
|
||||
std::unordered_map<const ggml_tensor*, size_t> indices;
|
||||
auto add = [&](const ggml_tensor* tensor) {
|
||||
if (tensor != nullptr && indices.emplace(tensor, tensors.size() + 1).second) {
|
||||
tensors.push_back(tensor);
|
||||
}
|
||||
struct GraphLayoutTensors {
|
||||
struct Entry {
|
||||
const ggml_tensor* tensor = nullptr;
|
||||
size_t index = 0;
|
||||
};
|
||||
|
||||
std::vector<ggml_tensor*> tensors;
|
||||
std::vector<Entry> entries;
|
||||
|
||||
explicit GraphLayoutTensors(size_t graph_size) {
|
||||
tensors.reserve(graph_size);
|
||||
size_t capacity = 2;
|
||||
while (capacity < 2 * graph_size) {
|
||||
capacity *= 2;
|
||||
}
|
||||
entries.resize(capacity);
|
||||
}
|
||||
|
||||
size_t find(const ggml_tensor* tensor) const {
|
||||
size_t hash = reinterpret_cast<uintptr_t>(tensor) >> 4;
|
||||
hash ^= hash >> 16;
|
||||
const size_t mask = entries.size() - 1;
|
||||
size_t slot = hash & mask;
|
||||
while (entries[slot].tensor != nullptr && entries[slot].tensor != tensor) {
|
||||
slot = (slot + 1) & mask;
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
void add(ggml_tensor* tensor) {
|
||||
if (tensor == nullptr) {
|
||||
return;
|
||||
}
|
||||
size_t slot = find(tensor);
|
||||
if (entries[slot].tensor != nullptr) {
|
||||
return;
|
||||
}
|
||||
if (2 * (tensors.size() + 1) > entries.size()) {
|
||||
// Segment graphs can reference tensors outside their node and leaf arrays.
|
||||
std::vector<Entry> next(2 * entries.size());
|
||||
entries.swap(next);
|
||||
for (size_t i = 0; i < tensors.size(); ++i) {
|
||||
entries[find(tensors[i])] = {tensors[i], i + 1};
|
||||
}
|
||||
slot = find(tensor);
|
||||
}
|
||||
entries[slot] = {tensor, tensors.size() + 1};
|
||||
tensors.push_back(tensor);
|
||||
}
|
||||
|
||||
size_t index(const ggml_tensor* tensor) const {
|
||||
return tensor == nullptr ? 0 : entries[find(tensor)].index;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<uint64_t> graph_layout(ggml_cgraph* graph, bool include_bindings) {
|
||||
const size_t graph_size = static_cast<size_t>(graph->n_leafs) + graph->n_nodes;
|
||||
GraphLayoutTensors layout_tensors(graph_size);
|
||||
const auto& tensors = layout_tensors.tensors;
|
||||
for (int i = 0; i < graph->n_leafs; ++i) {
|
||||
add(graph->leafs[i]);
|
||||
layout_tensors.add(graph->leafs[i]);
|
||||
}
|
||||
for (int i = 0; i < graph->n_nodes; ++i) {
|
||||
add(graph->nodes[i]);
|
||||
layout_tensors.add(graph->nodes[i]);
|
||||
}
|
||||
for (size_t i = 0; i < tensors.size(); ++i) {
|
||||
add(tensors[i]->view_src);
|
||||
layout_tensors.add(tensors[i]->view_src);
|
||||
for (auto source : tensors[i]->src) {
|
||||
add(source);
|
||||
layout_tensors.add(source);
|
||||
}
|
||||
}
|
||||
std::vector<uint64_t> signature;
|
||||
signature.reserve(tensors.size() * 24);
|
||||
const size_t tensor_fields = 5 + 2 * GGML_MAX_DIMS + GGML_MAX_SRC +
|
||||
GGML_MAX_OP_PARAMS / sizeof(int32_t) + (include_bindings ? 2 : 0);
|
||||
signature.reserve(2 + graph_size + tensors.size() * tensor_fields);
|
||||
signature.push_back(graph->n_nodes);
|
||||
signature.push_back(graph->n_leafs);
|
||||
for (int i = 0; i < graph->n_leafs; ++i) {
|
||||
signature.push_back(indices.at(graph->leafs[i]));
|
||||
signature.push_back(layout_tensors.index(graph->leafs[i]));
|
||||
}
|
||||
for (int i = 0; i < graph->n_nodes; ++i) {
|
||||
signature.push_back(indices.at(graph->nodes[i]));
|
||||
signature.push_back(layout_tensors.index(graph->nodes[i]));
|
||||
}
|
||||
for (auto tensor : tensors) {
|
||||
signature.push_back(tensor->op);
|
||||
@@ -536,9 +589,9 @@ namespace sd::ggml_graph_cut {
|
||||
signature.push_back(tensor->ne[d]);
|
||||
signature.push_back(tensor->nb[d]);
|
||||
}
|
||||
signature.push_back(tensor->view_src == nullptr ? 0 : indices.at(tensor->view_src));
|
||||
signature.push_back(layout_tensors.index(tensor->view_src));
|
||||
for (auto source : tensor->src) {
|
||||
signature.push_back(source == nullptr ? 0 : indices.at(source));
|
||||
signature.push_back(layout_tensors.index(source));
|
||||
}
|
||||
if (!can_ignore_op_params(tensor->op)) {
|
||||
for (int value : tensor->op_params) {
|
||||
@@ -562,14 +615,18 @@ namespace sd::ggml_graph_cut {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::vector<std::pair<int, std::string>> cut_markers;
|
||||
for (int i = 0; i < ggml_graph_n_nodes(gf); ++i) {
|
||||
auto node = ggml_graph_node(gf, i);
|
||||
size_t cut_index = 0;
|
||||
for (int i = 0; i < gf->n_nodes; ++i) {
|
||||
auto node = gf->nodes[i];
|
||||
if (is_graph_cut_tensor(node)) {
|
||||
cut_markers.emplace_back(i, node->name);
|
||||
if (cut_index >= plan.cut_markers.size() ||
|
||||
plan.cut_markers[cut_index].first != i || plan.cut_markers[cut_index].second != node->name) {
|
||||
return false;
|
||||
}
|
||||
++cut_index;
|
||||
}
|
||||
}
|
||||
return cut_markers == plan.cut_markers;
|
||||
return cut_index == plan.cut_markers.size();
|
||||
}
|
||||
|
||||
bool plan_matches_graph(ggml_cgraph* gf, const Plan& plan) {
|
||||
@@ -948,11 +1005,11 @@ namespace sd::ggml_graph_cut {
|
||||
return plan;
|
||||
}
|
||||
|
||||
Plan resolve_plan(ggml_backend_t backend,
|
||||
ggml_cgraph* gf,
|
||||
PlanCache* cache,
|
||||
const std::unordered_set<const ggml_tensor*>& params_tensor_set,
|
||||
const char* log_desc) {
|
||||
const Plan& resolve_plan(ggml_backend_t backend,
|
||||
ggml_cgraph* gf,
|
||||
PlanCache* cache,
|
||||
const std::unordered_set<const ggml_tensor*>& params_tensor_set,
|
||||
const char* log_desc) {
|
||||
GGML_ASSERT(backend != nullptr);
|
||||
GGML_ASSERT(gf != nullptr);
|
||||
GGML_ASSERT(cache != nullptr);
|
||||
|
||||
@@ -94,11 +94,12 @@ namespace sd::ggml_graph_cut {
|
||||
ggml_cgraph* gf,
|
||||
const std::unordered_set<const ggml_tensor*>& params_tensor_set,
|
||||
const char* log_desc);
|
||||
Plan resolve_plan(ggml_backend_t backend,
|
||||
ggml_cgraph* gf,
|
||||
PlanCache* cache,
|
||||
const std::unordered_set<const ggml_tensor*>& params_tensor_set,
|
||||
const char* log_desc);
|
||||
// The returned reference is valid until its cache entry is evicted or the cache is destroyed.
|
||||
const Plan& resolve_plan(ggml_backend_t backend,
|
||||
ggml_cgraph* gf,
|
||||
PlanCache* cache,
|
||||
const std::unordered_set<const ggml_tensor*>& params_tensor_set,
|
||||
const char* log_desc);
|
||||
|
||||
} // namespace sd::ggml_graph_cut
|
||||
|
||||
|
||||
+25
-27
@@ -342,21 +342,17 @@ void GGMLRunner::copy_data_to_backend_tensor(ggml_cgraph* gf, bool clear_after_c
|
||||
}
|
||||
}
|
||||
|
||||
bool GGMLRunner::resolve_graph_cut_plan(ggml_cgraph* gf,
|
||||
GraphCutPlan* plan_out) {
|
||||
GGML_ASSERT(plan_out != nullptr);
|
||||
const GGMLRunner::GraphCutPlan& GGMLRunner::resolve_graph_cut_plan(ggml_cgraph* gf) {
|
||||
GGML_ASSERT(gf != nullptr);
|
||||
*plan_out = sd::ggml_graph_cut::resolve_plan(runtime_backend,
|
||||
gf,
|
||||
&graph_cut_plan_cache_,
|
||||
params_tensor_set_,
|
||||
get_desc().c_str());
|
||||
return true;
|
||||
return sd::ggml_graph_cut::resolve_plan(runtime_backend,
|
||||
gf,
|
||||
&graph_cut_plan_cache_,
|
||||
params_tensor_set_,
|
||||
get_desc().c_str());
|
||||
}
|
||||
|
||||
bool GGMLRunner::resolve_graph_cut_layer_split_plan(ggml_cgraph* gf,
|
||||
GraphCutPlan* plan_out) {
|
||||
return resolve_graph_cut_plan(gf, plan_out);
|
||||
const GGMLRunner::GraphCutPlan& GGMLRunner::resolve_graph_cut_layer_split_plan(ggml_cgraph* gf) {
|
||||
return resolve_graph_cut_plan(gf);
|
||||
}
|
||||
|
||||
bool GGMLRunner::assign_graph_cut_layer_split_backends(ggml_cgraph* gf) {
|
||||
@@ -369,10 +365,7 @@ bool GGMLRunner::assign_graph_cut_layer_split_backends(ggml_cgraph* gf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GraphCutPlan plan;
|
||||
if (!resolve_graph_cut_layer_split_plan(gf, &plan)) {
|
||||
return false;
|
||||
}
|
||||
const auto& plan = resolve_graph_cut_layer_split_plan(gf);
|
||||
if (!plan.valid || !plan.has_cuts || plan.segments.size() <= 1) {
|
||||
auto manager = residency_manager.lock();
|
||||
if (manager == nullptr) {
|
||||
@@ -530,6 +523,7 @@ GGMLRunnerContext GGMLRunner::get_context() {
|
||||
runner_ctx.linear_scale = linear_scale;
|
||||
runner_ctx.attn_scale = attn_scale;
|
||||
runner_ctx.conv2d_direct_enabled = conv2d_direct_enabled;
|
||||
runner_ctx.conv3d_direct_enabled = conv3d_direct_enabled;
|
||||
runner_ctx.circular_x_enabled = circular_x_enabled;
|
||||
runner_ctx.circular_y_enabled = circular_y_enabled;
|
||||
runner_ctx.weight_adapter = weight_adapter;
|
||||
@@ -819,24 +813,25 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
|
||||
if (!assign_graph_cut_layer_split_backends(graph)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto params = collect_used_param_tensors(graph);
|
||||
ggml_graph_cut::Plan plan;
|
||||
if (!resolve_graph_cut_plan(graph, &plan)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto full_measurement = measure(graph, plan.compute_buffer_size);
|
||||
const auto params = collect_used_param_tensors(graph);
|
||||
const auto& cached_plan = resolve_graph_cut_plan(graph);
|
||||
const auto full_measurement = measure(graph, cached_plan.compute_buffer_size);
|
||||
if (full_measurement.buffers.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto manager = residency_manager.lock();
|
||||
const bool segmented = !is_multi_device() && !sd_backend_is_cpu(runtime_backend) &&
|
||||
manager != nullptr && manager->segmented_compute_enabled() &&
|
||||
plan.valid && plan.has_cuts && plan.segments.size() > 1 &&
|
||||
cached_plan.valid && cached_plan.has_cuts && cached_plan.segments.size() > 1 &&
|
||||
!fits(memory_requests(full_measurement.buffers, cache_.pending_bytes(graph)), params);
|
||||
ggml_graph_cut::Plan monolithic_plan;
|
||||
if (!segmented) {
|
||||
ggml_graph_cut::Segment segment;
|
||||
monolithic_plan.segments.emplace_back();
|
||||
auto& segment = monolithic_plan.segments.back();
|
||||
segment.group_name = "graph";
|
||||
segment.compute_buffer_size = plan.compute_buffer_size;
|
||||
segment.compute_buffer_size = cached_plan.compute_buffer_size;
|
||||
segment.internal_node_indices.reserve(ggml_graph_n_nodes(graph));
|
||||
segment.input_refs.reserve(ggml_graph_cut::leaf_count(graph));
|
||||
for (int i = 0; i < ggml_graph_n_nodes(graph); ++i) {
|
||||
segment.internal_node_indices.push_back(i);
|
||||
}
|
||||
@@ -849,8 +844,8 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
|
||||
: ggml_graph_cut::Segment::INPUT_EXTERNAL;
|
||||
segment.input_refs.push_back(input);
|
||||
}
|
||||
plan.segments = {std::move(segment)};
|
||||
}
|
||||
const auto& plan = segmented ? cached_plan : monolithic_plan;
|
||||
const bool segments_changed = plan.segments.size() != logged_segment_count_;
|
||||
if (segments_changed && (segmented || logged_segment_count_ > 1)) {
|
||||
LOG_VERBOSE("%s using %zu segment%s", get_desc().c_str(),
|
||||
@@ -909,7 +904,10 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
|
||||
auto ensure_capacity = [&]() {
|
||||
sync_runtime_residency();
|
||||
auto requests = memory_requests(measurement.buffers, new_cache_bytes);
|
||||
if (!fits(requests, weights.params(index)) && workspace_.release_excess(measurement)) {
|
||||
if (fits(requests, weights.params(index))) {
|
||||
return true;
|
||||
}
|
||||
if (workspace_.release_excess(measurement)) {
|
||||
sync_runtime_residency();
|
||||
requests = memory_requests(measurement.buffers, new_cache_bytes);
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ struct GGMLRunnerContext {
|
||||
float linear_scale = 0.f;
|
||||
float attn_scale = 0.f;
|
||||
bool conv2d_direct_enabled = false;
|
||||
bool conv3d_direct_enabled = false;
|
||||
bool circular_x_enabled = false;
|
||||
bool circular_y_enabled = false;
|
||||
ggml_tensor* ip_context = nullptr;
|
||||
@@ -178,6 +179,7 @@ protected:
|
||||
float linear_scale = 0.f;
|
||||
float attn_scale = 0.f;
|
||||
bool conv2d_direct_enabled = false;
|
||||
bool conv3d_direct_enabled = false;
|
||||
bool circular_x_enabled = false;
|
||||
bool circular_y_enabled = false;
|
||||
|
||||
@@ -263,11 +265,9 @@ protected:
|
||||
|
||||
void copy_data_to_backend_tensor(ggml_cgraph* gf, bool clear_after_copy = true);
|
||||
|
||||
bool resolve_graph_cut_plan(ggml_cgraph* gf,
|
||||
GraphCutPlan* plan_out);
|
||||
const GraphCutPlan& resolve_graph_cut_plan(ggml_cgraph* gf);
|
||||
|
||||
bool resolve_graph_cut_layer_split_plan(ggml_cgraph* gf,
|
||||
GraphCutPlan* plan_out);
|
||||
const GraphCutPlan& resolve_graph_cut_layer_split_plan(ggml_cgraph* gf);
|
||||
|
||||
bool assign_graph_cut_layer_split_backends(ggml_cgraph* gf);
|
||||
|
||||
@@ -346,6 +346,10 @@ public:
|
||||
conv2d_direct_enabled = enabled;
|
||||
}
|
||||
|
||||
void set_conv3d_direct_enabled(bool enabled) {
|
||||
conv3d_direct_enabled = enabled;
|
||||
}
|
||||
|
||||
void set_circular_axes(bool circular_x, bool circular_y) {
|
||||
circular_x_enabled = circular_x;
|
||||
circular_y_enabled = circular_y;
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
#include "core/parallel.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace sd {
|
||||
|
||||
namespace parallel_detail {
|
||||
thread_local ParallelExecutor* executor = nullptr;
|
||||
thread_local bool active = false;
|
||||
}
|
||||
|
||||
struct ParallelExecutor::Impl {
|
||||
struct Worker {
|
||||
std::condition_variable wake;
|
||||
std::thread thread;
|
||||
bool ready = false;
|
||||
};
|
||||
|
||||
ParallelExecutor* owner;
|
||||
std::mutex invocation_mutex;
|
||||
std::mutex mutex;
|
||||
std::condition_variable finished;
|
||||
std::vector<std::unique_ptr<Worker>> workers;
|
||||
bool stopping = false;
|
||||
int pending = 0;
|
||||
int participants = 1;
|
||||
int64_t begin = 0;
|
||||
int64_t count = 0;
|
||||
const std::function<void(int64_t, int64_t)>* task = nullptr;
|
||||
std::exception_ptr error;
|
||||
|
||||
explicit Impl(ParallelExecutor* owner)
|
||||
: owner(owner) {}
|
||||
|
||||
~Impl() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
stopping = true;
|
||||
}
|
||||
for (auto& worker : workers) {
|
||||
worker->wake.notify_one();
|
||||
}
|
||||
for (auto& worker : workers) {
|
||||
worker->thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void execute(int index) {
|
||||
ParallelScope scope(owner);
|
||||
parallel_detail::Region region;
|
||||
const int64_t size = count / participants;
|
||||
const int64_t extra = count % participants;
|
||||
const int64_t first = begin + index * size + std::min<int64_t>(index, extra);
|
||||
const int64_t last = first + size + (index < extra ? 1 : 0);
|
||||
try {
|
||||
(*task)(first, last);
|
||||
} catch (...) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if (!error) {
|
||||
error = std::current_exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void worker_loop(Worker* worker, int index) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
for (;;) {
|
||||
worker->wake.wait(lock, [&] { return stopping || worker->ready; });
|
||||
if (stopping) {
|
||||
return;
|
||||
}
|
||||
worker->ready = false;
|
||||
lock.unlock();
|
||||
execute(index);
|
||||
lock.lock();
|
||||
if (--pending == 0) {
|
||||
finished.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run(int64_t first, int64_t last, int n_tasks, const std::function<void(int64_t, int64_t)>& callback) {
|
||||
std::lock_guard<std::mutex> invocation_lock(invocation_mutex);
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
while (static_cast<int>(workers.size()) < n_tasks - 1) {
|
||||
workers.push_back(std::make_unique<Worker>());
|
||||
auto* worker = workers.back().get();
|
||||
const int index = static_cast<int>(workers.size());
|
||||
try {
|
||||
worker->thread = std::thread([this, worker, index] { worker_loop(worker, index); });
|
||||
} catch (...) {
|
||||
workers.pop_back();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
begin = first;
|
||||
count = last - first;
|
||||
participants = n_tasks;
|
||||
pending = n_tasks - 1;
|
||||
task = &callback;
|
||||
error = nullptr;
|
||||
for (int i = 0; i < pending; ++i) {
|
||||
workers[i]->ready = true;
|
||||
workers[i]->wake.notify_one();
|
||||
}
|
||||
lock.unlock();
|
||||
execute(0);
|
||||
lock.lock();
|
||||
finished.wait(lock, [&] { return pending == 0; });
|
||||
task = nullptr;
|
||||
if (error) {
|
||||
std::rethrow_exception(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ParallelExecutor::ParallelExecutor(int n_threads)
|
||||
: n_threads_(std::max(1, n_threads)), impl_(std::make_unique<Impl>(this)) {}
|
||||
|
||||
ParallelExecutor::~ParallelExecutor() = default;
|
||||
|
||||
void ParallelExecutor::run(int64_t begin, int64_t end, int64_t grain_size, const std::function<void(int64_t, int64_t)>& task) {
|
||||
if (begin < 0 || grain_size <= 0) {
|
||||
throw std::invalid_argument("parallel_for requires begin >= 0 and grain_size > 0");
|
||||
}
|
||||
if (end <= begin) {
|
||||
return;
|
||||
}
|
||||
const int n_tasks = static_cast<int>(std::min<int64_t>(n_threads_, (end - begin) / grain_size));
|
||||
if (parallel_detail::active || n_tasks <= 1) {
|
||||
parallel_detail::Region region;
|
||||
task(begin, end);
|
||||
return;
|
||||
}
|
||||
impl_->run(begin, end, n_tasks, task);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef __SD_CORE_PARALLEL_H__
|
||||
#define __SD_CORE_PARALLEL_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace sd {
|
||||
|
||||
class ParallelExecutor {
|
||||
struct Impl;
|
||||
int n_threads_;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
|
||||
public:
|
||||
explicit ParallelExecutor(int n_threads);
|
||||
~ParallelExecutor();
|
||||
ParallelExecutor(const ParallelExecutor&) = delete;
|
||||
ParallelExecutor& operator=(const ParallelExecutor&) = delete;
|
||||
|
||||
int num_threads() const { return n_threads_; }
|
||||
void run(int64_t begin, int64_t end, int64_t grain_size, const std::function<void(int64_t, int64_t)>& task);
|
||||
};
|
||||
|
||||
namespace parallel_detail {
|
||||
extern thread_local ParallelExecutor* executor;
|
||||
extern thread_local bool active;
|
||||
|
||||
class Region {
|
||||
bool previous_;
|
||||
|
||||
public:
|
||||
Region()
|
||||
: previous_(active) { active = true; }
|
||||
~Region() { active = previous_; }
|
||||
Region(const Region&) = delete;
|
||||
Region& operator=(const Region&) = delete;
|
||||
};
|
||||
}
|
||||
|
||||
class ParallelScope {
|
||||
ParallelExecutor* previous_;
|
||||
|
||||
public:
|
||||
explicit ParallelScope(ParallelExecutor* executor)
|
||||
: previous_(parallel_detail::executor) {
|
||||
parallel_detail::executor = executor;
|
||||
}
|
||||
~ParallelScope() { parallel_detail::executor = previous_; }
|
||||
ParallelScope(const ParallelScope&) = delete;
|
||||
ParallelScope& operator=(const ParallelScope&) = delete;
|
||||
};
|
||||
|
||||
// Ranges are non-negative. The callback may run concurrently and must own its writes.
|
||||
template <typename F>
|
||||
inline void parallel_for(int64_t begin, int64_t end, int64_t grain_size, F&& task) {
|
||||
if (begin < 0 || grain_size <= 0) {
|
||||
throw std::invalid_argument("parallel_for requires begin >= 0 and grain_size > 0");
|
||||
}
|
||||
if (end <= begin) {
|
||||
return;
|
||||
}
|
||||
auto* executor = parallel_detail::executor;
|
||||
if (parallel_detail::active || executor == nullptr || executor->num_threads() <= 1 ||
|
||||
(end - begin) / grain_size < 2) {
|
||||
parallel_detail::Region region;
|
||||
task(begin, end);
|
||||
return;
|
||||
}
|
||||
executor->run(begin, end, grain_size, std::forward<F>(task));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // __SD_CORE_PARALLEL_H__
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef __SD_CORE_RNG_HPP__
|
||||
#define __SD_CORE_RNG_HPP__
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
@@ -8,6 +10,7 @@ class RNG {
|
||||
public:
|
||||
virtual void manual_seed(uint64_t seed) = 0;
|
||||
virtual std::vector<float> randn(uint32_t n) = 0;
|
||||
virtual std::shared_ptr<RNG> clone() const = 0;
|
||||
};
|
||||
|
||||
class STDDefaultRNG : public RNG {
|
||||
@@ -15,6 +18,10 @@ private:
|
||||
std::default_random_engine generator;
|
||||
|
||||
public:
|
||||
std::shared_ptr<RNG> clone() const override {
|
||||
return std::make_shared<STDDefaultRNG>(*this);
|
||||
}
|
||||
|
||||
void manual_seed(uint64_t seed) override {
|
||||
generator.seed((unsigned int)seed);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#ifndef __SD_CORE_RNG_MT19937_HPP__
|
||||
#define __SD_CORE_RNG_MT19937_HPP__
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "core/rng.hpp"
|
||||
@@ -123,6 +125,10 @@ class MT19937RNG : public RNG {
|
||||
public:
|
||||
MT19937RNG(uint64_t seed = 0) { manual_seed(seed); }
|
||||
|
||||
std::shared_ptr<RNG> clone() const override {
|
||||
return std::make_shared<MT19937RNG>(*this);
|
||||
}
|
||||
|
||||
void manual_seed(uint64_t seed) override {
|
||||
s.seed_ = seed;
|
||||
s.seeded_ = true;
|
||||
|
||||
+31
-60
@@ -1,6 +1,7 @@
|
||||
#ifndef __SD_CORE_RNG_PHILOX_HPP__
|
||||
#define __SD_CORE_RNG_PHILOX_HPP__
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
@@ -14,67 +15,35 @@ private:
|
||||
uint32_t offset;
|
||||
|
||||
private:
|
||||
std::vector<uint32_t> philox_m = {0xD2511F53, 0xCD9E8D57};
|
||||
std::vector<uint32_t> philox_w = {0x9E3779B9, 0xBB67AE85};
|
||||
float two_pow32_inv = 2.3283064e-10f;
|
||||
float two_pow32_inv_2pi = 2.3283064e-10f * 6.2831855f;
|
||||
using Counter = std::array<std::vector<uint32_t>, 4>;
|
||||
|
||||
std::vector<uint32_t> uint32(uint64_t x) {
|
||||
std::vector<uint32_t> result(2);
|
||||
result[0] = static_cast<uint32_t>(x & 0xFFFFFFFF);
|
||||
result[1] = static_cast<uint32_t>(x >> 32);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::vector<uint32_t>> uint32(const std::vector<uint64_t>& x) {
|
||||
uint32_t N = (uint32_t)x.size();
|
||||
std::vector<std::vector<uint32_t>> result(2, std::vector<uint32_t>(N));
|
||||
|
||||
for (uint32_t i = 0; i < N; ++i) {
|
||||
result[0][i] = static_cast<uint32_t>(x[i] & 0xFFFFFFFF);
|
||||
result[1][i] = static_cast<uint32_t>(x[i] >> 32);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
static constexpr uint32_t philox_m[2] = {0xD2511F53, 0xCD9E8D57};
|
||||
static constexpr uint32_t philox_w[2] = {0x9E3779B9, 0xBB67AE85};
|
||||
float two_pow32_inv = 2.3283064e-10f;
|
||||
float two_pow32_inv_2pi = 2.3283064e-10f * 6.2831855f;
|
||||
|
||||
// A single round of the Philox 4x32 random number generator.
|
||||
void philox4_round(std::vector<std::vector<uint32_t>>& counter,
|
||||
const std::vector<std::vector<uint32_t>>& key) {
|
||||
void philox4_round(Counter& counter, uint32_t key0, uint32_t key1) {
|
||||
uint32_t N = (uint32_t)counter[0].size();
|
||||
for (uint32_t i = 0; i < N; i++) {
|
||||
std::vector<uint32_t> v1 = uint32(static_cast<uint64_t>(counter[0][i]) * static_cast<uint64_t>(philox_m[0]));
|
||||
std::vector<uint32_t> v2 = uint32(static_cast<uint64_t>(counter[2][i]) * static_cast<uint64_t>(philox_m[1]));
|
||||
const uint64_t v1 = static_cast<uint64_t>(counter[0][i]) * static_cast<uint64_t>(philox_m[0]);
|
||||
const uint64_t v2 = static_cast<uint64_t>(counter[2][i]) * static_cast<uint64_t>(philox_m[1]);
|
||||
|
||||
counter[0][i] = v2[1] ^ counter[1][i] ^ key[0][i];
|
||||
counter[1][i] = v2[0];
|
||||
counter[2][i] = v1[1] ^ counter[3][i] ^ key[1][i];
|
||||
counter[3][i] = v1[0];
|
||||
counter[0][i] = static_cast<uint32_t>(v2 >> 32) ^ counter[1][i] ^ key0;
|
||||
counter[1][i] = static_cast<uint32_t>(v2);
|
||||
counter[2][i] = static_cast<uint32_t>(v1 >> 32) ^ counter[3][i] ^ key1;
|
||||
counter[3][i] = static_cast<uint32_t>(v1);
|
||||
}
|
||||
}
|
||||
|
||||
// Generates 32-bit random numbers using the Philox 4x32 random number generator.
|
||||
// Parameters:
|
||||
// counter : A 4xN array of 32-bit integers representing the counter values (offset into generation).
|
||||
// key : A 2xN array of 32-bit integers representing the key values (seed).
|
||||
// rounds : The number of rounds to perform.
|
||||
// Returns:
|
||||
// std::vector<std::vector<uint32_t>>: A 4xN array of 32-bit integers containing the generated random numbers.
|
||||
std::vector<std::vector<uint32_t>> philox4_32(std::vector<std::vector<uint32_t>>& counter,
|
||||
std::vector<std::vector<uint32_t>>& key,
|
||||
int rounds = 10) {
|
||||
uint32_t N = (uint32_t)counter[0].size();
|
||||
void philox4_32(Counter& counter, uint32_t key0, uint32_t key1, int rounds = 10) {
|
||||
for (int i = 0; i < rounds - 1; ++i) {
|
||||
philox4_round(counter, key);
|
||||
|
||||
for (uint32_t j = 0; j < N; ++j) {
|
||||
key[0][j] += philox_w[0];
|
||||
key[1][j] += philox_w[1];
|
||||
}
|
||||
philox4_round(counter, key0, key1);
|
||||
key0 += philox_w[0];
|
||||
key1 += philox_w[1];
|
||||
}
|
||||
|
||||
philox4_round(counter, key);
|
||||
return counter;
|
||||
philox4_round(counter, key0, key1);
|
||||
}
|
||||
|
||||
float box_muller(float x, float y) {
|
||||
@@ -93,33 +62,35 @@ public:
|
||||
this->offset = 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<RNG> clone() const override {
|
||||
return std::make_shared<PhiloxRNG>(*this);
|
||||
}
|
||||
|
||||
void manual_seed(uint64_t seed) override {
|
||||
this->seed = seed;
|
||||
this->offset = 0;
|
||||
}
|
||||
|
||||
std::vector<float> randn(uint32_t n) override {
|
||||
std::vector<std::vector<uint32_t>> counter(4, std::vector<uint32_t>(n, 0));
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
counter[0][i] = this->offset;
|
||||
}
|
||||
Counter counter;
|
||||
counter[0].resize(n, this->offset);
|
||||
counter[1].resize(n);
|
||||
counter[2].resize(n);
|
||||
counter[3].resize(n);
|
||||
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
counter[2][i] = i;
|
||||
}
|
||||
this->offset += 1;
|
||||
|
||||
std::vector<uint64_t> key(n, this->seed);
|
||||
std::vector<std::vector<uint32_t>> key_uint32 = uint32(key);
|
||||
philox4_32(counter, static_cast<uint32_t>(this->seed), static_cast<uint32_t>(this->seed >> 32));
|
||||
|
||||
std::vector<std::vector<uint32_t>> g = philox4_32(counter, key_uint32);
|
||||
|
||||
std::vector<float> result;
|
||||
std::vector<float> result(n);
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
result.push_back(box_muller((float)g[0][i], (float)g[1][i]));
|
||||
result[i] = box_muller((float)counter[0][i], (float)counter[1][i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __SD_CORE_RNG_PHILOX_HPP__
|
||||
#endif // __SD_CORE_RNG_PHILOX_HPP__
|
||||
|
||||
+121
-89
@@ -16,6 +16,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "core/parallel.h"
|
||||
#include "core/rng.hpp"
|
||||
|
||||
namespace sd {
|
||||
@@ -59,6 +60,15 @@ namespace sd {
|
||||
return numel;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
inline void tensor_for_each(int64_t count, F&& fn, int64_t grain_size = 65536) {
|
||||
parallel_for(0, count, grain_size, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
fn(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class Tensor {
|
||||
public:
|
||||
@@ -230,7 +240,10 @@ namespace sd {
|
||||
}
|
||||
|
||||
void fill_(const T& value) {
|
||||
std::fill(data_.begin(), data_.end(), value);
|
||||
const T fill_value = value;
|
||||
parallel_for(0, numel(), 65536, [&](int64_t begin, int64_t end) {
|
||||
std::fill_n(data_.data() + begin, end - begin, fill_value);
|
||||
});
|
||||
}
|
||||
|
||||
Tensor& masked_fill_(const Tensor<uint8_t>& mask, const T& value);
|
||||
@@ -390,7 +403,7 @@ namespace sd {
|
||||
tensor_shape_to_string(lhs) + ", rhs_shape=" +
|
||||
tensor_shape_to_string(rhs));
|
||||
}
|
||||
shape[i] = std::max(lhs_dim, rhs_dim);
|
||||
shape[i] = lhs_dim == 1 ? rhs_dim : lhs_dim;
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
@@ -425,39 +438,55 @@ namespace sd {
|
||||
const std::vector<int64_t>& rhs_shape_raw,
|
||||
const std::vector<int64_t>& rhs_strides_raw,
|
||||
F&& fn) {
|
||||
const size_t ndim = out_shape.size();
|
||||
std::vector<int64_t> out_strides = tensor_compute_strides(out_shape);
|
||||
std::vector<int64_t> lhs_shape(ndim, 1);
|
||||
std::vector<int64_t> lhs_strides(ndim, 0);
|
||||
std::vector<int64_t> rhs_shape(ndim, 1);
|
||||
std::vector<int64_t> rhs_strides(ndim, 0);
|
||||
|
||||
for (size_t i = 0; i < lhs_shape_raw.size(); ++i) {
|
||||
lhs_shape[i] = lhs_shape_raw[i];
|
||||
lhs_strides[i] = lhs_strides_raw[i];
|
||||
}
|
||||
for (size_t i = 0; i < rhs_shape_raw.size(); ++i) {
|
||||
rhs_shape[i] = rhs_shape_raw[i];
|
||||
rhs_strides[i] = rhs_strides_raw[i];
|
||||
}
|
||||
|
||||
const int64_t numel = tensor_numel(out_shape);
|
||||
for (int64_t flat = 0; flat < numel; ++flat) {
|
||||
int64_t remaining = flat;
|
||||
int64_t lhs_offset = 0;
|
||||
int64_t rhs_offset = 0;
|
||||
for (size_t i = ndim; i-- > 0;) {
|
||||
int64_t coord = remaining / out_strides[i];
|
||||
remaining %= out_strides[i];
|
||||
if (lhs_shape[i] != 1) {
|
||||
lhs_offset += coord * lhs_strides[i];
|
||||
const int64_t numel = tensor_numel(out_shape);
|
||||
const size_t ndim = out_shape.size();
|
||||
auto broadcast_strides = [&](const std::vector<int64_t>& shape,
|
||||
const std::vector<int64_t>& strides) {
|
||||
if ((numel != 0 && tensor_numel(shape) == 0) || strides.size() != shape.size()) {
|
||||
tensor_throw_invalid_argument("Tensor broadcast requires non-empty inputs and matching strides");
|
||||
}
|
||||
std::vector<int64_t> result(ndim, 0);
|
||||
for (size_t i = 0; i < std::max(ndim, shape.size()); ++i) {
|
||||
const int64_t input_dim = i < shape.size() ? shape[i] : 1;
|
||||
const int64_t output_dim = i < ndim ? out_shape[i] : 1;
|
||||
if (input_dim != 1 && input_dim != output_dim) {
|
||||
tensor_throw_invalid_argument("Tensor broadcast cannot expand the destination: input_shape=" +
|
||||
tensor_shape_to_string(shape) + ", output_shape=" +
|
||||
tensor_shape_to_string(out_shape));
|
||||
}
|
||||
if (rhs_shape[i] != 1) {
|
||||
rhs_offset += coord * rhs_strides[i];
|
||||
if (i < ndim && input_dim != 1) {
|
||||
result[i] = strides[i];
|
||||
}
|
||||
}
|
||||
fn(flat, lhs_offset, rhs_offset);
|
||||
return result;
|
||||
};
|
||||
const auto lhs_strides = broadcast_strides(lhs_shape_raw, lhs_strides_raw);
|
||||
const auto rhs_strides = broadcast_strides(rhs_shape_raw, rhs_strides_raw);
|
||||
if (numel == 0) {
|
||||
return;
|
||||
}
|
||||
parallel_for(0, numel, 16384, [&](int64_t begin, int64_t end) {
|
||||
auto coord = tensor_unravel_index(begin, out_shape);
|
||||
int64_t lhs_offset = 0;
|
||||
int64_t rhs_offset = 0;
|
||||
for (size_t i = 0; i < ndim; ++i) {
|
||||
lhs_offset += coord[i] * lhs_strides[i];
|
||||
rhs_offset += coord[i] * rhs_strides[i];
|
||||
}
|
||||
for (int64_t flat = begin; flat < end; ++flat) {
|
||||
fn(flat, lhs_offset, rhs_offset);
|
||||
for (size_t i = 0; i < ndim; ++i) {
|
||||
lhs_offset += lhs_strides[i];
|
||||
rhs_offset += rhs_strides[i];
|
||||
if (++coord[i] < out_shape[i]) {
|
||||
break;
|
||||
}
|
||||
coord[i] = 0;
|
||||
lhs_offset -= out_shape[i] * lhs_strides[i];
|
||||
rhs_offset -= out_shape[i] * rhs_strides[i];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -469,6 +498,7 @@ namespace sd {
|
||||
const std::vector<int64_t> data_strides = tensor_compute_strides(shape_);
|
||||
const std::vector<int64_t> mask_strides = tensor_compute_strides(mask.shape());
|
||||
const uint8_t* mask_data = mask.data();
|
||||
const T fill_value = value;
|
||||
tensor_for_each_broadcast_offset(shape_,
|
||||
shape_,
|
||||
data_strides,
|
||||
@@ -476,7 +506,7 @@ namespace sd {
|
||||
mask_strides,
|
||||
[&](int64_t, int64_t data_offset, int64_t mask_offset) {
|
||||
if (mask_data[mask_offset] != 0) {
|
||||
data_[static_cast<size_t>(data_offset)] = value;
|
||||
data_[static_cast<size_t>(data_offset)] = fill_value;
|
||||
}
|
||||
});
|
||||
return *this;
|
||||
@@ -486,9 +516,9 @@ namespace sd {
|
||||
inline Tensor<uint8_t> operator<(const Tensor<T>& lhs, Scalar rhs) {
|
||||
Tensor<uint8_t> result(lhs.shape());
|
||||
const T value = static_cast<T>(rhs);
|
||||
for (int64_t i = 0; i < lhs.numel(); ++i) {
|
||||
result[i] = lhs[i] < value ? 1 : 0;
|
||||
}
|
||||
tensor_for_each(lhs.numel(), [&](int64_t i) {
|
||||
result.data()[i] = lhs.data()[i] < value ? 1 : 0;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -496,9 +526,9 @@ namespace sd {
|
||||
inline Tensor<uint8_t> operator<(Scalar lhs, const Tensor<T>& rhs) {
|
||||
Tensor<uint8_t> result(rhs.shape());
|
||||
const T value = static_cast<T>(lhs);
|
||||
for (int64_t i = 0; i < rhs.numel(); ++i) {
|
||||
result[i] = value < rhs[i] ? 1 : 0;
|
||||
}
|
||||
tensor_for_each(rhs.numel(), [&](int64_t i) {
|
||||
result.data()[i] = value < rhs.data()[i] ? 1 : 0;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -516,7 +546,7 @@ namespace sd {
|
||||
rhs.shape(),
|
||||
rhs_strides,
|
||||
[&](int64_t flat, int64_t lhs_offset, int64_t rhs_offset) {
|
||||
result[flat] = lhs_data[lhs_offset] < rhs_data[rhs_offset] ? 1 : 0;
|
||||
result.data()[flat] = lhs_data[lhs_offset] < rhs_data[rhs_offset] ? 1 : 0;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -524,9 +554,9 @@ namespace sd {
|
||||
template <typename T>
|
||||
inline Tensor<T>& operator+=(Tensor<T>& lhs, const Tensor<T>& rhs) {
|
||||
if (lhs.shape() == rhs.shape()) {
|
||||
for (int64_t i = 0; i < lhs.numel(); ++i) {
|
||||
lhs[i] += rhs[i];
|
||||
}
|
||||
tensor_for_each(lhs.numel(), [&](int64_t i) {
|
||||
lhs.data()[i] += rhs.data()[i];
|
||||
});
|
||||
return lhs;
|
||||
}
|
||||
tensor_broadcast_shape(lhs.shape(), rhs.shape());
|
||||
@@ -539,7 +569,7 @@ namespace sd {
|
||||
rhs.shape(),
|
||||
rhs_strides,
|
||||
[&](int64_t, int64_t lhs_offset, int64_t rhs_offset) {
|
||||
lhs[static_cast<int64_t>(lhs_offset)] += rhs_data[rhs_offset];
|
||||
lhs.data()[lhs_offset] += rhs_data[rhs_offset];
|
||||
});
|
||||
return lhs;
|
||||
}
|
||||
@@ -547,18 +577,18 @@ namespace sd {
|
||||
template <typename T, typename Scalar, typename = std::enable_if_t<std::is_arithmetic<Scalar>::value>>
|
||||
inline Tensor<T>& operator+=(Tensor<T>& lhs, Scalar rhs) {
|
||||
const T value = static_cast<T>(rhs);
|
||||
for (int64_t i = 0; i < lhs.numel(); ++i) {
|
||||
lhs[i] += value;
|
||||
}
|
||||
tensor_for_each(lhs.numel(), [&](int64_t i) {
|
||||
lhs.data()[i] += value;
|
||||
});
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Tensor<T>& operator-=(Tensor<T>& lhs, const Tensor<T>& rhs) {
|
||||
if (lhs.shape() == rhs.shape()) {
|
||||
for (int64_t i = 0; i < lhs.numel(); ++i) {
|
||||
lhs[i] -= rhs[i];
|
||||
}
|
||||
tensor_for_each(lhs.numel(), [&](int64_t i) {
|
||||
lhs.data()[i] -= rhs.data()[i];
|
||||
});
|
||||
return lhs;
|
||||
}
|
||||
tensor_broadcast_shape(lhs.shape(), rhs.shape());
|
||||
@@ -571,7 +601,7 @@ namespace sd {
|
||||
rhs.shape(),
|
||||
rhs_strides,
|
||||
[&](int64_t, int64_t lhs_offset, int64_t rhs_offset) {
|
||||
lhs[static_cast<int64_t>(lhs_offset)] -= rhs_data[rhs_offset];
|
||||
lhs.data()[lhs_offset] -= rhs_data[rhs_offset];
|
||||
});
|
||||
return lhs;
|
||||
}
|
||||
@@ -579,18 +609,18 @@ namespace sd {
|
||||
template <typename T, typename Scalar, typename = std::enable_if_t<std::is_arithmetic<Scalar>::value>>
|
||||
inline Tensor<T>& operator-=(Tensor<T>& lhs, Scalar rhs) {
|
||||
const T value = static_cast<T>(rhs);
|
||||
for (int64_t i = 0; i < lhs.numel(); ++i) {
|
||||
lhs[i] -= value;
|
||||
}
|
||||
tensor_for_each(lhs.numel(), [&](int64_t i) {
|
||||
lhs.data()[i] -= value;
|
||||
});
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Tensor<T>& operator*=(Tensor<T>& lhs, const Tensor<T>& rhs) {
|
||||
if (lhs.shape() == rhs.shape()) {
|
||||
for (int64_t i = 0; i < lhs.numel(); ++i) {
|
||||
lhs[i] *= rhs[i];
|
||||
}
|
||||
tensor_for_each(lhs.numel(), [&](int64_t i) {
|
||||
lhs.data()[i] *= rhs.data()[i];
|
||||
});
|
||||
return lhs;
|
||||
}
|
||||
tensor_broadcast_shape(lhs.shape(), rhs.shape());
|
||||
@@ -603,7 +633,7 @@ namespace sd {
|
||||
rhs.shape(),
|
||||
rhs_strides,
|
||||
[&](int64_t, int64_t lhs_offset, int64_t rhs_offset) {
|
||||
lhs[static_cast<int64_t>(lhs_offset)] *= rhs_data[rhs_offset];
|
||||
lhs.data()[lhs_offset] *= rhs_data[rhs_offset];
|
||||
});
|
||||
return lhs;
|
||||
}
|
||||
@@ -611,18 +641,18 @@ namespace sd {
|
||||
template <typename T, typename Scalar, typename = std::enable_if_t<std::is_arithmetic<Scalar>::value>>
|
||||
inline Tensor<T>& operator*=(Tensor<T>& lhs, Scalar rhs) {
|
||||
const T value = static_cast<T>(rhs);
|
||||
for (int64_t i = 0; i < lhs.numel(); ++i) {
|
||||
lhs[i] *= value;
|
||||
}
|
||||
tensor_for_each(lhs.numel(), [&](int64_t i) {
|
||||
lhs.data()[i] *= value;
|
||||
});
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Tensor<T>& operator/=(Tensor<T>& lhs, const Tensor<T>& rhs) {
|
||||
if (lhs.shape() == rhs.shape()) {
|
||||
for (int64_t i = 0; i < lhs.numel(); ++i) {
|
||||
lhs[i] /= rhs[i];
|
||||
}
|
||||
tensor_for_each(lhs.numel(), [&](int64_t i) {
|
||||
lhs.data()[i] /= rhs.data()[i];
|
||||
});
|
||||
return lhs;
|
||||
}
|
||||
tensor_broadcast_shape(lhs.shape(), rhs.shape());
|
||||
@@ -635,7 +665,7 @@ namespace sd {
|
||||
rhs.shape(),
|
||||
rhs_strides,
|
||||
[&](int64_t, int64_t lhs_offset, int64_t rhs_offset) {
|
||||
lhs[static_cast<int64_t>(lhs_offset)] /= rhs_data[rhs_offset];
|
||||
lhs.data()[lhs_offset] /= rhs_data[rhs_offset];
|
||||
});
|
||||
return lhs;
|
||||
}
|
||||
@@ -643,9 +673,9 @@ namespace sd {
|
||||
template <typename T, typename Scalar, typename = std::enable_if_t<std::is_arithmetic<Scalar>::value>>
|
||||
inline Tensor<T>& operator/=(Tensor<T>& lhs, Scalar rhs) {
|
||||
const T value = static_cast<T>(rhs);
|
||||
for (int64_t i = 0; i < lhs.numel(); ++i) {
|
||||
lhs[i] /= value;
|
||||
}
|
||||
tensor_for_each(lhs.numel(), [&](int64_t i) {
|
||||
lhs.data()[i] /= value;
|
||||
});
|
||||
return lhs;
|
||||
}
|
||||
|
||||
@@ -664,7 +694,7 @@ namespace sd {
|
||||
rhs.shape(),
|
||||
rhs_strides,
|
||||
[&](int64_t flat, int64_t lhs_offset, int64_t rhs_offset) {
|
||||
result[flat] = lhs_data[lhs_offset] + rhs_data[rhs_offset];
|
||||
result.data()[flat] = lhs_data[lhs_offset] + rhs_data[rhs_offset];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -699,7 +729,7 @@ namespace sd {
|
||||
rhs.shape(),
|
||||
rhs_strides,
|
||||
[&](int64_t flat, int64_t lhs_offset, int64_t rhs_offset) {
|
||||
result[flat] = lhs_data[lhs_offset] - rhs_data[rhs_offset];
|
||||
result.data()[flat] = lhs_data[lhs_offset] - rhs_data[rhs_offset];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -717,9 +747,9 @@ namespace sd {
|
||||
inline Tensor<T> operator-(Scalar lhs, const Tensor<T>& rhs) {
|
||||
Tensor<T> result = rhs;
|
||||
const T value = static_cast<T>(lhs);
|
||||
for (int64_t i = 0; i < result.numel(); ++i) {
|
||||
result[i] = value - result[i];
|
||||
}
|
||||
tensor_for_each(result.numel(), [&](int64_t i) {
|
||||
result.data()[i] = value - result.data()[i];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -738,7 +768,7 @@ namespace sd {
|
||||
rhs.shape(),
|
||||
rhs_strides,
|
||||
[&](int64_t flat, int64_t lhs_offset, int64_t rhs_offset) {
|
||||
result[flat] = lhs_data[lhs_offset] * rhs_data[rhs_offset];
|
||||
result.data()[flat] = lhs_data[lhs_offset] * rhs_data[rhs_offset];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -773,7 +803,7 @@ namespace sd {
|
||||
rhs.shape(),
|
||||
rhs_strides,
|
||||
[&](int64_t flat, int64_t lhs_offset, int64_t rhs_offset) {
|
||||
result[flat] = lhs_data[lhs_offset] / rhs_data[rhs_offset];
|
||||
result.data()[flat] = lhs_data[lhs_offset] / rhs_data[rhs_offset];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -791,18 +821,18 @@ namespace sd {
|
||||
inline Tensor<T> operator/(Scalar lhs, const Tensor<T>& rhs) {
|
||||
Tensor<T> result = rhs;
|
||||
const T value = static_cast<T>(lhs);
|
||||
for (int64_t i = 0; i < result.numel(); ++i) {
|
||||
result[i] = value / result[i];
|
||||
}
|
||||
tensor_for_each(result.numel(), [&](int64_t i) {
|
||||
result.data()[i] = value / result.data()[i];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Tensor<T> operator-(const Tensor<T>& tensor) {
|
||||
Tensor<T> result = tensor;
|
||||
for (int64_t i = 0; i < result.numel(); ++i) {
|
||||
result[i] = -result[i];
|
||||
}
|
||||
tensor_for_each(result.numel(), [&](int64_t i) {
|
||||
result.data()[i] = -result.data()[i];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1067,9 +1097,11 @@ namespace sd {
|
||||
template <typename T>
|
||||
inline Tensor<T> exp(const Tensor<T>& input) {
|
||||
Tensor<T> output(input.shape());
|
||||
for (int64_t i = 0; i < input.numel(); ++i) {
|
||||
output[i] = static_cast<T>(std::exp(static_cast<double>(input[i])));
|
||||
}
|
||||
tensor_for_each(
|
||||
input.numel(), [&](int64_t i) {
|
||||
output.data()[i] = static_cast<T>(std::exp(static_cast<double>(input.data()[i])));
|
||||
},
|
||||
4096);
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -1079,18 +1111,18 @@ namespace sd {
|
||||
tensor_throw_invalid_argument("Tensor clamp requires min_value <= max_value");
|
||||
}
|
||||
Tensor<T> output(input.shape());
|
||||
for (int64_t i = 0; i < input.numel(); ++i) {
|
||||
output[i] = std::clamp(input[i], min_value, max_value);
|
||||
}
|
||||
tensor_for_each(input.numel(), [&](int64_t i) {
|
||||
output.data()[i] = std::clamp(input.data()[i], min_value, max_value);
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Tensor<T> round(const Tensor<T>& input) {
|
||||
Tensor<T> output(input.shape());
|
||||
for (int64_t i = 0; i < input.numel(); ++i) {
|
||||
output[i] = static_cast<T>(std::round(static_cast<double>(input[i])));
|
||||
}
|
||||
tensor_for_each(input.numel(), [&](int64_t i) {
|
||||
output.data()[i] = static_cast<T>(std::round(static_cast<double>(input.data()[i])));
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
+81
-30
@@ -62,17 +62,34 @@ void replace_all_chars(std::string& str, char target, char replacement) {
|
||||
}
|
||||
}
|
||||
|
||||
static std::string sd_vformat(const char* fmt, va_list ap) {
|
||||
char small[128];
|
||||
va_list ap2;
|
||||
va_copy(ap2, ap);
|
||||
int size = vsnprintf(small, sizeof small, fmt, ap);
|
||||
if (size < 0) {
|
||||
va_end(ap2);
|
||||
return {};
|
||||
}
|
||||
size_t needed = (size_t)size;
|
||||
if (needed < sizeof small) {
|
||||
va_end(ap2);
|
||||
return std::string(small, needed);
|
||||
}
|
||||
std::string out(needed, '\0');
|
||||
int size2 = vsnprintf(out.data(), needed + 1, fmt, ap2);
|
||||
va_end(ap2);
|
||||
if (size2 < 0)
|
||||
out.clear();
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string sd_format(const char* fmt, ...) {
|
||||
va_list ap;
|
||||
va_list ap2;
|
||||
va_start(ap, fmt);
|
||||
va_copy(ap2, ap);
|
||||
int size = vsnprintf(nullptr, 0, fmt, ap);
|
||||
std::vector<char> buf(size + 1);
|
||||
int size2 = vsnprintf(buf.data(), size + 1, fmt, ap2);
|
||||
va_end(ap2);
|
||||
std::string result = sd_vformat(fmt, ap);
|
||||
va_end(ap);
|
||||
return std::string(buf.data(), size);
|
||||
return result;
|
||||
}
|
||||
|
||||
int round_up_to(int value, int base) {
|
||||
@@ -414,14 +431,43 @@ std::vector<std::string> split_string(const std::string& str, char delimiter) {
|
||||
}
|
||||
|
||||
ggml_type sd_type_to_ggml_type(sd_type_t sdtype) {
|
||||
if (sdtype == SD_TYPE_F8_E4M3 || sdtype == SD_TYPE_F8_E5M2) {
|
||||
#ifndef SD_USE_UPSTREAM_GGML
|
||||
return sdtype == SD_TYPE_F8_E4M3 ? GGML_TYPE_F8_E4M3 : GGML_TYPE_F8_E5M2;
|
||||
#else
|
||||
return GGML_TYPE_COUNT;
|
||||
#endif
|
||||
}
|
||||
const int type_value = static_cast<int>(sdtype);
|
||||
if (type_value < std::min<int>(SD_TYPE_COUNT, GGML_TYPE_COUNT)) {
|
||||
if (type_value >= 0 && type_value < std::min<int>(SD_TYPE_COUNT, GGML_TYPE_COUNT)) {
|
||||
return static_cast<ggml_type>(type_value);
|
||||
} else {
|
||||
return GGML_TYPE_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
bool validate_tensor_types(sd_type_t type, const char* tensor_type_rules) {
|
||||
if (type != SD_TYPE_COUNT && sd_type_to_ggml_type(type) == GGML_TYPE_COUNT) {
|
||||
LOG_ERROR("weight type %s is not supported by this ggml build", sd_type_name(type));
|
||||
return false;
|
||||
}
|
||||
#ifdef SD_USE_UPSTREAM_GGML
|
||||
for (const auto& rule : split_string(SAFE_STR(tensor_type_rules), ',')) {
|
||||
const auto pos = rule.find('=');
|
||||
if (pos != std::string::npos) {
|
||||
const auto name = rule.substr(pos + 1);
|
||||
if (name == "f8_e4m3" || name == "f8_e5m2") {
|
||||
LOG_ERROR("FP8 is not supported by this ggml build (tensor type rule '%s')", rule.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
GGML_UNUSED(tensor_type_rules);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
KeyValueArgs parse_key_value_args(const char* args, const char* context) {
|
||||
KeyValueArgs pairs;
|
||||
|
||||
@@ -595,47 +641,45 @@ std::string trim(const std::string& s) {
|
||||
static sd_log_cb_t sd_log_cb = nullptr;
|
||||
void* sd_log_cb_data = nullptr;
|
||||
|
||||
#define LOG_BUFFER_SIZE 4096
|
||||
static void sd_log_dispatch(sd_log_level_t level, const std::string& origin, const std::string& text) {
|
||||
if (sd_log_cb == nullptr)
|
||||
return;
|
||||
std::string message = origin + " - " + text;
|
||||
if (message.back() != '\n') {
|
||||
message += '\n';
|
||||
}
|
||||
sd_log_cb(level, message.c_str(), sd_log_cb_data);
|
||||
}
|
||||
|
||||
void log_printf(sd_log_level_t level, const char* file, int line, const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
static char log_buffer[LOG_BUFFER_SIZE + 1];
|
||||
int written = snprintf(log_buffer, LOG_BUFFER_SIZE, "%s:%-4d - ", sd_basename(file).c_str(), line);
|
||||
|
||||
if (written >= 0 && written < LOG_BUFFER_SIZE) {
|
||||
vsnprintf(log_buffer + written, LOG_BUFFER_SIZE - written, format, args);
|
||||
}
|
||||
size_t len = strlen(log_buffer);
|
||||
if (log_buffer[len - 1] != '\n') {
|
||||
strncat(log_buffer, "\n", LOG_BUFFER_SIZE - len);
|
||||
}
|
||||
|
||||
if (sd_log_cb) {
|
||||
sd_log_cb(level, log_buffer, sd_log_cb_data);
|
||||
}
|
||||
|
||||
std::string message = sd_vformat(format, args);
|
||||
va_end(args);
|
||||
std::string origin = sd_format("%s:%-4d", sd_basename(file).c_str(), line);
|
||||
sd_log_dispatch(level, origin, message);
|
||||
}
|
||||
|
||||
void sd_ggml_log_callback(ggml_log_level level, const char* text, void*) {
|
||||
sd_log_level_t sd_level = SD_LOG_VERBOSE;
|
||||
switch (level) {
|
||||
case GGML_LOG_LEVEL_DEBUG:
|
||||
LOG_VERBOSE(text);
|
||||
sd_level = SD_LOG_VERBOSE;
|
||||
break;
|
||||
case GGML_LOG_LEVEL_INFO:
|
||||
LOG_INFO(text);
|
||||
sd_level = SD_LOG_INFO;
|
||||
break;
|
||||
case GGML_LOG_LEVEL_WARN:
|
||||
LOG_WARN(text);
|
||||
sd_level = SD_LOG_WARN;
|
||||
break;
|
||||
case GGML_LOG_LEVEL_ERROR:
|
||||
LOG_ERROR(text);
|
||||
sd_level = SD_LOG_ERROR;
|
||||
break;
|
||||
default:
|
||||
LOG_VERBOSE(text);
|
||||
sd_level = SD_LOG_VERBOSE;
|
||||
break;
|
||||
}
|
||||
sd_log_dispatch(sd_level, "ggml", text);
|
||||
}
|
||||
|
||||
void sd_set_log_callback(sd_log_cb_t cb, void* data) {
|
||||
@@ -755,6 +799,13 @@ sd::Tensor<float> clip_preprocess(const sd::Tensor<float>& image, int target_wid
|
||||
int64_t resized_width = static_cast<int64_t>(scale * static_cast<float>(image.shape()[0]));
|
||||
int64_t resized_height = static_cast<int64_t>(scale * static_cast<float>(image.shape()[1]));
|
||||
|
||||
// The resized image must cover the crop window. Floating-point rounding can
|
||||
// leave a side one pixel short of the crop target (e.g. 730 -> 735.999...
|
||||
// -> 735 after truncation), so clamp to keep the center crop in bounds.
|
||||
// Truncation is otherwise preserved to avoid changing existing results.
|
||||
resized_width = std::max<int64_t>(resized_width, target_width);
|
||||
resized_height = std::max<int64_t>(resized_height, target_height);
|
||||
|
||||
sd::Tensor<float> resized = sd::ops::interpolate(
|
||||
image,
|
||||
{resized_width, resized_height, image.shape()[2], image.shape()[3]});
|
||||
|
||||
@@ -90,6 +90,7 @@ void log_printf(sd_log_level_t level, const char* file, int line, const char* fo
|
||||
void sd_ggml_log_callback(ggml_log_level level, const char* text, void*);
|
||||
|
||||
ggml_type sd_type_to_ggml_type(sd_type_t sdtype);
|
||||
bool validate_tensor_types(sd_type_t type, const char* tensor_type_rules);
|
||||
|
||||
std::string trim(const std::string& s);
|
||||
|
||||
|
||||
+2
-1
@@ -39,6 +39,7 @@ enum SDVersion {
|
||||
VERSION_LINGBOT_VIDEO,
|
||||
VERSION_QWEN_IMAGE,
|
||||
VERSION_QWEN_IMAGE_LAYERED,
|
||||
VERSION_QWEN_IMAGE_2_1,
|
||||
VERSION_HUNYUAN_VIDEO,
|
||||
VERSION_ANIMA,
|
||||
VERSION_FLUX2,
|
||||
@@ -145,7 +146,7 @@ static inline bool sd_version_is_lingbot_video(SDVersion version) {
|
||||
}
|
||||
|
||||
static inline bool sd_version_is_qwen_image(SDVersion version) {
|
||||
if (version == VERSION_QWEN_IMAGE || version == VERSION_QWEN_IMAGE_LAYERED) {
|
||||
if (version == VERSION_QWEN_IMAGE || version == VERSION_QWEN_IMAGE_LAYERED || version == VERSION_QWEN_IMAGE_2_1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -67,7 +67,7 @@ struct LoraModel : public GGMLRunner {
|
||||
std::map<std::string, ggml_tensor*> scalars;
|
||||
std::set<std::string> scalar_names;
|
||||
for (const auto& [name, source] : sources) {
|
||||
if (is_unused_tensor(name) || (filter && !filter(name)))
|
||||
if (filter && !filter(name))
|
||||
continue;
|
||||
const bool scalar = source.nelements() == 1 && (ends_with(name, ".alpha") || ends_with(name, ".scale"));
|
||||
auto* tensor = ggml_new_tensor(params_ctx, scalar ? GGML_TYPE_F32 : source.type, source.n_dims, source.ne);
|
||||
|
||||
@@ -208,6 +208,7 @@ public:
|
||||
ggml_tensor* w = params["weight"];
|
||||
const float scale = ctx->linear_scale > 0.f ? ctx->linear_scale : this->scale;
|
||||
ggml_tensor* weight_scale = has_weight_scale ? params["weight_scale"] : nullptr;
|
||||
#ifndef SD_USE_UPSTREAM_GGML
|
||||
if (w->type == GGML_TYPE_F8_E4M3 || w->type == GGML_TYPE_F8_E5M2) {
|
||||
bool supports_fp8_matmul = false;
|
||||
if (ctx->backend != nullptr) {
|
||||
@@ -221,6 +222,7 @@ public:
|
||||
w = ggml_cast(ctx->ggml_ctx, w, GGML_TYPE_BF16);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
ggml_tensor* b = nullptr;
|
||||
if (bias) {
|
||||
b = params["bias"];
|
||||
@@ -238,6 +240,7 @@ public:
|
||||
if (ctx->weight_adapter && b != nullptr) {
|
||||
b = ctx->weight_adapter->patch_weight(ctx->ggml_ctx, ctx->backend, b, prefix + "bias");
|
||||
}
|
||||
#ifndef SD_USE_UPSTREAM_GGML
|
||||
if (int8_convrot && scale == 1.f) {
|
||||
const auto cache_key = std::make_pair(x, int8_convrot_group_size);
|
||||
auto cached = ctx->int8_convrot_cache.find(cache_key);
|
||||
@@ -248,6 +251,7 @@ public:
|
||||
x = cached->second;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
out = ggml_ext_linear_i8_tensorwise(ctx->ggml_ctx,
|
||||
x,
|
||||
w,
|
||||
@@ -728,7 +732,7 @@ public:
|
||||
std::get<2>(stride), std::get<1>(stride), std::get<0>(stride),
|
||||
std::get<2>(padding), std::get<1>(padding), std::get<0>(padding),
|
||||
std::get<2>(dilation), std::get<1>(dilation), std::get<0>(dilation),
|
||||
force_prec_f32);
|
||||
force_prec_f32, ctx->conv3d_direct_enabled);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -66,6 +66,10 @@ struct AnimaDiffusionExtra {
|
||||
const sd::Tensor<float>* t5_weights = nullptr;
|
||||
};
|
||||
|
||||
struct QwenImage21DiffusionExtra {
|
||||
const sd::Tensor<int32_t>* image_slots = nullptr;
|
||||
};
|
||||
|
||||
struct WanDiffusionExtra {
|
||||
const sd::Tensor<float>* vace_context = nullptr;
|
||||
float vace_strength = 1.f;
|
||||
@@ -132,6 +136,7 @@ using DiffusionExtraParams = std::variant<std::monostate,
|
||||
SkipLayerDiffusionExtra,
|
||||
FluxDiffusionExtra,
|
||||
AnimaDiffusionExtra,
|
||||
QwenImage21DiffusionExtra,
|
||||
WanDiffusionExtra,
|
||||
HiDreamO1DiffusionExtra,
|
||||
LTXAVDiffusionExtra,
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
#ifndef __SD_MODEL_DIFFUSION_QWEN_IMAGE_2_1_H__
|
||||
#define __SD_MODEL_DIFFUSION_QWEN_IMAGE_2_1_H__
|
||||
|
||||
#include "model/diffusion/qwen_image.hpp"
|
||||
|
||||
namespace Qwen {
|
||||
|
||||
struct QwenImage21Config {
|
||||
int64_t in_channels = 64;
|
||||
int64_t out_channels = 64;
|
||||
int64_t hidden_size = 4096;
|
||||
int64_t context_dim = 4096;
|
||||
int64_t head_dim = 128;
|
||||
int64_t intermediate_size = 12288;
|
||||
int num_layers = 32;
|
||||
bool fused_mlp = false;
|
||||
std::vector<int> axes_dim = {16, 56, 56};
|
||||
|
||||
static QwenImage21Config detect_from_weights(const String2TensorStorage& weights, const std::string& prefix) {
|
||||
QwenImage21Config config;
|
||||
auto find = [&](const std::string& suffix) -> const TensorStorage* {
|
||||
auto it = weights.find(prefix + "." + suffix);
|
||||
return it == weights.end() ? nullptr : &it->second;
|
||||
};
|
||||
if (auto w = find("img_in.weight")) {
|
||||
config.in_channels = w->ne[0];
|
||||
config.hidden_size = w->ne[1];
|
||||
}
|
||||
if (auto w = find("proj_out.weight")) {
|
||||
config.out_channels = w->ne[1];
|
||||
}
|
||||
if (auto w = find("txt_in.in_layer.weight")) {
|
||||
config.context_dim = w->ne[0];
|
||||
}
|
||||
if (auto w = find("transformer_blocks.0.attn.norm_q.weight")) {
|
||||
config.head_dim = w->ne[0];
|
||||
}
|
||||
if (auto w = find("transformer_blocks.0.img_mlp.gate_up.weight")) {
|
||||
config.intermediate_size = w->ne[1] / 2;
|
||||
config.fused_mlp = true;
|
||||
} else if (auto w = find("transformer_blocks.0.img_mlp.proj.weight")) {
|
||||
config.intermediate_size = w->ne[1];
|
||||
}
|
||||
int layers = 0;
|
||||
const std::string block_prefix = prefix + ".transformer_blocks.";
|
||||
for (const auto& [name, _] : weights) {
|
||||
if (starts_with(name, block_prefix)) {
|
||||
layers = std::max(layers, atoi(name.substr(block_prefix.size()).c_str()) + 1);
|
||||
}
|
||||
}
|
||||
if (layers > 0) {
|
||||
config.num_layers = layers;
|
||||
LOG_VERBOSE("qwen_image_2_1: layers = %d, hidden_size = %" PRId64 ", context_dim = %" PRId64,
|
||||
layers, config.hidden_size, config.context_dim);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
struct QwenImage21Segment {
|
||||
int64_t start;
|
||||
int64_t end;
|
||||
int64_t context_start;
|
||||
int image_index;
|
||||
};
|
||||
|
||||
struct QwenImage21Layout {
|
||||
std::vector<QwenImage21Segment> segments;
|
||||
std::vector<std::vector<float>> positions;
|
||||
int64_t prefix_length = 0;
|
||||
|
||||
static QwenImage21Layout build(int64_t text_length,
|
||||
const sd::Tensor<int32_t>& image_slots,
|
||||
const std::vector<std::pair<int64_t, int64_t>>& image_shapes) {
|
||||
if (image_shapes.empty() || (!image_slots.empty() && image_slots.numel() != text_length)) {
|
||||
throw std::runtime_error("Qwen Image 2.1: invalid image token layout");
|
||||
}
|
||||
QwenImage21Layout layout;
|
||||
int64_t position = 0;
|
||||
int next_image = 0;
|
||||
auto append_image = [&](int index, int64_t context_start) {
|
||||
auto [height, width] = image_shapes[index];
|
||||
int64_t start = static_cast<int64_t>(layout.positions.size());
|
||||
layout.segments.push_back({start, start + height * width, context_start, index});
|
||||
for (int64_t h = 0; h < height; ++h) {
|
||||
for (int64_t w = 0; w < width; ++w) {
|
||||
layout.positions.push_back({static_cast<float>(position),
|
||||
static_cast<float>(h - (height - height / 2)),
|
||||
static_cast<float>(w - (width - width / 2))});
|
||||
}
|
||||
}
|
||||
position += std::max(height, width);
|
||||
};
|
||||
for (int64_t i = 0; i < text_length;) {
|
||||
int tag = image_slots.empty() ? 0 : image_slots[i];
|
||||
int64_t begin = i++;
|
||||
while (i < text_length && (image_slots.empty() ? 0 : image_slots[i]) == tag) {
|
||||
++i;
|
||||
}
|
||||
if (tag != 0) {
|
||||
if (tag != next_image + 1 || next_image + 1 >= static_cast<int>(image_shapes.size()) ||
|
||||
(i - begin) * 4 != image_shapes[next_image].first * image_shapes[next_image].second) {
|
||||
throw std::runtime_error("Qwen Image 2.1: vision slots and reference latents must have matching sizes");
|
||||
}
|
||||
append_image(next_image++, begin);
|
||||
} else {
|
||||
int64_t start = static_cast<int64_t>(layout.positions.size());
|
||||
layout.segments.push_back({start, start + i - begin, begin, -1});
|
||||
for (int64_t j = begin; j < i; ++j, ++position) {
|
||||
float p = static_cast<float>(position);
|
||||
layout.positions.push_back({p, p, p});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next_image + 1 != static_cast<int>(image_shapes.size())) {
|
||||
throw std::runtime_error("Qwen Image 2.1: missing reference image slots");
|
||||
}
|
||||
layout.prefix_length = static_cast<int64_t>(layout.positions.size());
|
||||
append_image(next_image, text_length);
|
||||
return layout;
|
||||
}
|
||||
};
|
||||
|
||||
class QwenImage21ZeroCenterRMSNorm : public RMSNorm {
|
||||
public:
|
||||
using RMSNorm::RMSNorm;
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
auto weight = params["weight"];
|
||||
if (ctx->weight_adapter) {
|
||||
weight = ctx->weight_adapter->patch_weight(ctx->ggml_ctx, ctx->backend, weight, prefix + "weight");
|
||||
}
|
||||
weight = ggml_scale_bias(ctx->ggml_ctx, weight, 1.f, 1.f);
|
||||
return ggml_mul(ctx->ggml_ctx, ggml_rms_norm(ctx->ggml_ctx, x, eps), weight);
|
||||
}
|
||||
};
|
||||
|
||||
class QwenImage21TextProjection : public GGMLBlock {
|
||||
public:
|
||||
QwenImage21TextProjection(const QwenImage21Config& config) {
|
||||
blocks["text_norm"] = std::make_shared<QwenImage21ZeroCenterRMSNorm>(config.context_dim, 1e-6f);
|
||||
blocks["in_layer"] = std::make_shared<Linear>(config.context_dim, config.hidden_size, false);
|
||||
blocks["out_layer"] = std::make_shared<Linear>(config.hidden_size, config.hidden_size, false);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) {
|
||||
x = std::dynamic_pointer_cast<QwenImage21ZeroCenterRMSNorm>(blocks["text_norm"])->forward(ctx, x);
|
||||
x = std::dynamic_pointer_cast<Linear>(blocks["in_layer"])->forward(ctx, x);
|
||||
x = ggml_ext_gelu(ctx->ggml_ctx, x);
|
||||
return std::dynamic_pointer_cast<Linear>(blocks["out_layer"])->forward(ctx, x);
|
||||
}
|
||||
};
|
||||
|
||||
class QwenImage21Attention : public QwenImageAttention {
|
||||
public:
|
||||
QwenImage21Attention(const QwenImage21Config& config)
|
||||
: QwenImageAttention(config.hidden_size, config.head_dim, config.hidden_size / config.head_dim, 0, 0, false, false) {
|
||||
for (const auto* name : {"add_q_proj", "add_k_proj", "add_v_proj", "norm_added_q", "norm_added_k", "to_add_out"}) {
|
||||
blocks.erase(name);
|
||||
}
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* pe, const std::vector<QwenImage21Segment>& segments, const std::vector<ggml_tensor*>& masks) {
|
||||
int64_t heads = x->ne[0] / dim_head;
|
||||
auto project = [&](const char* name) {
|
||||
auto h = std::dynamic_pointer_cast<Linear>(blocks[name])->forward(ctx, x);
|
||||
return ggml_reshape_4d(ctx->ggml_ctx, h, dim_head, heads, x->ne[1], x->ne[2]);
|
||||
};
|
||||
auto q = project("to_q");
|
||||
auto k = project("to_k");
|
||||
auto v = project("to_v");
|
||||
q = std::dynamic_pointer_cast<RMSNorm>(blocks["norm_q"])->forward(ctx, q);
|
||||
k = std::dynamic_pointer_cast<RMSNorm>(blocks["norm_k"])->forward(ctx, k);
|
||||
q = Rope::apply_rope(ctx->ggml_ctx, q, pe);
|
||||
k = Rope::apply_rope(ctx->ggml_ctx, k, pe);
|
||||
ggml_tensor* result = nullptr;
|
||||
for (size_t i = 0; i < segments.size(); ++i) {
|
||||
const auto& segment = segments[i];
|
||||
auto sq = ggml_ext_slice(ctx->ggml_ctx, q, 1, segment.start, segment.end);
|
||||
auto sk = ggml_ext_slice(ctx->ggml_ctx, k, 1, 0, segment.end);
|
||||
auto sv = ggml_ext_slice(ctx->ggml_ctx, v, 2, 0, segment.end);
|
||||
auto out = ggml_ext_attention_ext(ctx, sq, sk, sv, heads, masks[i], true, ctx->flash_attn_enabled);
|
||||
result = result == nullptr ? out : ggml_concat(ctx->ggml_ctx, result, out, 1);
|
||||
}
|
||||
auto to_out = std::dynamic_pointer_cast<Linear>(blocks["to_out.0"]);
|
||||
if (sd_backend_is(ctx->backend, "Vulkan") || sd_backend_is(ctx->backend, "ROCm")) {
|
||||
to_out->set_force_prec_f32(true);
|
||||
}
|
||||
return to_out->forward(ctx, result);
|
||||
}
|
||||
};
|
||||
|
||||
class QwenImage21TransformerBlock : public GGMLBlock {
|
||||
public:
|
||||
QwenImage21TransformerBlock(const QwenImage21Config& config) {
|
||||
blocks["img_norm1"] = std::make_shared<LayerNorm>(config.hidden_size, 1e-6f, false);
|
||||
blocks["img_norm2"] = std::make_shared<LayerNorm>(config.hidden_size, 1e-6f, false);
|
||||
blocks["attn"] = std::make_shared<QwenImage21Attention>(config);
|
||||
if (config.fused_mlp) {
|
||||
blocks["img_mlp.gate_up"] = std::make_shared<Linear>(config.hidden_size, 2 * config.intermediate_size, false);
|
||||
} else {
|
||||
blocks["img_mlp.proj"] = std::make_shared<Linear>(config.hidden_size, config.intermediate_size, false);
|
||||
blocks["img_mlp.gate_layer"] = std::make_shared<Linear>(config.hidden_size, config.intermediate_size, false);
|
||||
}
|
||||
blocks["img_mlp.out"] = std::make_shared<Linear>(config.intermediate_size, config.hidden_size, false);
|
||||
}
|
||||
|
||||
static ggml_tensor* modulate(ggml_context* ctx, ggml_tensor* x, ggml_tensor* params, int64_t prefix_length, bool gate = false) {
|
||||
auto rows = ggml_ext_chunk(ctx, params, 2, 1);
|
||||
auto apply = [&](ggml_tensor* part, ggml_tensor* row) {
|
||||
row = gate ? ggml_tanh(ctx, row) : ggml_scale_bias(ctx, row, 1.f, 1.f);
|
||||
return ggml_mul(ctx, part, row);
|
||||
};
|
||||
auto target = apply(ggml_ext_slice(ctx, x, 1, prefix_length, x->ne[1]), rows[0]);
|
||||
if (prefix_length == 0) {
|
||||
return target;
|
||||
}
|
||||
auto prefix = apply(ggml_ext_slice(ctx, x, 1, 0, prefix_length), rows[1]);
|
||||
return ggml_concat(ctx, prefix, target, 1);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, const std::vector<ggml_tensor*>& modulation, ggml_tensor* pe, const QwenImage21Layout& layout, const std::vector<ggml_tensor*>& masks) {
|
||||
auto h = std::dynamic_pointer_cast<LayerNorm>(blocks["img_norm1"])->forward(ctx, x);
|
||||
h = modulate(ctx->ggml_ctx, h, modulation[0], layout.prefix_length);
|
||||
h = std::dynamic_pointer_cast<QwenImage21Attention>(blocks["attn"])->forward(ctx, h, pe, layout.segments, masks);
|
||||
x = ggml_add(ctx->ggml_ctx, x, modulate(ctx->ggml_ctx, h, modulation[1], layout.prefix_length, true));
|
||||
h = std::dynamic_pointer_cast<LayerNorm>(blocks["img_norm2"])->forward(ctx, x);
|
||||
h = modulate(ctx->ggml_ctx, h, modulation[2], layout.prefix_length);
|
||||
ggml_tensor* gate;
|
||||
auto fused = blocks.find("img_mlp.gate_up");
|
||||
if (fused != blocks.end()) {
|
||||
auto gate_up = std::dynamic_pointer_cast<Linear>(fused->second)->forward(ctx, h);
|
||||
auto parts = ggml_ext_chunk(ctx->ggml_ctx, gate_up, 2, 0);
|
||||
gate = parts[0];
|
||||
h = parts[1];
|
||||
} else {
|
||||
gate = std::dynamic_pointer_cast<Linear>(blocks["img_mlp.gate_layer"])->forward(ctx, h);
|
||||
h = std::dynamic_pointer_cast<Linear>(blocks["img_mlp.proj"])->forward(ctx, h);
|
||||
}
|
||||
h = ggml_mul(ctx->ggml_ctx, h, ggml_silu(ctx->ggml_ctx, gate));
|
||||
h = std::dynamic_pointer_cast<Linear>(blocks["img_mlp.out"])->forward(ctx, h);
|
||||
return ggml_add(ctx->ggml_ctx, x, modulate(ctx->ggml_ctx, h, modulation[3], layout.prefix_length, true));
|
||||
}
|
||||
};
|
||||
|
||||
class QwenImage21Model : public GGMLBlock {
|
||||
QwenImage21Config config;
|
||||
|
||||
public:
|
||||
QwenImage21Model(const QwenImage21Config& config)
|
||||
: config(config) {
|
||||
blocks["time_text_embed.timestep_embedder"] = std::make_shared<TimestepEmbedding>(256, config.hidden_size, 0, 0, false);
|
||||
blocks["txt_in"] = std::make_shared<QwenImage21TextProjection>(config);
|
||||
blocks["img_in"] = std::make_shared<Linear>(config.in_channels, config.hidden_size, false);
|
||||
blocks["modulation.1"] = std::make_shared<Linear>(config.hidden_size, 4 * config.hidden_size, false);
|
||||
blocks["norm_out.linear"] = std::make_shared<Linear>(config.hidden_size, config.hidden_size, false);
|
||||
blocks["norm_out.norm"] = std::make_shared<LayerNorm>(config.hidden_size, 1e-6f, false);
|
||||
blocks["proj_out"] = std::make_shared<Linear>(config.hidden_size, config.out_channels, false);
|
||||
for (int i = 0; i < config.num_layers; ++i) {
|
||||
blocks["transformer_blocks." + std::to_string(i)] = std::make_shared<QwenImage21TransformerBlock>(config);
|
||||
}
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* timestep, ggml_tensor* context, const std::vector<ggml_tensor*>& refs, ggml_tensor* pe, const QwenImage21Layout& layout, const std::vector<ggml_tensor*>& masks) {
|
||||
auto time = ggml_concat(ctx->ggml_ctx, timestep, ggml_ext_zeros_like(ctx->ggml_ctx, timestep), 0);
|
||||
// Runtime flow timesteps already use the [0, 1000] scale.
|
||||
time = ggml_ext_timestep_embedding(ctx->ggml_ctx, time, 256, 10000, 1.f);
|
||||
time = std::dynamic_pointer_cast<TimestepEmbedding>(blocks["time_text_embed.timestep_embedder"])->forward(ctx, time);
|
||||
time = ggml_silu(ctx->ggml_ctx, time);
|
||||
auto modulation = std::dynamic_pointer_cast<Linear>(blocks["modulation.1"])->forward(ctx, time);
|
||||
auto mod = ggml_ext_chunk(ctx->ggml_ctx, modulation, 4, 0);
|
||||
auto text = std::dynamic_pointer_cast<QwenImage21TextProjection>(blocks["txt_in"])->forward(ctx, context);
|
||||
auto img_in = std::dynamic_pointer_cast<Linear>(blocks["img_in"]);
|
||||
ggml_tensor* joint = nullptr;
|
||||
for (const auto& segment : layout.segments) {
|
||||
ggml_tensor* h;
|
||||
if (segment.image_index < 0) {
|
||||
h = ggml_ext_slice(ctx->ggml_ctx, text, 1, segment.context_start,
|
||||
segment.context_start + segment.end - segment.start);
|
||||
} else {
|
||||
auto image = segment.image_index == static_cast<int>(refs.size()) ? x : refs[segment.image_index];
|
||||
h = img_in->forward(ctx, DiT::patchify(ctx->ggml_ctx, image, 1, 1));
|
||||
}
|
||||
joint = joint == nullptr ? h : ggml_concat(ctx->ggml_ctx, joint, h, 1);
|
||||
}
|
||||
sd::ggml_graph_cut::mark_graph_cut(joint, "qwen_image_2_1.prelude", "joint");
|
||||
for (int i = 0; i < config.num_layers; ++i) {
|
||||
auto block = std::dynamic_pointer_cast<QwenImage21TransformerBlock>(blocks["transformer_blocks." + std::to_string(i)]);
|
||||
joint = block->forward(ctx, joint, mod, pe, layout, masks);
|
||||
sd::ggml_graph_cut::mark_graph_cut(joint, "qwen_image_2_1.transformer_blocks." + std::to_string(i), "joint");
|
||||
}
|
||||
joint = ggml_ext_slice(ctx->ggml_ctx, joint, 1, layout.prefix_length, joint->ne[1]);
|
||||
auto scale = std::dynamic_pointer_cast<Linear>(blocks["norm_out.linear"])->forward(ctx, ggml_ext_chunk(ctx->ggml_ctx, time, 2, 1)[0]);
|
||||
joint = std::dynamic_pointer_cast<LayerNorm>(blocks["norm_out.norm"])->forward(ctx, joint);
|
||||
joint = ggml_mul(ctx->ggml_ctx, joint, ggml_scale_bias(ctx->ggml_ctx, scale, 1.f, 1.f));
|
||||
joint = std::dynamic_pointer_cast<Linear>(blocks["proj_out"])->forward(ctx, joint);
|
||||
return DiT::unpatchify_and_crop(ctx->ggml_ctx, joint, x->ne[1], x->ne[0], 1, 1);
|
||||
}
|
||||
};
|
||||
|
||||
struct QwenImage21Runner : public DiffusionModelRunner {
|
||||
QwenImage21Config config;
|
||||
QwenImage21Model model;
|
||||
std::vector<float> pe_data;
|
||||
std::vector<sd::Tensor<float>> mask_data;
|
||||
|
||||
QwenImage21Runner(ggml_backend_t backend, const String2TensorStorage& weights, const std::string& prefix, std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
: DiffusionModelRunner(backend, prefix, weight_manager),
|
||||
config(QwenImage21Config::detect_from_weights(weights, prefix)),
|
||||
model(config) {
|
||||
model.init(params_ctx, weights, prefix);
|
||||
}
|
||||
|
||||
std::string get_desc() override { return "qwen_image_2_1"; }
|
||||
|
||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors, const std::string& prefix) override {
|
||||
model.get_param_tensors(tensors, prefix);
|
||||
}
|
||||
|
||||
sd::Tensor<float> compute(int n_threads, const DiffusionParams& inputs) override {
|
||||
const auto& x = tensor_or_empty(inputs.x);
|
||||
const auto& context = tensor_or_empty(inputs.context);
|
||||
if (x.empty() || context.empty() || context.dim() < 2 || context.shape()[0] != config.context_dim ||
|
||||
tensor_or_empty(inputs.timesteps).numel() != 1 ||
|
||||
x.dim() != 4 || x.shape()[3] != 1 || x.shape()[2] != config.in_channels) {
|
||||
LOG_ERROR("Qwen Image 2.1 requires an image latent and text conditioning with batch size 1");
|
||||
return {};
|
||||
}
|
||||
static const std::vector<sd::Tensor<float>> empty_refs;
|
||||
const auto& refs = inputs.ref_latents && inputs.ref_image_params.pass_to_dit ? *inputs.ref_latents : empty_refs;
|
||||
std::vector<std::pair<int64_t, int64_t>> shapes;
|
||||
for (const auto& ref : refs) {
|
||||
if (ref.dim() != 4 || ref.shape()[2] != config.in_channels || ref.shape()[3] != 1) {
|
||||
LOG_ERROR("Qwen Image 2.1: invalid reference latent shape");
|
||||
return {};
|
||||
}
|
||||
shapes.emplace_back(ref.shape()[1], ref.shape()[0]);
|
||||
}
|
||||
shapes.emplace_back(x.shape()[1], x.shape()[0]);
|
||||
const auto* extra = std::get_if<QwenImage21DiffusionExtra>(&inputs.extra);
|
||||
QwenImage21Layout layout;
|
||||
try {
|
||||
layout = QwenImage21Layout::build(context.shape()[1], tensor_or_empty(extra ? extra->image_slots : nullptr), shapes);
|
||||
} catch (const std::exception& error) {
|
||||
LOG_ERROR("%s", error.what());
|
||||
return {};
|
||||
}
|
||||
pe_data = Rope::embed_nd(layout.positions, 1, 10000.f, config.axes_dim);
|
||||
mask_data.clear();
|
||||
for (const auto& segment : layout.segments) {
|
||||
sd::Tensor<float> mask;
|
||||
if (segment.image_index < 0) {
|
||||
mask = sd::Tensor<float>::zeros({segment.end, segment.end - segment.start});
|
||||
for (int64_t q = segment.start; q < segment.end; ++q) {
|
||||
for (int64_t k = q + 1; k < segment.end; ++k) {
|
||||
mask[k + segment.end * (q - segment.start)] = -INFINITY;
|
||||
}
|
||||
}
|
||||
}
|
||||
mask_data.push_back(std::move(mask));
|
||||
}
|
||||
auto build = [&]() {
|
||||
auto graph = new_graph_custom(QWEN_IMAGE_GRAPH_SIZE * 2);
|
||||
auto pe = ggml_new_tensor_4d(compute_ctx, GGML_TYPE_F32, 2, 2, config.head_dim / 2, layout.positions.size());
|
||||
set_backend_tensor_data(pe, pe_data.data());
|
||||
std::vector<ggml_tensor*> masks, ref_inputs;
|
||||
for (const auto& mask : mask_data) {
|
||||
masks.push_back(mask.empty() ? nullptr : make_input(mask));
|
||||
}
|
||||
for (const auto& ref : refs) {
|
||||
ref_inputs.push_back(make_input(ref));
|
||||
}
|
||||
auto ctx = get_context();
|
||||
auto out = model.forward(&ctx, make_input(x), make_input(*inputs.timesteps), make_input(context),
|
||||
ref_inputs, pe, layout, masks);
|
||||
ggml_build_forward_expand(graph, out);
|
||||
return graph;
|
||||
};
|
||||
return restore_trailing_singleton_dims(GGMLRunner::compute(build, n_threads, false), x.dim());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif // __SD_MODEL_DIFFUSION_QWEN_IMAGE_2_1_H__
|
||||
+22
-8
@@ -15,6 +15,7 @@
|
||||
#include <regex>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -31,9 +32,9 @@
|
||||
#include "model_manager.h"
|
||||
#include "tokenizers/bpe_tokenizer.h"
|
||||
#include "tokenizers/gemma_tokenizer.h"
|
||||
#include "tokenizers/gpt_oss_tokenizer.h"
|
||||
#include "tokenizers/mistral_tokenizer.h"
|
||||
#include "tokenizers/qwen2_tokenizer.h"
|
||||
#include "tokenizers/tokenizer_config.h"
|
||||
|
||||
namespace LLM {
|
||||
constexpr int LLM_GRAPH_SIZE = 65536;
|
||||
@@ -1376,7 +1377,7 @@ namespace LLM {
|
||||
x = ggml_ext_cont(ctx->ggml_ctx, kqv);
|
||||
x = ggml_reshape_3d(ctx->ggml_ctx, x, head_dim * num_heads, n_token, N);
|
||||
} else {
|
||||
x = ggml_ext_attention_ext(ctx, q, k, v, num_heads, attention_mask, true, false); // [N, n_token, hidden_size]
|
||||
x = ggml_ext_attention_ext(ctx, q, k, v, num_heads, attention_mask, true, ctx->flash_attn_enabled); // [N, n_token, hidden_size]
|
||||
}
|
||||
|
||||
x = out_proj->forward(ctx, x); // [N, n_token, hidden_size]
|
||||
@@ -2351,7 +2352,7 @@ namespace LLM {
|
||||
};
|
||||
|
||||
struct LLMEmbedder {
|
||||
std::shared_ptr<BPETokenizer> tokenizer;
|
||||
std::shared_ptr<Tokenizer> tokenizer;
|
||||
LLMRunner model;
|
||||
|
||||
LLMEmbedder(LLMArch arch,
|
||||
@@ -2359,14 +2360,27 @@ namespace LLM {
|
||||
const String2TensorStorage& tensor_storage_map = {},
|
||||
const std::string prefix = "",
|
||||
bool enable_vision = false,
|
||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr,
|
||||
const TokenizerConfig& tokenizers = {})
|
||||
: model(arch, backend, tensor_storage_map, prefix, enable_vision, weight_manager) {
|
||||
int pad_id = 151643;
|
||||
if (arch == LLMArch::MISTRAL_SMALL_3_2 || arch == LLMArch::MINISTRAL_3_3B) {
|
||||
tokenizer = std::make_shared<MistralTokenizer>();
|
||||
pad_id = 11;
|
||||
} else if (arch == LLMArch::GPT_OSS_20B) {
|
||||
tokenizer = std::make_shared<GPTOSSTokenizer>();
|
||||
} else {
|
||||
tokenizer = std::make_shared<Qwen2Tokenizer>();
|
||||
pad_id = 199999;
|
||||
} else if (arch == LLMArch::GEMMA2_2B) {
|
||||
pad_id = 0;
|
||||
}
|
||||
tokenizer = tokenizers.create(TokenizerConfig::MAIN, model.config.vocab_size, pad_id);
|
||||
if (!tokenizer) {
|
||||
if (arch == LLMArch::GPT_OSS_20B || arch == LLMArch::GEMMA2_2B) {
|
||||
throw std::runtime_error("GPT-OSS and Gemma 2 require an external tokenizer.json in the main tokenizer slot");
|
||||
}
|
||||
if (arch == LLMArch::MISTRAL_SMALL_3_2 || arch == LLMArch::MINISTRAL_3_3B) {
|
||||
tokenizer = std::make_shared<MistralTokenizer>();
|
||||
} else {
|
||||
tokenizer = std::make_shared<Qwen2Tokenizer>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ public:
|
||||
int scale_factor = 8;
|
||||
if (version == VERSION_LTXAV) {
|
||||
scale_factor = 32;
|
||||
} else if (version == VERSION_WAN2_2_TI2V || sd_version_is_hunyuan_video(version) || sd_version_is_mage_flow(version) || sd_version_is_minimax_h3(version)) {
|
||||
} else if (version == VERSION_WAN2_2_TI2V || version == VERSION_QWEN_IMAGE_2_1 || sd_version_is_hunyuan_video(version) || sd_version_is_mage_flow(version) || sd_version_is_minimax_h3(version)) {
|
||||
scale_factor = 16;
|
||||
} else if (sd_version_uses_flux2_vae(version)) {
|
||||
scale_factor = 16;
|
||||
|
||||
+64
-20
@@ -26,6 +26,13 @@ namespace WAN {
|
||||
bool bias;
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map = {}, const std::string prefix = "") override {
|
||||
auto weight = tensor_storage_map.find(prefix + "weight");
|
||||
if (weight != tensor_storage_map.end() && weight->second.ne[2] == 1 &&
|
||||
weight->second.ne[3] == in_channels * out_channels) {
|
||||
// Image VAE exports may retain Conv3d weights with a singleton temporal kernel.
|
||||
std::get<0>(kernel_size) = 1;
|
||||
std::get<0>(padding) = 0;
|
||||
}
|
||||
params["weight"] = ggml_new_tensor_4d(ctx,
|
||||
GGML_TYPE_F16,
|
||||
std::get<2>(kernel_size),
|
||||
@@ -78,7 +85,8 @@ namespace WAN {
|
||||
return ggml_ext_conv_3d(ctx->ggml_ctx, ctx->backend, x, w, b, in_channels,
|
||||
std::get<2>(stride), std::get<1>(stride), std::get<0>(stride),
|
||||
0, 0, 0,
|
||||
std::get<2>(dilation), std::get<1>(dilation), std::get<0>(dilation));
|
||||
std::get<2>(dilation), std::get<1>(dilation), std::get<0>(dilation),
|
||||
false, ctx->conv3d_direct_enabled);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,7 +147,7 @@ namespace WAN {
|
||||
std::string mode;
|
||||
|
||||
public:
|
||||
Resample(int64_t dim, const std::string& mode, bool wan2_2 = false)
|
||||
Resample(int64_t dim, const std::string& mode, bool wan2_2 = false, bool is_2D = false)
|
||||
: dim(dim), mode(mode) {
|
||||
if (mode == "upsample2d") {
|
||||
if (wan2_2) {
|
||||
@@ -153,12 +161,20 @@ namespace WAN {
|
||||
} else {
|
||||
blocks["resample.1"] = std::shared_ptr<GGMLBlock>(new Conv2d(dim, dim / 2, {3, 3}, {1, 1}, {1, 1}));
|
||||
}
|
||||
blocks["time_conv"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(dim, dim * 2, {3, 1, 1}, {1, 1, 1}, {1, 0, 0}));
|
||||
if (is_2D) {
|
||||
blocks["time_conv"] = std::make_shared<Conv2dBut3d>(dim, dim * 2, std::pair<int, int>{1, 1});
|
||||
} else {
|
||||
blocks["time_conv"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(dim, dim * 2, {3, 1, 1}, {1, 1, 1}, {1, 0, 0}));
|
||||
}
|
||||
} else if (mode == "downsample2d") {
|
||||
blocks["resample.1"] = std::shared_ptr<GGMLBlock>(new Conv2d(dim, dim, {3, 3}, {2, 2}));
|
||||
} else if (mode == "downsample3d") {
|
||||
blocks["resample.1"] = std::shared_ptr<GGMLBlock>(new Conv2d(dim, dim, {3, 3}, {2, 2}));
|
||||
blocks["time_conv"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(dim, dim, {3, 1, 1}, {2, 1, 1}, {0, 0, 0}));
|
||||
if (is_2D) {
|
||||
blocks["time_conv"] = std::make_shared<Conv2dBut3d>(dim, dim, std::pair<int, int>{1, 1});
|
||||
} else {
|
||||
blocks["time_conv"] = std::shared_ptr<GGMLBlock>(new CausalConv3d(dim, dim, {3, 1, 1}, {2, 1, 1}, {0, 0, 0}));
|
||||
}
|
||||
} else if (mode == "none") {
|
||||
// nn.Identity()
|
||||
} else {
|
||||
@@ -468,7 +484,7 @@ namespace WAN {
|
||||
}
|
||||
if (down_flag) {
|
||||
std::string mode = temperal_downsample ? "downsample3d" : "downsample2d";
|
||||
blocks["downsamples." + std::to_string(i)] = std::shared_ptr<GGMLBlock>(new Resample(out_dim, mode, true));
|
||||
blocks["downsamples." + std::to_string(i)] = std::shared_ptr<GGMLBlock>(new Resample(out_dim, mode, true, is_2D));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
@@ -531,7 +547,7 @@ namespace WAN {
|
||||
}
|
||||
if (up_flag) {
|
||||
std::string mode = temperal_upsample ? "upsample3d" : "upsample2d";
|
||||
blocks["upsamples." + std::to_string(i)] = std::shared_ptr<GGMLBlock>(new Resample(out_dim, mode, true));
|
||||
blocks["upsamples." + std::to_string(i)] = std::shared_ptr<GGMLBlock>(new Resample(out_dim, mode, true, is_2D));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
@@ -1053,9 +1069,24 @@ namespace WAN {
|
||||
input_channels = 4;
|
||||
}
|
||||
|
||||
if (version == VERSION_QWEN_IMAGE_2_1) {
|
||||
wan2_2 = true;
|
||||
dec_dim = 144;
|
||||
z_dim = 64;
|
||||
input_channels = 4;
|
||||
dim_mult = {1, 2, 4, 8, 8};
|
||||
}
|
||||
|
||||
if (is_2D) {
|
||||
temperal_upsample = {false, false, false};
|
||||
temperal_downsample = {false, false, false};
|
||||
temperal_upsample.assign(dim_mult.size() - 1, false);
|
||||
temperal_downsample.assign(dim_mult.size() - 1, false);
|
||||
}
|
||||
if (version == VERSION_QWEN_IMAGE_2_1) {
|
||||
// Temporal shortcut factors still affect single-frame channel grouping.
|
||||
temperal_upsample = {true, true, true, false};
|
||||
temperal_downsample = {false, true, true, true};
|
||||
_conv_num = 2 * (2 + static_cast<int>(dim_mult.size()) * (num_res_blocks + 1)) + 3 + (is_2D ? 0 : 2);
|
||||
_enc_conv_num = 2 * (2 + static_cast<int>(dim_mult.size()) * num_res_blocks) + 3 + (is_2D ? 0 : 2);
|
||||
}
|
||||
|
||||
if (!decode_only) {
|
||||
@@ -1270,18 +1301,9 @@ namespace WAN {
|
||||
SDVersion version = VERSION_WAN2,
|
||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
: 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_VERBOSE("USING 2D VAE");
|
||||
}
|
||||
const auto conv_in = tensor_storage_map.find((prefix.empty() ? "" : prefix + ".") + "decoder.conv1.weight");
|
||||
const bool is_2D = conv_in != tensor_storage_map.end() && conv_in->second.ne[2] > 3;
|
||||
LOG_VERBOSE("Wan VAE convolution type: %s", is_2D ? "2D" : "3D");
|
||||
ae = WanVAE(decode_only, version, is_2D);
|
||||
ae.init(params_ctx, tensor_storage_map, prefix);
|
||||
}
|
||||
@@ -1341,6 +1363,28 @@ namespace WAN {
|
||||
std_tensor.reshape_(stats_shape);
|
||||
return {std::move(mean_tensor), std::move(std_tensor)};
|
||||
}
|
||||
if (version == VERSION_QWEN_IMAGE_2_1 && latents.shape()[channel_dim] == 64) {
|
||||
stats_shape[static_cast<size_t>(channel_dim)] = 64;
|
||||
auto mean_tensor = sd::Tensor<float>::from_vector({0.5126f, 0.7721f, -0.0631f, 1.3506f, -0.7855f, -2.1025f, -0.3458f, 1.3722f,
|
||||
1.8873f, -1.7177f, -0.6510f, 0.2732f, 0.7562f, -0.6163f, -1.0277f, 3.8363f,
|
||||
2.0210f, 0.0472f, 0.9320f, 2.0087f, 2.4954f, -0.1391f, -1.4249f, 1.8464f,
|
||||
-0.5236f, 1.2826f, 3.7046f, -1.3035f, 2.7286f, -1.4518f, -1.9036f, -1.9955f,
|
||||
-0.0342f, -1.0265f, -0.7636f, 3.0555f, 0.0746f, -3.0751f, -0.1076f, 1.7376f,
|
||||
-1.0914f, -1.9435f, -0.2784f, -1.3680f, 0.4809f, -0.4433f, 0.3764f, 0.5729f,
|
||||
-2.0595f, 1.0960f, -1.3260f, -2.0211f, -5.0179f, 0.5275f, 4.0162f, 1.8505f,
|
||||
0.3026f, 1.9373f, 1.4937f, 0.2632f, 0.5547f, -1.7121f, -0.1562f, 0.0304f});
|
||||
auto std_tensor = sd::Tensor<float>::from_vector({3.2001f, 3.2936f, 3.4321f, 3.0091f, 3.1061f, 4.0379f, 4.0705f, 3.7910f,
|
||||
3.0785f, 3.6500f, 3.9308f, 3.0904f, 2.8778f, 3.7675f, 3.7320f, 5.0756f,
|
||||
3.2864f, 4.0397f, 3.1317f, 4.0443f, 2.9249f, 3.9454f, 3.0988f, 4.2489f,
|
||||
3.4896f, 3.8513f, 3.9323f, 3.4719f, 3.7498f, 4.2830f, 3.5694f, 4.2467f,
|
||||
3.9037f, 3.2947f, 5.0770f, 3.5075f, 3.2700f, 3.4767f, 2.8063f, 5.1125f,
|
||||
3.5327f, 4.7833f, 3.1286f, 4.1819f, 3.8527f, 3.8312f, 3.5605f, 4.3875f,
|
||||
3.9624f, 4.0168f, 3.5643f, 4.0550f, 5.5614f, 4.2963f, 4.4080f, 3.4959f,
|
||||
3.8747f, 3.7608f, 3.5735f, 3.1490f, 3.7662f, 3.6746f, 3.4563f, 3.8161f});
|
||||
mean_tensor.reshape_(stats_shape);
|
||||
std_tensor.reshape_(stats_shape);
|
||||
return {std::move(mean_tensor), std::move(std_tensor)};
|
||||
}
|
||||
GGML_ABORT("unexpected latent channel dimension %lld for version %d",
|
||||
(long long)latents.shape()[channel_dim],
|
||||
version);
|
||||
|
||||
@@ -57,6 +57,18 @@ bool read_gguf_file(const std::string& file_path,
|
||||
|
||||
size_t data_offset = gguf_reader.data_offset();
|
||||
for (const auto& gguf_tensor_info : gguf_reader.tensors()) {
|
||||
#ifdef SD_USE_UPSTREAM_GGML
|
||||
if (static_cast<int>(gguf_tensor_info.type) == SD_TYPE_F8_E4M3 ||
|
||||
static_cast<int>(gguf_tensor_info.type) == SD_TYPE_F8_E5M2) {
|
||||
set_error(error, "FP8 is not supported by this ggml build (tensor '" + gguf_tensor_info.name + "')");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (static_cast<unsigned>(gguf_tensor_info.type) >= GGML_TYPE_COUNT ||
|
||||
ggml_get_type_traits(gguf_tensor_info.type)->type_size == 0) {
|
||||
set_error(error, "unsupported GGUF tensor type (tensor '" + gguf_tensor_info.name + "')");
|
||||
return false;
|
||||
}
|
||||
TensorStorage tensor_storage(
|
||||
gguf_tensor_info.name,
|
||||
gguf_tensor_info.type,
|
||||
|
||||
@@ -86,10 +86,17 @@ static ggml_type safetensors_dtype_to_ggml_type(const std::string& dtype) {
|
||||
ttype = GGML_TYPE_F32;
|
||||
} else if (dtype == "F64") {
|
||||
ttype = GGML_TYPE_F32;
|
||||
#ifdef SD_USE_UPSTREAM_GGML
|
||||
} else if (dtype == "F8_E4M3") {
|
||||
ttype = GGML_TYPE_F16;
|
||||
} else if (dtype == "F8_E5M2") {
|
||||
ttype = GGML_TYPE_F16;
|
||||
#else
|
||||
} else if (dtype == "F8_E4M3") {
|
||||
ttype = GGML_TYPE_F8_E4M3;
|
||||
} else if (dtype == "F8_E5M2") {
|
||||
ttype = GGML_TYPE_F8_E5M2;
|
||||
#endif
|
||||
} else if (dtype == "I32") {
|
||||
ttype = GGML_TYPE_I32;
|
||||
} else if (dtype == "I64") {
|
||||
@@ -230,6 +237,12 @@ bool read_safetensors_file(const std::string& file_path,
|
||||
if (!read_comfy_quant_config(file, file_path, name, data_start + begin, end - begin, config, error)) {
|
||||
return false;
|
||||
}
|
||||
#ifdef SD_USE_UPSTREAM_GGML
|
||||
if (config.format == "int8_tensorwise") {
|
||||
set_error(error, "INT8 tensorwise/convrot is not supported by this ggml build (tensor '" + name + "')");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
const std::string module_name = name.substr(0, name.size() - std::string(".comfy_quant").size());
|
||||
comfy_quant_configs.emplace(module_name, std::move(config));
|
||||
}
|
||||
@@ -362,10 +375,20 @@ bool read_safetensors_file(const std::string& file_path,
|
||||
bool tensor_size_ok;
|
||||
if (dtype == "F8_E4M3") {
|
||||
tensor_storage.is_f8_e4m3 = true;
|
||||
tensor_size_ok = (tensor_storage.nbytes() == tensor_data_size);
|
||||
#ifdef SD_USE_UPSTREAM_GGML
|
||||
// f8 -> f16
|
||||
tensor_size_ok = (tensor_storage.nbytes() == tensor_data_size * 2);
|
||||
#else
|
||||
tensor_size_ok = (tensor_storage.nbytes() == tensor_data_size);
|
||||
#endif
|
||||
} else if (dtype == "F8_E5M2") {
|
||||
tensor_storage.is_f8_e5m2 = true;
|
||||
tensor_size_ok = (tensor_storage.nbytes() == tensor_data_size);
|
||||
#ifdef SD_USE_UPSTREAM_GGML
|
||||
// f8 -> f16
|
||||
tensor_size_ok = (tensor_storage.nbytes() == tensor_data_size * 2);
|
||||
#else
|
||||
tensor_size_ok = (tensor_storage.nbytes() == tensor_data_size);
|
||||
#endif
|
||||
} else if (dtype == "F64") {
|
||||
tensor_storage.is_f64 = true;
|
||||
// f64 -> f32
|
||||
|
||||
@@ -58,6 +58,10 @@ struct TensorStorage {
|
||||
int64_t nbytes_to_read() const {
|
||||
if (is_f64 || is_i64) {
|
||||
return nbytes() * 2;
|
||||
#ifdef SD_USE_UPSTREAM_GGML
|
||||
} else if (is_f8_e4m3 || is_f8_e5m2) {
|
||||
return nbytes() / 2;
|
||||
#endif
|
||||
} else {
|
||||
return nbytes();
|
||||
}
|
||||
|
||||
+70
-74
@@ -35,53 +35,66 @@
|
||||
|
||||
/*================================================= Preprocess ==================================================*/
|
||||
|
||||
const char* unused_tensors[] = {
|
||||
"betas",
|
||||
"alphas_cumprod_prev",
|
||||
"sqrt_alphas_cumprod",
|
||||
"sqrt_one_minus_alphas_cumprod",
|
||||
"log_one_minus_alphas_cumprod",
|
||||
"sqrt_recip_alphas_cumprod",
|
||||
"sqrt_recipm1_alphas_cumprod",
|
||||
"posterior_variance",
|
||||
"posterior_log_variance_clipped",
|
||||
"posterior_mean_coef1",
|
||||
"posterior_mean_coef2",
|
||||
"cond_stage_model.transformer.text_model.embeddings.position_ids",
|
||||
"cond_stage_model.1.model.text_model.embeddings.position_ids",
|
||||
"cond_stage_model.transformer.vision_model.embeddings.position_ids",
|
||||
"cond_stage_model.model.logit_scale",
|
||||
"conditioner.embedders.0.transformer.text_model.embeddings.position_ids",
|
||||
"conditioner.embedders.0.model.logit_scale",
|
||||
"conditioner.embedders.1.model.logit_scale",
|
||||
"model.diffusion_model.time_embedding.cond_proj.weight",
|
||||
"unet.time_embedding.cond_proj.weight",
|
||||
"model_ema.decay",
|
||||
"model_ema.num_updates",
|
||||
"model_ema.diffusion_model",
|
||||
"embedding_manager",
|
||||
"denoiser.sigmas",
|
||||
"text_encoders.t5xxl.transformer.encoder.embed_tokens.weight", // only used during training
|
||||
"ztsnr", // Found in some SDXL vpred models
|
||||
"edm_vpred.sigma_min", // Found in CosXL
|
||||
// TODO: find another way to avoid the "unknown tensor" for these two
|
||||
// "edm_vpred.sigma_max", // Used to detect CosXL
|
||||
// "v_pred", // Used to detect SDXL vpred models
|
||||
"text_encoders.llm.output.weight",
|
||||
"text_encoders.llm.lm_head.",
|
||||
"language_model.lm_head.",
|
||||
"vision_model.",
|
||||
};
|
||||
|
||||
bool is_unused_tensor(const std::string& name) {
|
||||
for (size_t i = 0; i < sizeof(unused_tensors) / sizeof(const char*); i++) {
|
||||
if (starts_with(name, unused_tensors[i])) {
|
||||
return true;
|
||||
}
|
||||
#ifdef SD_USE_UPSTREAM_GGML
|
||||
uint16_t f8_e4m3_to_f16(uint8_t f8) {
|
||||
const uint32_t exponent_bias = 7;
|
||||
if (f8 == 0xff) {
|
||||
return ggml_fp32_to_fp16(-NAN);
|
||||
} else if (f8 == 0x7f) {
|
||||
return ggml_fp32_to_fp16(NAN);
|
||||
}
|
||||
return false;
|
||||
|
||||
uint32_t sign = f8 & 0x80;
|
||||
uint32_t exponent = (f8 & 0x78) >> 3;
|
||||
uint32_t mantissa = f8 & 0x07;
|
||||
uint32_t result = sign << 24;
|
||||
if (exponent == 0) {
|
||||
if (mantissa > 0) {
|
||||
exponent = 0x7f - exponent_bias;
|
||||
|
||||
// yes, 2 times
|
||||
if ((mantissa & 0x04) == 0) {
|
||||
mantissa &= 0x03;
|
||||
mantissa <<= 1;
|
||||
exponent -= 1;
|
||||
}
|
||||
if ((mantissa & 0x04) == 0) {
|
||||
mantissa &= 0x03;
|
||||
mantissa <<= 1;
|
||||
exponent -= 1;
|
||||
}
|
||||
|
||||
result |= (mantissa & 0x03) << 21;
|
||||
result |= exponent << 23;
|
||||
}
|
||||
} else {
|
||||
result |= mantissa << 20;
|
||||
exponent += 0x7f - exponent_bias;
|
||||
result |= exponent << 23;
|
||||
}
|
||||
|
||||
return ggml_fp32_to_fp16(*reinterpret_cast<const float*>(&result));
|
||||
}
|
||||
|
||||
uint16_t f8_e5m2_to_f16(uint8_t fp8) {
|
||||
return static_cast<uint16_t>(fp8) << 8;
|
||||
}
|
||||
|
||||
void f8_e4m3_to_f16_vec(uint8_t* src, uint16_t* dst, int64_t n) {
|
||||
// support inplace op
|
||||
for (int64_t i = n - 1; i >= 0; i--) {
|
||||
dst[i] = f8_e4m3_to_f16(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void f8_e5m2_to_f16_vec(uint8_t* src, uint16_t* dst, int64_t n) {
|
||||
// support inplace op
|
||||
for (int64_t i = n - 1; i >= 0; i--) {
|
||||
dst[i] = f8_e5m2_to_f16(src[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void f64_to_f32_vec(double* src, float* dst, int64_t n) {
|
||||
// support inplace op
|
||||
for (int64_t i = 0; i < n; i++) {
|
||||
@@ -285,10 +298,6 @@ bool ModelLoader::init_from_safetensors_file(const std::string& file_path, const
|
||||
size_t file_index = add_file_path(file_path);
|
||||
|
||||
for (auto& tensor_storage : tensor_storages) {
|
||||
if (is_unused_tensor(tensor_storage.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!starts_with(tensor_storage.name, prefix)) {
|
||||
tensor_storage.name = prefix + tensor_storage.name;
|
||||
}
|
||||
@@ -357,10 +366,6 @@ bool ModelLoader::init_from_torch_legacy_file(const std::string& file_path, cons
|
||||
size_t file_index = add_file_path(file_path);
|
||||
|
||||
for (auto& tensor_storage : tensor_storages) {
|
||||
if (is_unused_tensor(tensor_storage.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!starts_with(tensor_storage.name, prefix)) {
|
||||
tensor_storage.name = prefix + tensor_storage.name;
|
||||
}
|
||||
@@ -478,6 +483,9 @@ SDVersion ModelLoader::get_sd_version() const {
|
||||
if (tensor_storage.name.find("language_model.model.layers.0.self_attn.q_proj_mot_gen.weight") != std::string::npos) {
|
||||
return VERSION_SENSENOVA_U1_5;
|
||||
}
|
||||
if (tensor_storage.name == "model.diffusion_model.txt_in.text_norm.weight") {
|
||||
return VERSION_QWEN_IMAGE_2_1;
|
||||
}
|
||||
if (tensor_storage.name.find("model.diffusion_model.transformer_blocks.0.img_mod.1.weight") != std::string::npos) {
|
||||
auto img_in = tensor_storage_map.find("model.diffusion_model.img_in.weight");
|
||||
if (img_in != tensor_storage_map.end() && img_in->second.ne[0] == 128) {
|
||||
@@ -675,10 +683,6 @@ SDVersion ModelLoader::get_sd_version() const {
|
||||
std::map<ggml_type, uint32_t> ModelLoader::get_wtype_stat() const {
|
||||
std::map<ggml_type, uint32_t> wtype_stat;
|
||||
for (auto& [name, tensor_storage] : tensor_storage_map) {
|
||||
if (is_unused_tensor(tensor_storage.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto iter = wtype_stat.find(tensor_storage.type);
|
||||
if (iter != wtype_stat.end()) {
|
||||
iter->second++;
|
||||
@@ -692,10 +696,6 @@ std::map<ggml_type, uint32_t> ModelLoader::get_wtype_stat() const {
|
||||
std::map<ggml_type, uint32_t> ModelLoader::get_conditioner_wtype_stat() const {
|
||||
std::map<ggml_type, uint32_t> wtype_stat;
|
||||
for (auto& [name, tensor_storage] : tensor_storage_map) {
|
||||
if (is_unused_tensor(tensor_storage.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tensor_storage.name.find("text_encoders") == std::string::npos &&
|
||||
tensor_storage.name.find("cond_stage_model") == std::string::npos &&
|
||||
tensor_storage.name.find("te.text_model.") == std::string::npos &&
|
||||
@@ -716,10 +716,6 @@ std::map<ggml_type, uint32_t> ModelLoader::get_conditioner_wtype_stat() const {
|
||||
std::map<ggml_type, uint32_t> ModelLoader::get_diffusion_model_wtype_stat() const {
|
||||
std::map<ggml_type, uint32_t> wtype_stat;
|
||||
for (auto& [name, tensor_storage] : tensor_storage_map) {
|
||||
if (is_unused_tensor(tensor_storage.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tensor_storage.name.find("model.diffusion_model.") == std::string::npos && tensor_storage.name.find("unet.") == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
@@ -737,10 +733,6 @@ std::map<ggml_type, uint32_t> ModelLoader::get_diffusion_model_wtype_stat() cons
|
||||
std::map<ggml_type, uint32_t> ModelLoader::get_vae_wtype_stat() const {
|
||||
std::map<ggml_type, uint32_t> wtype_stat;
|
||||
for (auto& [name, tensor_storage] : tensor_storage_map) {
|
||||
if (is_unused_tensor(tensor_storage.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tensor_storage.name.find("vae.") == std::string::npos &&
|
||||
tensor_storage.name.find("first_stage_model") == std::string::npos) {
|
||||
continue;
|
||||
@@ -824,9 +816,6 @@ void ModelLoader::process_model_files(bool enable_mmap, bool writable_mmap) {
|
||||
|
||||
std::vector<TensorStorage> processed_tensor_storages;
|
||||
for (const auto& [name, tensor_storage] : tensor_storage_map) {
|
||||
if (is_unused_tensor(tensor_storage.name)) {
|
||||
continue;
|
||||
}
|
||||
processed_tensor_storages.push_back(tensor_storage);
|
||||
}
|
||||
|
||||
@@ -932,6 +921,10 @@ std::vector<MmapTensorStore> ModelLoader::mmap_tensors(std::map<std::string, ggm
|
||||
|
||||
if (tensor_storage.is_f64 ||
|
||||
tensor_storage.is_i64 ||
|
||||
#ifdef SD_USE_UPSTREAM_GGML
|
||||
tensor_storage.is_f8_e4m3 ||
|
||||
tensor_storage.is_f8_e5m2 ||
|
||||
#endif
|
||||
tensor_storage.type != dst_tensor->type) {
|
||||
continue;
|
||||
}
|
||||
@@ -1221,6 +1214,12 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb,
|
||||
f64_to_f32_vec((double*)read_buf, (float*)target_buf, tensor_storage.nelements());
|
||||
} else if (tensor_storage.is_i64) {
|
||||
i64_to_i32_vec((int64_t*)read_buf, (int32_t*)target_buf, tensor_storage.nelements());
|
||||
#ifdef SD_USE_UPSTREAM_GGML
|
||||
} else if (tensor_storage.is_f8_e4m3) {
|
||||
f8_e4m3_to_f16_vec((uint8_t*)read_buf, (uint16_t*)target_buf, tensor_storage.nelements());
|
||||
} else if (tensor_storage.is_f8_e5m2) {
|
||||
f8_e5m2_to_f16_vec((uint8_t*)read_buf, (uint16_t*)target_buf, tensor_storage.nelements());
|
||||
#endif
|
||||
}
|
||||
if (tensor_storage.type != dst_tensor->type) {
|
||||
if (convert_buf == nullptr) {
|
||||
@@ -1552,9 +1551,6 @@ int64_t ModelLoader::get_params_mem_size(ggml_backend_t backend, ggml_type type)
|
||||
int64_t mem_size = 0;
|
||||
std::vector<TensorStorage> processed_tensor_storages;
|
||||
for (auto [name, tensor_storage] : tensor_storage_map) {
|
||||
if (is_unused_tensor(tensor_storage.name)) {
|
||||
continue;
|
||||
}
|
||||
if (tensor_should_be_converted(tensor_storage, type)) {
|
||||
tensor_storage.type = type;
|
||||
}
|
||||
|
||||
@@ -28,8 +28,6 @@ struct MmapTensorStore {
|
||||
std::shared_ptr<struct ggml_backend_buffer> mmbuffer;
|
||||
};
|
||||
|
||||
bool is_unused_tensor(const std::string& name);
|
||||
|
||||
class ModelLoader {
|
||||
public:
|
||||
using FileId = uint64_t;
|
||||
|
||||
@@ -147,7 +147,7 @@ bool ModelLoader::add_file_impl(const std::string& path, const std::string& pref
|
||||
}
|
||||
if (tensor.index_in_zip < 0) {
|
||||
const auto& stamp = physical_files[tensor.file_index];
|
||||
if (tensor.offset > stamp.size || elements / block_size * type_size > stamp.size - tensor.offset) {
|
||||
if (tensor.offset > stamp.size || static_cast<uint64_t>(tensor.nbytes_to_read()) > stamp.size - tensor.offset) { //kcpp int8 fp8
|
||||
LOG_ERROR("tensor '%s' extends beyond its model file", tensor.name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
+42
-8
@@ -274,6 +274,7 @@ bool ModelManager::register_param_tensors(ModelComponent component,
|
||||
new_states.push_back(std::move(state));
|
||||
}
|
||||
|
||||
resolved_tensor_states_.clear();
|
||||
for (auto& state : new_states) {
|
||||
TensorState* registered_state = state.get();
|
||||
tensor_states_by_tensor_[registered_state->tensor] = registered_state;
|
||||
@@ -369,6 +370,7 @@ bool ModelManager::unregister_tensor_states(const std::unordered_set<TensorState
|
||||
}
|
||||
}
|
||||
|
||||
resolved_tensor_states_.clear();
|
||||
for (auto it = tensor_states_by_tensor_.begin(); it != tensor_states_by_tensor_.end();) {
|
||||
if (target_states.count(it->second) > 0) {
|
||||
it = tensor_states_by_tensor_.erase(it);
|
||||
@@ -1199,22 +1201,52 @@ bool ModelManager::resolve_required_tensor_states(const std::vector<ggml_tensor*
|
||||
std::vector<TensorState*>& required_states,
|
||||
ggml_backend_t compute_backend) const {
|
||||
required_states.clear();
|
||||
required_states.reserve(tensors.size());
|
||||
auto append_states = [&](const std::vector<TensorState*>& states) {
|
||||
for (TensorState* state : states) {
|
||||
if (compute_backend == nullptr || state->compute_backend == nullptr ||
|
||||
state->compute_backend == compute_backend) {
|
||||
required_states.push_back(state);
|
||||
}
|
||||
}
|
||||
};
|
||||
for (auto it = resolved_tensor_states_.begin(); it != resolved_tensor_states_.end(); ++it) {
|
||||
if (it->tensors == tensors) {
|
||||
append_states(it->states);
|
||||
resolved_tensor_states_.splice(resolved_tensor_states_.begin(), resolved_tensor_states_, it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
std::vector<TensorState*> states;
|
||||
states.reserve(tensors.size());
|
||||
std::unordered_set<TensorState*> seen;
|
||||
seen.reserve(tensors.size());
|
||||
bool cacheable = true;
|
||||
for (ggml_tensor* tensor : tensors) {
|
||||
if (tensor == nullptr) {
|
||||
continue;
|
||||
}
|
||||
auto param = resolve_param_tensor(tensor);
|
||||
auto found = tensor_states_by_tensor_.find(param);
|
||||
auto found = tensor_states_by_tensor_.find(tensor);
|
||||
// Unregistered views can be rebound without changing the parameter list.
|
||||
cacheable &= found != tensor_states_by_tensor_.end();
|
||||
for (auto view = tensor->view_src; found == tensor_states_by_tensor_.end() && view != nullptr; view = view->view_src) {
|
||||
found = tensor_states_by_tensor_.find(view);
|
||||
}
|
||||
if (found == tensor_states_by_tensor_.end()) {
|
||||
LOG_ERROR("model manager tensor '%s' is not registered", ggml_get_name(tensor));
|
||||
return false;
|
||||
}
|
||||
TensorState* state = found->second;
|
||||
if ((compute_backend == nullptr || state->compute_backend == nullptr ||
|
||||
state->compute_backend == compute_backend) &&
|
||||
seen.insert(state).second) {
|
||||
required_states.push_back(state);
|
||||
if (seen.insert(state).second) {
|
||||
states.push_back(state);
|
||||
}
|
||||
}
|
||||
append_states(states);
|
||||
if (cacheable && !tensors.empty()) {
|
||||
static constexpr size_t MAX_RESOLVED_LISTS = 4;
|
||||
resolved_tensor_states_.push_front({tensors, std::move(states)});
|
||||
if (resolved_tensor_states_.size() > MAX_RESOLVED_LISTS) {
|
||||
resolved_tensor_states_.pop_back();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -1274,8 +1306,7 @@ size_t ModelManager::compute_backend_alloc_size(const std::vector<TensorState*>&
|
||||
size_t total_size = 0;
|
||||
std::unordered_set<TensorState*> seen;
|
||||
for (TensorState* state : states) {
|
||||
if (state == nullptr || state->tensor == nullptr || !seen.insert(state).second ||
|
||||
should_ignore(*state) || is_optional_missing_tensor(state->name)) {
|
||||
if (state == nullptr || state->tensor == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const bool compute_resident =
|
||||
@@ -1285,6 +1316,9 @@ size_t ModelManager::compute_backend_alloc_size(const std::vector<TensorState*>&
|
||||
if (missing_only && compute_resident) {
|
||||
continue;
|
||||
}
|
||||
if (!seen.insert(state).second || should_ignore(*state) || is_optional_missing_tensor(state->name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ggml_backend_buffer_type_t buffer_type = nullptr;
|
||||
if (state->compute_backend == state->params_backend) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define __MODEL_MANAGER_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
@@ -84,9 +85,15 @@ private:
|
||||
size_t resident_bytes = 0;
|
||||
};
|
||||
|
||||
struct ResolvedTensorStates {
|
||||
std::vector<ggml_tensor*> tensors;
|
||||
std::vector<TensorState*> states;
|
||||
};
|
||||
|
||||
ModelLoader model_loader_;
|
||||
std::vector<std::unique_ptr<TensorState>> tensor_states_;
|
||||
std::map<const ggml_tensor*, TensorState*> tensor_states_by_tensor_;
|
||||
mutable std::list<ResolvedTensorStates> resolved_tensor_states_;
|
||||
std::vector<std::unique_ptr<ParamsStorageBlock>> params_storage_blocks_;
|
||||
std::vector<std::unique_ptr<ComputeStagingBlock>> compute_staging_blocks_;
|
||||
std::map<ggml_backend_t, ggml_backend_buffer_type_t> split_buffer_types_;
|
||||
|
||||
+31
-4
@@ -999,7 +999,30 @@ std::string convert_diffusers_vae_to_original_sd1(std::string name) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string convert_diffusers_to_original_wan_vae(std::string name) {
|
||||
std::string convert_diffusers_to_original_wan_vae(std::string name, bool qwen_image_2_1 = false) {
|
||||
if (qwen_image_2_1) {
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
const auto index = std::to_string(i);
|
||||
for (const auto& side : {std::string("encoder"), std::string("decoder")}) {
|
||||
const bool encoder = side == "encoder";
|
||||
const std::string old_prefix = side + (encoder ? ".down_blocks." : ".up_blocks.") + index + ".";
|
||||
const std::string new_prefix = side + (encoder ? ".downsamples." : ".upsamples.") + index + ".";
|
||||
if (!starts_with(name, old_prefix)) {
|
||||
continue;
|
||||
}
|
||||
name.replace(0, old_prefix.size(), new_prefix);
|
||||
const std::string layers = encoder ? "downsamples." : "upsamples.";
|
||||
for (int j = 0; j < (encoder ? 2 : 3); ++j) {
|
||||
const auto old_resnet = new_prefix + "resnets." + std::to_string(j) + ".";
|
||||
const auto new_resnet = new_prefix + layers + std::to_string(j) + ".";
|
||||
replace_with_prefix_map(name, std::vector<std::pair<std::string, std::string>>{{old_resnet + "conv_shortcut.", new_resnet + "shortcut."},
|
||||
{old_resnet, new_resnet + "residual."}});
|
||||
}
|
||||
replace_with_prefix_map(name, std::vector<std::pair<std::string, std::string>>{{new_prefix + (encoder ? "downsampler." : "upsampler."),
|
||||
new_prefix + layers + (encoder ? "2." : "3.")}});
|
||||
}
|
||||
}
|
||||
}
|
||||
static const std::vector<std::pair<std::string, std::string>> prefix_map = {
|
||||
{"quant_conv.", "conv1."},
|
||||
{"post_quant_conv.", "conv2."},
|
||||
@@ -1055,7 +1078,11 @@ std::string convert_diffusers_to_original_wan_vae(std::string name) {
|
||||
};
|
||||
|
||||
replace_with_name_map(name, shared_name_map);
|
||||
replace_with_prefix_map(name, prefix_map);
|
||||
if (qwen_image_2_1) {
|
||||
replace_with_prefix_map(name, std::vector<std::pair<std::string, std::string>>{{"quant_conv.", "conv1."}, {"post_quant_conv.", "conv2."}});
|
||||
} else {
|
||||
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.
|
||||
@@ -1071,7 +1098,7 @@ std::string convert_first_stage_model_name(std::string name, std::string prefix,
|
||||
return name;
|
||||
}
|
||||
if (sd_version_uses_wan_vae(version)) {
|
||||
return convert_diffusers_to_original_wan_vae(name);
|
||||
return convert_diffusers_to_original_wan_vae(name, version == VERSION_QWEN_IMAGE_2_1);
|
||||
}
|
||||
static std::unordered_map<std::string, std::string> vae_name_map = {
|
||||
{"decoder.post_quant_conv.", "post_quant_conv."},
|
||||
@@ -1489,7 +1516,7 @@ 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) || sd_version_is_mage_flow(version) || sd_version_is_minimax_h3(version)) {
|
||||
if (version == VERSION_QWEN_IMAGE_2_1 || sd_version_is_boogu_image(version) || sd_version_is_krea2(version) || sd_version_is_mage_flow(version) || sd_version_is_minimax_h3(version)) {
|
||||
const std::string hf_vision_prefix = "text_encoders.llm.model.visual.";
|
||||
if (starts_with(name, hf_vision_prefix)) {
|
||||
name = "text_encoders.llm.visual." + name.substr(hf_vision_prefix.size());
|
||||
|
||||
@@ -80,6 +80,7 @@ const char* model_version_to_str[] = {
|
||||
"LingBot Video",
|
||||
"Qwen Image",
|
||||
"Qwen Image Layered",
|
||||
"Qwen Image 2.1",
|
||||
"Hunyuan Video",
|
||||
"Anima",
|
||||
"Flux.2",
|
||||
@@ -857,14 +858,25 @@ bool StableDiffusionGGML::init_model_loader(ModelLoader& model_loader, ModelConf
|
||||
}
|
||||
|
||||
bool StableDiffusionGGML::init(const sd_ctx_params_t* sd_ctx_params) {
|
||||
#ifdef SD_USE_UPSTREAM_GGML
|
||||
LOG_WARN(
|
||||
"Using upstream GGML: INT8 tensorwise/convrot is disabled and FP8 weights are "
|
||||
"converted to F16 at load time. Some operators may be unsupported and performance "
|
||||
"may be lower than with patched GGML.");
|
||||
#endif
|
||||
if (!validate_tensor_types(sd_ctx_params->wtype, sd_ctx_params->tensor_type_rules)) {
|
||||
return false;
|
||||
}
|
||||
for (float scale : {sd_ctx_params->linear_scale, sd_ctx_params->attn_scale}) {
|
||||
if (!std::isfinite(scale) || scale < 0.f || (scale > 0.f && !std::isfinite(1.f / scale))) {
|
||||
LOG_ERROR("scale overrides must be finite positive values, or 0 to keep model defaults");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
auto configuration = std::make_unique<ModelConfig>(*sd_ctx_params);
|
||||
n_threads = sd_ctx_params->n_threads;
|
||||
auto configuration = std::make_unique<ModelConfig>(*sd_ctx_params);
|
||||
n_threads = sd_ctx_params->n_threads;
|
||||
tensor_executor = std::make_unique<sd::ParallelExecutor>(n_threads > 0 ? n_threads : sd_get_num_physical_cores());
|
||||
sd::ParallelScope tensor_scope(tensor_executor.get());
|
||||
enable_mmap = sd_ctx_params->enable_mmap;
|
||||
disable_prefetch = sd_ctx_params->disable_prefetch;
|
||||
disable_segmented_compute = sd_ctx_params->disable_segmented_compute;
|
||||
@@ -2158,6 +2170,10 @@ sd::Tensor<float> StableDiffusionGGML::sample(const std::shared_ptr<DiffusionMod
|
||||
|
||||
RunnerEndOnExit sample_control_runner_end{!control_image.empty() && control_net != nullptr ? control_net.get() : nullptr};
|
||||
|
||||
const bool apply_denoise_mask = !denoise_mask.empty() &&
|
||||
std::any_of(denoise_mask.values().begin(), denoise_mask.values().end(),
|
||||
[](float value) { return value != 1.f; });
|
||||
|
||||
std::vector<int> skip_layers(guidance.slg.layers, guidance.slg.layers + guidance.slg.layer_count);
|
||||
float cfg_scale = guidance.txt_cfg;
|
||||
float img_cfg_scale = guidance.img_cfg;
|
||||
@@ -2287,13 +2303,13 @@ sd::Tensor<float> StableDiffusionGGML::sample(const std::shared_ptr<DiffusionMod
|
||||
hunyuan_timestep_r_tensor = sd::Tensor<float>::from_vector({sigmas[step + 1]});
|
||||
}
|
||||
sd::Tensor<float> noised_input = x * c_in;
|
||||
if (!denoise_mask.empty() && (version == VERSION_WAN2_2_TI2V || sd_version_is_ltxav(version) || sd_version_is_lingbot_video(version))) {
|
||||
if (apply_denoise_mask && (version == VERSION_WAN2_2_TI2V || sd_version_is_ltxav(version) || sd_version_is_lingbot_video(version))) {
|
||||
noised_input = noised_input * denoise_mask + sampling_init_latent * (1.0f - denoise_mask);
|
||||
}
|
||||
|
||||
if (cache_runtime.spectrum_enabled && cache_runtime.spectrum.should_predict()) {
|
||||
cache_runtime.spectrum.predict(&denoised);
|
||||
if (!denoise_mask.empty()) {
|
||||
if (apply_denoise_mask) {
|
||||
denoised = denoised * denoise_mask + sampling_init_latent * (1.0f - denoise_mask);
|
||||
}
|
||||
if (preview_needed && sd_should_preview_denoised()) {
|
||||
@@ -2361,6 +2377,8 @@ sd::Tensor<float> StableDiffusionGGML::sample(const std::shared_ptr<DiffusionMod
|
||||
} else if (sd_version_is_flux(version) || sd_version_is_flux2(version) || sd_version_is_longcat(version) || sd_version_is_sefi_image(version)) {
|
||||
diffusion_params.extra = FluxDiffusionExtra{&guidance_tensor,
|
||||
local_skip_layers};
|
||||
} else if (version == VERSION_QWEN_IMAGE_2_1) {
|
||||
diffusion_params.extra = QwenImage21DiffusionExtra{&condition.c_token_types};
|
||||
} else if (sd_version_is_anima(version)) {
|
||||
diffusion_params.extra = AnimaDiffusionExtra{condition.c_t5_ids.empty() ? nullptr : &condition.c_t5_ids,
|
||||
condition.c_t5_weights.empty() ? nullptr : &condition.c_t5_weights};
|
||||
@@ -2516,7 +2534,7 @@ sd::Tensor<float> StableDiffusionGGML::sample(const std::shared_ptr<DiffusionMod
|
||||
if (cache_runtime.spectrum_enabled) {
|
||||
cache_runtime.spectrum.update(denoised);
|
||||
}
|
||||
if (!denoise_mask.empty()) {
|
||||
if (apply_denoise_mask) {
|
||||
denoised = denoised * denoise_mask + sampling_init_latent * (1.0f - denoise_mask);
|
||||
}
|
||||
if (preview_needed && sd_should_preview_denoised()) {
|
||||
@@ -2561,7 +2579,7 @@ int StableDiffusionGGML::get_diffusion_model_down_factor() {
|
||||
if (sd_version_is_dit(version)) {
|
||||
if (sd_version_is_sensenova_u1(version)) {
|
||||
down_factor = 32;
|
||||
} else if (sd_version_is_wan(version) || sd_version_is_lingbot_video(version) || sd_version_is_minimax_h3(version)) {
|
||||
} else if (version == VERSION_QWEN_IMAGE_2_1 || sd_version_is_wan(version) || sd_version_is_lingbot_video(version) || sd_version_is_minimax_h3(version)) {
|
||||
down_factor = 2;
|
||||
} else {
|
||||
down_factor = 1;
|
||||
@@ -2577,6 +2595,8 @@ int StableDiffusionGGML::get_latent_channel() {
|
||||
latent_channel = 128;
|
||||
} else if (sd_version_is_minimax_h3(version)) {
|
||||
latent_channel = 24;
|
||||
} else if (version == VERSION_QWEN_IMAGE_2_1) {
|
||||
latent_channel = 64;
|
||||
} else if (version == VERSION_WAN2_2_TI2V) {
|
||||
latent_channel = 48;
|
||||
} else if (sd_version_is_hunyuan_video(version)) {
|
||||
@@ -2605,7 +2625,7 @@ int StableDiffusionGGML::get_latent_channel() {
|
||||
}
|
||||
|
||||
int StableDiffusionGGML::get_image_channels() const {
|
||||
return version == VERSION_QWEN_IMAGE_LAYERED ? 4 : 3;
|
||||
return version == VERSION_QWEN_IMAGE_LAYERED || version == VERSION_QWEN_IMAGE_2_1 ? 4 : 3;
|
||||
}
|
||||
|
||||
int StableDiffusionGGML::get_image_seq_len(int h, int w) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -36,7 +37,7 @@ extern const char* model_version_to_str[];
|
||||
static inline bool sd_version_supports_ref_latent_img_cfg(SDVersion version) {
|
||||
return version == VERSION_FLUX ||
|
||||
sd_version_is_flux2(version) ||
|
||||
sd_version_is_qwen_image(version) ||
|
||||
(sd_version_is_qwen_image(version) && version != VERSION_QWEN_IMAGE_2_1) ||
|
||||
sd_version_is_mage_flow(version) ||
|
||||
sd_version_is_longcat(version) ||
|
||||
sd_version_is_z_image(version) ||
|
||||
@@ -56,8 +57,9 @@ public:
|
||||
std::shared_ptr<RNG> rng;
|
||||
std::shared_ptr<RNG> sampler_rng = nullptr;
|
||||
int n_threads = -1;
|
||||
float default_flow_shift = INFINITY;
|
||||
float active_flow_shift = INFINITY;
|
||||
std::unique_ptr<sd::ParallelExecutor> tensor_executor;
|
||||
float default_flow_shift = INFINITY;
|
||||
float active_flow_shift = INFINITY;
|
||||
|
||||
std::shared_ptr<Conditioner> cond_stage_model;
|
||||
std::shared_ptr<FrozenCLIPVisionEmbedder> clip_vision; // for svd or wan2.1 i2v
|
||||
@@ -206,6 +208,7 @@ public:
|
||||
StableDiffusionGGML& sd;
|
||||
std::unique_lock<std::recursive_mutex> lock;
|
||||
bool acquired = false;
|
||||
std::optional<sd::ParallelScope> tensor_scope;
|
||||
|
||||
explicit ContextOperation(StableDiffusionGGML& sd)
|
||||
: sd(sd), lock(sd.execution_mutex, std::try_to_lock) {
|
||||
@@ -215,6 +218,7 @@ public:
|
||||
}
|
||||
sd.executing_ = true;
|
||||
acquired = true;
|
||||
tensor_scope.emplace(sd.tensor_executor.get());
|
||||
}
|
||||
|
||||
~ContextOperation() {
|
||||
|
||||
@@ -285,7 +285,8 @@ namespace sd::pipeline {
|
||||
vae_width = request->width;
|
||||
vae_height = request->height;
|
||||
} else {
|
||||
int target_pixels = ref_image_params.vae_input_max_pixels > 0 ? ref_image_params.vae_input_max_pixels : 1024 * 1024;
|
||||
int default_pixels = sd->version == VERSION_QWEN_IMAGE_2_1 ? request->width * request->height : 1024 * 1024;
|
||||
int target_pixels = ref_image_params.vae_input_max_pixels > 0 ? ref_image_params.vae_input_max_pixels : default_pixels;
|
||||
int vae_image_size = std::min(target_pixels, request->width * request->height);
|
||||
vae_width = sqrt(vae_image_size * ref_images[i].shape()[0] / ref_images[i].shape()[1]);
|
||||
vae_height = vae_width * ref_images[i].shape()[1] / ref_images[i].shape()[0];
|
||||
@@ -309,6 +310,9 @@ namespace sd::pipeline {
|
||||
resized_ref_img.shape()[0]);
|
||||
|
||||
ref_latent = sd->encode_first_stage(resized_ref_img);
|
||||
if (sd->version == VERSION_QWEN_IMAGE_2_1) {
|
||||
ref_images[i] = std::move(resized_ref_img);
|
||||
}
|
||||
} else {
|
||||
ref_latent = sd->encode_first_stage(ref_images[i]);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "model/diffusion/model.hpp"
|
||||
#include "model/diffusion/pid.hpp"
|
||||
#include "model/diffusion/qwen_image.hpp"
|
||||
#include "model/diffusion/qwen_image_2_1.hpp"
|
||||
#include "model/diffusion/sensenova_u1.h"
|
||||
#include "model/diffusion/unet.hpp"
|
||||
#include "model/diffusion/wan.hpp"
|
||||
@@ -285,12 +286,19 @@ namespace sd::model_builders {
|
||||
enable_vision,
|
||||
weight_manager,
|
||||
tokenizers);
|
||||
result.diffusion = std::make_shared<Qwen::QwenImageRunner>(ctx.backends.runtime_backend(SDBackendModule::DIFFUSION),
|
||||
tensor_storage_map,
|
||||
"model.diffusion_model",
|
||||
version,
|
||||
weight_manager,
|
||||
sd_ctx_params->model_args);
|
||||
if (version == VERSION_QWEN_IMAGE_2_1) {
|
||||
result.diffusion = std::make_shared<Qwen::QwenImage21Runner>(ctx.backends.runtime_backend(SDBackendModule::DIFFUSION),
|
||||
tensor_storage_map,
|
||||
"model.diffusion_model",
|
||||
weight_manager);
|
||||
} else {
|
||||
result.diffusion = std::make_shared<Qwen::QwenImageRunner>(ctx.backends.runtime_backend(SDBackendModule::DIFFUSION),
|
||||
tensor_storage_map,
|
||||
"model.diffusion_model",
|
||||
version,
|
||||
weight_manager,
|
||||
sd_ctx_params->model_args);
|
||||
}
|
||||
} else if (sd_version_is_mage_flow(version)) {
|
||||
result.conditioner = std::make_shared<LLMEmbedder>(ctx.backends.runtime_backend(SDBackendModule::TE),
|
||||
tensor_storage_map,
|
||||
@@ -584,10 +592,12 @@ namespace sd::model_builders {
|
||||
}
|
||||
|
||||
if (sd_ctx_params->vae_conv_direct) {
|
||||
LOG_INFO("Using Conv2d direct in the vae model");
|
||||
LOG_INFO("Using Conv2d/Conv3d direct in the vae model");
|
||||
result.vae->set_conv2d_direct_enabled(true);
|
||||
result.vae->set_conv3d_direct_enabled(true);
|
||||
if (result.preview) {
|
||||
result.preview->set_conv2d_direct_enabled(true);
|
||||
result.preview->set_conv3d_direct_enabled(true);
|
||||
}
|
||||
}
|
||||
if (result.vae) {
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace sd::pipeline {
|
||||
return LCM_SCHEDULER;
|
||||
} else if (sample_method == DDIM_TRAILING_SAMPLE_METHOD) {
|
||||
return SIMPLE_SCHEDULER;
|
||||
} else if (sd != nullptr && sd_version_is_flux(sd->version)) {
|
||||
} else if (sd != nullptr && (sd_version_is_flux(sd->version) || sd->version == VERSION_QWEN_IMAGE_2_1)) {
|
||||
return FLUX_SCHEDULER;
|
||||
} else if (sd != nullptr && sd_version_is_flux2(sd->version)) {
|
||||
return FLUX2_SCHEDULER;
|
||||
|
||||
+215
-190
@@ -12,6 +12,8 @@
|
||||
#include <utility>
|
||||
|
||||
#include "core/rng.hpp"
|
||||
#include "core/rng_mt19937.hpp"
|
||||
#include "core/rng_philox.hpp"
|
||||
#include "core/tensor.hpp"
|
||||
#include "core/util.h"
|
||||
#include "model.h"
|
||||
@@ -1632,12 +1634,18 @@ static std::tuple<float, float, float> get_ancestral_step(float sigma_from,
|
||||
}
|
||||
}
|
||||
|
||||
class NoiseSampler {
|
||||
public:
|
||||
virtual sd::Tensor<float> operator()(double sigma_from, double sigma_to) = 0;
|
||||
virtual ~NoiseSampler() = default;
|
||||
};
|
||||
|
||||
static sd::Tensor<float> sample_euler_ancestral(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
const std::vector<float>& sigmas,
|
||||
std::shared_ptr<RNG> rng = nullptr,
|
||||
bool is_flow_denoiser = false,
|
||||
float eta = 0.f) {
|
||||
NoiseSampler& noise_sampler,
|
||||
bool is_flow_denoiser = false,
|
||||
float eta = 0.f) {
|
||||
int steps = static_cast<int>(sigmas.size()) - 1;
|
||||
for (int i = 0; i < steps; i++) {
|
||||
float sigma = sigmas[i];
|
||||
@@ -1660,7 +1668,7 @@ static sd::Tensor<float> sample_euler_ancestral(denoise_cb_t model,
|
||||
if (is_flow_denoiser) {
|
||||
x *= alpha_scale;
|
||||
}
|
||||
x += sd::Tensor<float>::randn_like(x, rng) * sigma_up;
|
||||
x += noise_sampler(sigma, sigma_to) * sigma_up;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1778,7 +1786,7 @@ static sd::Tensor<float> sample_dpm2(denoise_cb_t model,
|
||||
static sd::Tensor<float> sample_dpmpp_2s_ancestral(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
const std::vector<float>& sigmas,
|
||||
std::shared_ptr<RNG> rng,
|
||||
NoiseSampler& noise_sampler,
|
||||
float eta) {
|
||||
auto t_fn = [](float sigma) -> float { return -log(sigma); };
|
||||
auto sigma_fn = [](float t) -> float { return exp(-t); };
|
||||
@@ -1810,7 +1818,7 @@ static sd::Tensor<float> sample_dpmpp_2s_ancestral(denoise_cb_t model,
|
||||
}
|
||||
|
||||
if (sigmas[i + 1] > 0) {
|
||||
x += sd::Tensor<float>::randn_like(x, rng) * sigma_up;
|
||||
x += noise_sampler(sigmas[i], sigmas[i + 1]) * sigma_up;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
@@ -1819,7 +1827,7 @@ static sd::Tensor<float> sample_dpmpp_2s_ancestral(denoise_cb_t model,
|
||||
static sd::Tensor<float> sample_dpmpp_2s_ancestral_flow(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
const std::vector<float>& sigmas,
|
||||
std::shared_ptr<RNG> rng,
|
||||
NoiseSampler& noise_sampler,
|
||||
float eta = 1.0f) {
|
||||
int steps = static_cast<int>(sigmas.size()) - 1;
|
||||
for (int i = 0; i < steps; i++) {
|
||||
@@ -1902,7 +1910,7 @@ static sd::Tensor<float> sample_dpmpp_2s_ancestral_flow(denoise_cb_t model,
|
||||
x = (x * sigma_down_i_ratio) + (D_i * (1.0f - sigma_down_i_ratio));
|
||||
|
||||
if (sigma_to > 0.0f && eta > 0.0f) {
|
||||
x = alpha_scale * x + sd::Tensor<float>::randn_like(x, rng) * sigma_up;
|
||||
x = alpha_scale * x + noise_sampler(sigma, sigma_to) * sigma_up;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1978,169 +1986,17 @@ static sd::Tensor<float> sample_dpmpp_2m_v2(denoise_cb_t model,
|
||||
return x;
|
||||
}
|
||||
|
||||
// DPM-Solver++(2M) SDE, midpoint variant. Ref: Lu et al. arXiv:2211.01095;
|
||||
// k-diffusion sample_dpmpp_2m_sde.
|
||||
// DPM-Solver++(2M) SDE, midpoint variant.
|
||||
// Ref: Lu et al. arXiv:2211.01095; k-diffusion sample_dpmpp_2m_sde
|
||||
static sd::Tensor<float> sample_dpmpp_2m_sde(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
const std::vector<float>& sigmas,
|
||||
std::shared_ptr<RNG> rng,
|
||||
NoiseSampler& noise_sampler,
|
||||
float eta) {
|
||||
sd::Tensor<float> old_denoised;
|
||||
bool have_old_denoised = false;
|
||||
float h_last = 0.f;
|
||||
|
||||
int steps = static_cast<int>(sigmas.size()) - 1;
|
||||
for (int i = 0; i < steps; i++) {
|
||||
auto denoised_opt = model(x, sigmas[i], i + 1);
|
||||
if (denoised_opt.pred.empty()) {
|
||||
return {};
|
||||
}
|
||||
sd::Tensor<float> denoised = std::move(denoised_opt.pred);
|
||||
|
||||
if (sigmas[i + 1] == 0.f) {
|
||||
x = denoised;
|
||||
} else {
|
||||
float t = -std::log(sigmas[i]);
|
||||
float s = -std::log(sigmas[i + 1]);
|
||||
float h = s - t;
|
||||
float eta_h = eta * h;
|
||||
float a = sigmas[i + 1] / sigmas[i] * std::exp(-eta_h);
|
||||
float b = -std::expm1(-h - eta_h);
|
||||
|
||||
x = a * x + b * denoised;
|
||||
|
||||
if (have_old_denoised) {
|
||||
float r = h_last / h;
|
||||
x += (0.5f * b / r) * (denoised - old_denoised);
|
||||
}
|
||||
if (eta > 0.f) {
|
||||
x += sd::Tensor<float>::randn_like(x, rng) * (sigmas[i + 1] * std::sqrt(-std::expm1(-2.f * eta_h)));
|
||||
}
|
||||
h_last = h;
|
||||
}
|
||||
old_denoised = denoised;
|
||||
have_old_denoised = true;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
// Seeded Brownian tree providing deterministic, step-count-stable Gaussian
|
||||
// increments for stochastic samplers. Constructed once per generation; each
|
||||
// call returns unit-variance noise for interval [sigma_a, sigma_b].
|
||||
// Reference: torchsde BrownianTree; k-diffusion BatchedBrownianTree.
|
||||
class BrownianTreeNoiseSampler {
|
||||
public:
|
||||
BrownianTreeNoiseSampler(const sd::Tensor<float>& x_template,
|
||||
double sigma_min,
|
||||
double sigma_max,
|
||||
uint64_t seed)
|
||||
: t_min_(sigma_min),
|
||||
t_max_(sigma_max),
|
||||
shape_(x_template.shape()),
|
||||
root_seed_(mix64(seed, 0x9E3779B97F4A7C15ULL)) {
|
||||
auto rng = std::make_shared<STDDefaultRNG>();
|
||||
rng->manual_seed(mix64(seed, 0xBF58476D1CE4E5B9ULL));
|
||||
w_at_tmax_ = sd::Tensor<float>::randn(shape_, rng) * std::sqrt(static_cast<float>(t_max_ - t_min_));
|
||||
}
|
||||
|
||||
sd::Tensor<float> operator()(double sigma_a, double sigma_b) {
|
||||
double a = clamp(std::min(sigma_a, sigma_b));
|
||||
double b = clamp(std::max(sigma_a, sigma_b));
|
||||
auto dW = w(b) - w(a);
|
||||
float span = static_cast<float>(std::max(std::abs(sigma_b - sigma_a), 1e-12));
|
||||
return dW * (1.0f / std::sqrt(span));
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr int kMaxDepth = 24;
|
||||
|
||||
static uint64_t mix64(uint64_t v, uint64_t salt) {
|
||||
uint64_t z = v + salt;
|
||||
z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL;
|
||||
z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL;
|
||||
return z ^ (z >> 31);
|
||||
}
|
||||
|
||||
double clamp(double t) const {
|
||||
return std::min(std::max(t, t_min_), t_max_);
|
||||
}
|
||||
|
||||
sd::Tensor<float> w(double t) {
|
||||
auto it = cache_.find(t);
|
||||
if (it != cache_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
sd::Tensor<float> zero = sd::Tensor<float>::zeros(shape_);
|
||||
sd::Tensor<float> out = bridge(t_min_, t_max_, zero, w_at_tmax_, t, root_seed_, kMaxDepth);
|
||||
cache_.emplace(t, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
sd::Tensor<float> bridge(double a,
|
||||
double c,
|
||||
const sd::Tensor<float>& w_a,
|
||||
const sd::Tensor<float>& w_c,
|
||||
double t,
|
||||
uint64_t node_seed,
|
||||
int depth) {
|
||||
if (depth <= 0 || c - a < 1e-9) {
|
||||
float alpha = (c > a) ? static_cast<float>((t - a) / (c - a)) : 0.5f;
|
||||
return (1.0f - alpha) * w_a + alpha * w_c;
|
||||
}
|
||||
double m = 0.5 * (a + c);
|
||||
double std_dev = std::sqrt((c - m) * (m - a) / (c - a));
|
||||
auto rng = std::make_shared<STDDefaultRNG>();
|
||||
rng->manual_seed(node_seed);
|
||||
auto z = sd::Tensor<float>::randn(shape_, rng);
|
||||
auto w_m = 0.5f * (w_a + w_c) + static_cast<float>(std_dev) * z;
|
||||
if (t == m) {
|
||||
return w_m;
|
||||
}
|
||||
if (t < m) {
|
||||
return bridge(a, m, w_a, w_m, t, mix64(node_seed, 1), depth - 1);
|
||||
}
|
||||
return bridge(m, c, w_m, w_c, t, mix64(node_seed, 2), depth - 1);
|
||||
}
|
||||
|
||||
double t_min_;
|
||||
double t_max_;
|
||||
std::vector<int64_t> shape_;
|
||||
uint64_t root_seed_;
|
||||
sd::Tensor<float> w_at_tmax_;
|
||||
std::map<double, sd::Tensor<float>> cache_;
|
||||
};
|
||||
|
||||
// DPM-Solver++(2M) SDE, midpoint variant, with step-count-stable Brownian-tree
|
||||
// noise. Same trajectory shape at any step count for a given seed. Aliased in
|
||||
// k-diffusion / ComfyUI as sample_dpmpp_2m_sde_gpu.
|
||||
// Ref: Lu et al. arXiv:2211.01095; torchsde BrownianTree.
|
||||
static sd::Tensor<float> sample_dpmpp_2m_sde_bt(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
const std::vector<float>& sigmas,
|
||||
std::shared_ptr<RNG> rng,
|
||||
float eta) {
|
||||
double sigma_max = 0.0;
|
||||
double sigma_min = std::numeric_limits<double>::infinity();
|
||||
for (float s : sigmas) {
|
||||
if (s > 0.0f) {
|
||||
sigma_max = std::max(sigma_max, static_cast<double>(s));
|
||||
sigma_min = std::min(sigma_min, static_cast<double>(s));
|
||||
}
|
||||
}
|
||||
if (sigma_max <= sigma_min) {
|
||||
return x;
|
||||
}
|
||||
uint64_t tree_seed = 0;
|
||||
{
|
||||
auto draw = rng->randn(2);
|
||||
std::memcpy(&tree_seed, draw.data(), sizeof(tree_seed));
|
||||
}
|
||||
BrownianTreeNoiseSampler noise_sampler(x, sigma_min, sigma_max, tree_seed);
|
||||
|
||||
sd::Tensor<float> old_denoised;
|
||||
bool have_old_denoised = false;
|
||||
float h_last = 0.f;
|
||||
|
||||
int steps = static_cast<int>(sigmas.size()) - 1;
|
||||
for (int i = 0; i < steps; i++) {
|
||||
auto denoised_opt = model(x, sigmas[i], i + 1);
|
||||
@@ -2181,7 +2037,7 @@ using SamplerExtraArgs = KeyValueArgs;
|
||||
static sd::Tensor<float> sample_lcm(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
const std::vector<float>& sigmas,
|
||||
std::shared_ptr<RNG> rng,
|
||||
NoiseSampler& noise_sampler,
|
||||
bool is_flow_denoiser,
|
||||
const SamplerExtraArgs& extra_sample_args) {
|
||||
struct LCMSampleArgs {
|
||||
@@ -2234,7 +2090,7 @@ static sd::Tensor<float> sample_lcm(denoise_cb_t model,
|
||||
if (is_flow_denoiser) {
|
||||
x *= (1 - sigmas[i + 1]);
|
||||
}
|
||||
auto noise = sd::Tensor<float>::randn_like(x, rng);
|
||||
auto noise = noise_sampler(sigmas[i], sigmas[i + 1]);
|
||||
if (args.noise_clip_std > 0.0f && noise.numel() > 0) {
|
||||
double mean = 0.0;
|
||||
for (int64_t j = 0; j < noise.numel(); ++j) {
|
||||
@@ -2352,7 +2208,7 @@ static sd::Tensor<float> sample_ipndm_v(denoise_cb_t model,
|
||||
static sd::Tensor<float> sample_res_multistep(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
const std::vector<float>& sigmas,
|
||||
std::shared_ptr<RNG> rng,
|
||||
NoiseSampler& noise_sampler,
|
||||
bool is_flow_denoiser,
|
||||
float eta) {
|
||||
sd::Tensor<float> old_denoised = x;
|
||||
@@ -2417,7 +2273,7 @@ static sd::Tensor<float> sample_res_multistep(denoise_cb_t model,
|
||||
if (is_flow_denoiser) {
|
||||
x *= alpha_scale;
|
||||
}
|
||||
x += sd::Tensor<float>::randn_like(x, rng) * sigma_up;
|
||||
x += noise_sampler(sigma_from, sigma_to) * sigma_up;
|
||||
}
|
||||
|
||||
old_denoised = denoised;
|
||||
@@ -2430,7 +2286,7 @@ static sd::Tensor<float> sample_res_multistep(denoise_cb_t model,
|
||||
static sd::Tensor<float> sample_res_2s(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
const std::vector<float>& sigmas,
|
||||
std::shared_ptr<RNG> rng,
|
||||
NoiseSampler& noise_sampler,
|
||||
bool is_flow_denoiser,
|
||||
float eta) {
|
||||
const float c2 = 0.5f;
|
||||
@@ -2493,7 +2349,7 @@ static sd::Tensor<float> sample_res_2s(denoise_cb_t model,
|
||||
if (is_flow_denoiser) {
|
||||
x *= alpha_scale;
|
||||
}
|
||||
x += sd::Tensor<float>::randn_like(x, rng) * sigma_up;
|
||||
x += noise_sampler(sigma_from, sigma_to) * sigma_up;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
@@ -2502,7 +2358,7 @@ static sd::Tensor<float> sample_res_2s(denoise_cb_t model,
|
||||
static sd::Tensor<float> sample_er_sde(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
std::vector<float> sigmas,
|
||||
std::shared_ptr<RNG> rng,
|
||||
NoiseSampler& noise_sampler,
|
||||
bool is_flow_denoiser,
|
||||
float eta) {
|
||||
constexpr int max_stage = 3;
|
||||
@@ -2624,7 +2480,7 @@ static sd::Tensor<float> sample_er_sde(denoise_cb_t model,
|
||||
float noise_scale_sq = er_lambda_t * er_lambda_t - er_lambda_s * er_lambda_s * r * r;
|
||||
if (s_noise > 0.0f && noise_scale_sq > 0.0f) {
|
||||
float noise_scale = alpha_t * std::sqrt(std::max(noise_scale_sq, 0.0f));
|
||||
x += sd::Tensor<float>::randn_like(x, rng) * noise_scale;
|
||||
x += noise_sampler(sigmas[i], sigmas[i + 1]) * noise_scale;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2637,7 +2493,7 @@ static sd::Tensor<float> sample_er_sde(denoise_cb_t model,
|
||||
static sd::Tensor<float> sample_tcd(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
const std::vector<float>& sigmas,
|
||||
std::shared_ptr<RNG> rng,
|
||||
NoiseSampler& noise_sampler,
|
||||
float eta) {
|
||||
float beta_start = 0.00085f;
|
||||
float beta_end = 0.0120f;
|
||||
@@ -2694,7 +2550,7 @@ static sd::Tensor<float> sample_tcd(denoise_cb_t model,
|
||||
|
||||
if (eta > 0 && sigma_to > 0.0f) {
|
||||
x = std::sqrt(alpha_prod_t_prev / alpha_prod_s) * x +
|
||||
std::sqrt(1.0f / alpha_prod_t_prev - 1.0f / alpha_prod_s) * sd::Tensor<float>::randn_like(x, rng);
|
||||
std::sqrt(1.0f / alpha_prod_t_prev - 1.0f / alpha_prod_s) * noise_sampler(sigma, sigma_to);
|
||||
}
|
||||
}
|
||||
return x;
|
||||
@@ -2829,7 +2685,7 @@ static sd::Tensor<float> sample_euler_cfg_pp(denoise_cb_t model,
|
||||
static sd::Tensor<float> sample_euler_ancestral_cfg_pp(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
const std::vector<float>& sigmas,
|
||||
std::shared_ptr<RNG> rng,
|
||||
NoiseSampler& noise_sampler,
|
||||
float eta) {
|
||||
int steps = static_cast<int>(sigmas.size()) - 1;
|
||||
for (int i = 0; i < steps; i++) {
|
||||
@@ -2848,7 +2704,7 @@ static sd::Tensor<float> sample_euler_ancestral_cfg_pp(denoise_cb_t model,
|
||||
x = denoised + d * sigma_down;
|
||||
|
||||
if (sigmas[i + 1] > 0) {
|
||||
x += sd::Tensor<float>::randn_like(x, rng) * sigma_up;
|
||||
x += noise_sampler(sigmas[i], sigmas[i + 1]) * sigma_up;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
@@ -2858,7 +2714,7 @@ static sd::Tensor<float> sample_euler_ancestral_cfg_pp(denoise_cb_t model,
|
||||
static sd::Tensor<float> sample_gradient_estimation(denoise_cb_t model,
|
||||
sd::Tensor<float> x,
|
||||
const std::vector<float>& sigmas,
|
||||
std::shared_ptr<RNG> rng,
|
||||
NoiseSampler& noise_sampler,
|
||||
bool is_flow_denoiser,
|
||||
float eta,
|
||||
const SamplerExtraArgs& extra_sample_args) {
|
||||
@@ -2905,13 +2761,180 @@ static sd::Tensor<float> sample_gradient_estimation(denoise_cb_t model,
|
||||
if (is_flow_denoiser) {
|
||||
x *= alpha_scale;
|
||||
}
|
||||
x += sd::Tensor<float>::randn_like(x, rng) * sigma_up;
|
||||
x += noise_sampler(sigma, sigma_to) * sigma_up;
|
||||
}
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
// independent and identically distributed Gaussian noise (default for most samplers)
|
||||
class IIDGaussianNoiseSampler : public NoiseSampler {
|
||||
public:
|
||||
IIDGaussianNoiseSampler(const sd::Tensor<float>& x_template, std::shared_ptr<RNG> r)
|
||||
: rng(std::move(r)), shape(x_template.shape()) {}
|
||||
sd::Tensor<float> operator()(double sigma_from, double sigma_to) override {
|
||||
(void)sigma_from;
|
||||
(void)sigma_to;
|
||||
return sd::Tensor<float>::randn(shape, rng);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<RNG> rng;
|
||||
std::vector<int64_t> shape;
|
||||
};
|
||||
|
||||
// A fixed tree seed, shape and sigma range give consistent increments across
|
||||
// interval subdivisions. Each query returns normalized Gaussian noise.
|
||||
// Reference: torchsde BrownianTree; k-diffusion BatchedBrownianTree.
|
||||
class BrownianTreeNoiseSampler : public NoiseSampler {
|
||||
public:
|
||||
BrownianTreeNoiseSampler(const sd::Tensor<float>& x_template,
|
||||
double sigma_min,
|
||||
double sigma_max,
|
||||
std::shared_ptr<RNG> seed_rng,
|
||||
std::shared_ptr<RNG> node_rng)
|
||||
: t_min_(sigma_min),
|
||||
t_max_(sigma_max),
|
||||
shape_(x_template.shape()),
|
||||
seed_rng_(std::move(seed_rng)),
|
||||
node_rng_(std::move(node_rng)) {}
|
||||
|
||||
sd::Tensor<float> operator()(double sigma_a, double sigma_b) override {
|
||||
if (!initialized_) {
|
||||
uint64_t seed = 0;
|
||||
auto draw = seed_rng_->randn(2);
|
||||
std::memcpy(&seed, draw.data(), sizeof(seed));
|
||||
root_seed_ = mix64(seed, 0x9E3779B97F4A7C15ULL);
|
||||
node_rng_->manual_seed(mix64(seed, 0xBF58476D1CE4E5B9ULL));
|
||||
w_at_tmax_ = sd::Tensor<float>::randn(shape_, node_rng_) * std::sqrt(static_cast<float>(t_max_ - t_min_));
|
||||
initialized_ = true;
|
||||
}
|
||||
double a = clamp(std::min(sigma_a, sigma_b));
|
||||
double b = clamp(std::max(sigma_a, sigma_b));
|
||||
auto dW = w(b) - w(a);
|
||||
float span = static_cast<float>(std::max(std::abs(sigma_b - sigma_a), 1e-12));
|
||||
return dW * (1.0f / std::sqrt(span));
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr int kMaxDepth = 24;
|
||||
|
||||
static uint64_t mix64(uint64_t v, uint64_t salt) {
|
||||
uint64_t z = v + salt;
|
||||
z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL;
|
||||
z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL;
|
||||
return z ^ (z >> 31);
|
||||
}
|
||||
|
||||
double clamp(double t) const {
|
||||
return std::min(std::max(t, t_min_), t_max_);
|
||||
}
|
||||
|
||||
sd::Tensor<float> w(double t) {
|
||||
auto it = cache_.find(t);
|
||||
if (it != cache_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
sd::Tensor<float> zero = sd::Tensor<float>::zeros(shape_);
|
||||
sd::Tensor<float> out = bridge(t_min_, t_max_, zero, w_at_tmax_, t, root_seed_, kMaxDepth);
|
||||
cache_.emplace(t, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
sd::Tensor<float> bridge(double a,
|
||||
double c,
|
||||
const sd::Tensor<float>& w_a,
|
||||
const sd::Tensor<float>& w_c,
|
||||
double t,
|
||||
uint64_t node_seed,
|
||||
int depth) {
|
||||
if (depth <= 0 || c - a < 1e-9) {
|
||||
float alpha = (c > a) ? static_cast<float>((t - a) / (c - a)) : 0.5f;
|
||||
return (1.0f - alpha) * w_a + alpha * w_c;
|
||||
}
|
||||
double m = 0.5 * (a + c);
|
||||
double std_dev = std::sqrt((c - m) * (m - a) / (c - a));
|
||||
node_rng_->manual_seed(node_seed);
|
||||
auto z = sd::Tensor<float>::randn(shape_, node_rng_);
|
||||
auto w_m = 0.5f * (w_a + w_c) + static_cast<float>(std_dev) * z;
|
||||
if (t == m) {
|
||||
return w_m;
|
||||
}
|
||||
if (t < m) {
|
||||
return bridge(a, m, w_a, w_m, t, mix64(node_seed, 1), depth - 1);
|
||||
}
|
||||
return bridge(m, c, w_m, w_c, t, mix64(node_seed, 2), depth - 1);
|
||||
}
|
||||
|
||||
double t_min_;
|
||||
double t_max_;
|
||||
std::vector<int64_t> shape_;
|
||||
std::shared_ptr<RNG> seed_rng_;
|
||||
std::shared_ptr<RNG> node_rng_;
|
||||
uint64_t root_seed_ = 0;
|
||||
bool initialized_ = false;
|
||||
sd::Tensor<float> w_at_tmax_;
|
||||
std::map<double, sd::Tensor<float>> cache_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<NoiseSampler> make_noise_sampler(const sd::Tensor<float>& x, std::shared_ptr<RNG> rng, sample_method_t method, const std::vector<float>& sigmas, const SamplerExtraArgs& extra_args) {
|
||||
bool brownian_tree = (method == DPMPP2M_SDE_BT_SAMPLE_METHOD);
|
||||
bool def_brownian_tree = brownian_tree;
|
||||
std::string brownian_tree_rng = "cpu";
|
||||
|
||||
for (const auto& [key, value] : extra_args) {
|
||||
if (key == "noise_sampler") {
|
||||
if (value == "iid") {
|
||||
brownian_tree = false;
|
||||
} else if (value == "brownian_tree") {
|
||||
brownian_tree = true;
|
||||
} else {
|
||||
LOG_WARN("unknown noise_sampler value '%s'; using default", value.c_str());
|
||||
}
|
||||
} else if (key == "brownian_tree_rng") {
|
||||
if (value == "cpu" || value == "cuda" || value == "std_default" || value == "sampler_rng") {
|
||||
brownian_tree_rng = value;
|
||||
} else {
|
||||
LOG_WARN("ignoring invalid brownian_tree_rng value '%s'; expected cpu, cuda, std_default or sampler_rng", value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (brownian_tree) {
|
||||
double sigma_max = 0.0;
|
||||
double sigma_min = std::numeric_limits<double>::infinity();
|
||||
for (float s : sigmas) {
|
||||
if (s > 0.0f) {
|
||||
sigma_max = std::max(sigma_max, static_cast<double>(s));
|
||||
sigma_min = std::min(sigma_min, static_cast<double>(s));
|
||||
}
|
||||
}
|
||||
|
||||
if (sigma_max > sigma_min) {
|
||||
std::shared_ptr<RNG> node_rng;
|
||||
if (brownian_tree_rng == "sampler_rng") {
|
||||
node_rng = rng->clone();
|
||||
} else if (brownian_tree_rng == "std_default") {
|
||||
node_rng = std::make_shared<STDDefaultRNG>();
|
||||
} else if (brownian_tree_rng == "cuda") {
|
||||
node_rng = std::make_shared<PhiloxRNG>();
|
||||
} else {
|
||||
node_rng = std::make_shared<MT19937RNG>();
|
||||
}
|
||||
if (!def_brownian_tree) {
|
||||
LOG_INFO("setting noise sampler to Brownian tree (%s RNG)", brownian_tree_rng.c_str());
|
||||
}
|
||||
return std::make_unique<BrownianTreeNoiseSampler>(x, sigma_min, sigma_max, rng, std::move(node_rng));
|
||||
}
|
||||
}
|
||||
|
||||
if (def_brownian_tree) {
|
||||
LOG_INFO("setting noise sampler to independent and identically distributed (iid)");
|
||||
}
|
||||
return std::make_unique<IIDGaussianNoiseSampler>(x, rng);
|
||||
}
|
||||
|
||||
// k diffusion reverse ODE: dx = (x - D(x;\sigma)) / \sigma dt; \sigma(t) = t
|
||||
static sd::Tensor<float> sample_k_diffusion(sample_method_t method,
|
||||
denoise_cb_t model,
|
||||
@@ -2928,9 +2951,12 @@ static sd::Tensor<float> sample_k_diffusion(sample_method_t method,
|
||||
}
|
||||
}
|
||||
SamplerExtraArgs extra_args = parse_key_value_args(extra_sample_args, "extra sample arg");
|
||||
|
||||
std::unique_ptr<NoiseSampler> noise_sampler = make_noise_sampler(x, rng, method, sigmas, extra_args);
|
||||
|
||||
switch (method) {
|
||||
case EULER_A_SAMPLE_METHOD:
|
||||
return sample_euler_ancestral(model, std::move(x), sigmas, rng, is_flow_denoiser, eta);
|
||||
return sample_euler_ancestral(model, std::move(x), sigmas, *noise_sampler, is_flow_denoiser, eta);
|
||||
case EULER_SAMPLE_METHOD:
|
||||
return sample_euler(model, std::move(x), sigmas);
|
||||
case HEUN_SAMPLE_METHOD:
|
||||
@@ -2939,42 +2965,41 @@ static sd::Tensor<float> sample_k_diffusion(sample_method_t method,
|
||||
return sample_dpm2(model, std::move(x), sigmas);
|
||||
case DPMPP2S_A_SAMPLE_METHOD:
|
||||
if (is_flow_denoiser)
|
||||
return sample_dpmpp_2s_ancestral_flow(model, std::move(x), sigmas, rng, eta);
|
||||
return sample_dpmpp_2s_ancestral_flow(model, std::move(x), sigmas, *noise_sampler, eta);
|
||||
else
|
||||
return sample_dpmpp_2s_ancestral(model, std::move(x), sigmas, rng, eta);
|
||||
return sample_dpmpp_2s_ancestral(model, std::move(x), sigmas, *noise_sampler, eta);
|
||||
case DPMPP2M_SAMPLE_METHOD:
|
||||
return sample_dpmpp_2m(model, std::move(x), sigmas);
|
||||
case DPMPP2Mv2_SAMPLE_METHOD:
|
||||
return sample_dpmpp_2m_v2(model, std::move(x), sigmas);
|
||||
case LCM_SAMPLE_METHOD:
|
||||
return sample_lcm(model, std::move(x), sigmas, rng, is_flow_denoiser, extra_args);
|
||||
return sample_lcm(model, std::move(x), sigmas, *noise_sampler, is_flow_denoiser, extra_args);
|
||||
case IPNDM_SAMPLE_METHOD:
|
||||
return sample_ipndm(model, std::move(x), sigmas);
|
||||
case IPNDM_V_SAMPLE_METHOD:
|
||||
return sample_ipndm_v(model, std::move(x), sigmas);
|
||||
case RES_MULTISTEP_SAMPLE_METHOD:
|
||||
return sample_res_multistep(model, std::move(x), sigmas, rng, is_flow_denoiser, eta);
|
||||
return sample_res_multistep(model, std::move(x), sigmas, *noise_sampler, is_flow_denoiser, eta);
|
||||
case RES_2S_SAMPLE_METHOD:
|
||||
return sample_res_2s(model, std::move(x), sigmas, rng, is_flow_denoiser, eta);
|
||||
return sample_res_2s(model, std::move(x), sigmas, *noise_sampler, is_flow_denoiser, eta);
|
||||
case ER_SDE_SAMPLE_METHOD:
|
||||
return sample_er_sde(model, std::move(x), sigmas, rng, is_flow_denoiser, eta);
|
||||
return sample_er_sde(model, std::move(x), sigmas, *noise_sampler, is_flow_denoiser, eta);
|
||||
case DPMPP2M_SDE_SAMPLE_METHOD:
|
||||
return sample_dpmpp_2m_sde(model, std::move(x), sigmas, rng, eta);
|
||||
case DPMPP2M_SDE_BT_SAMPLE_METHOD:
|
||||
return sample_dpmpp_2m_sde_bt(model, std::move(x), sigmas, rng, eta);
|
||||
return sample_dpmpp_2m_sde(model, std::move(x), sigmas, *noise_sampler, eta);
|
||||
case DDIM_TRAILING_SAMPLE_METHOD:
|
||||
// DDIM is equivalent to Euler Ancestral with the Simple scheduler
|
||||
return sample_euler_ancestral(model, std::move(x), sigmas, rng, is_flow_denoiser, eta);
|
||||
return sample_euler_ancestral(model, std::move(x), sigmas, *noise_sampler, is_flow_denoiser, eta);
|
||||
case TCD_SAMPLE_METHOD:
|
||||
return sample_tcd(model, std::move(x), sigmas, rng, eta);
|
||||
return sample_tcd(model, std::move(x), sigmas, *noise_sampler, eta);
|
||||
case LMS_SAMPLE_METHOD:
|
||||
return sample_lms(model, std::move(x), sigmas, extra_args);
|
||||
case EULER_CFG_PP_SAMPLE_METHOD:
|
||||
return sample_euler_cfg_pp(model, std::move(x), sigmas);
|
||||
case EULER_A_CFG_PP_SAMPLE_METHOD:
|
||||
return sample_euler_ancestral_cfg_pp(model, std::move(x), sigmas, rng, eta);
|
||||
return sample_euler_ancestral_cfg_pp(model, std::move(x), sigmas, *noise_sampler, eta);
|
||||
case EULER_GE_SAMPLE_METHOD:
|
||||
return sample_gradient_estimation(model, std::move(x), sigmas, rng, is_flow_denoiser, eta, extra_args);
|
||||
return sample_gradient_estimation(model, std::move(x), sigmas, *noise_sampler, is_flow_denoiser, eta, extra_args);
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -26,13 +26,26 @@ static float get_cache_reuse_threshold(const sd_cache_params_t& params) {
|
||||
}
|
||||
|
||||
const char* sd_type_name(enum sd_type_t type) {
|
||||
if ((int)type < std::min<int>(SD_TYPE_COUNT, GGML_TYPE_COUNT)) {
|
||||
return ggml_type_name((ggml_type)type);
|
||||
if (type == SD_TYPE_F8_E4M3) {
|
||||
return "f8_e4m3";
|
||||
}
|
||||
if (type == SD_TYPE_F8_E5M2) {
|
||||
return "f8_e5m2";
|
||||
}
|
||||
const auto ggml_type = sd_type_to_ggml_type(type);
|
||||
if (ggml_type != GGML_TYPE_COUNT) {
|
||||
return ggml_type_name(ggml_type);
|
||||
}
|
||||
return NONE_STR;
|
||||
}
|
||||
|
||||
enum sd_type_t str_to_sd_type(const char* str) {
|
||||
if (!strcmp(str, "f8_e4m3")) {
|
||||
return SD_TYPE_F8_E4M3;
|
||||
}
|
||||
if (!strcmp(str, "f8_e5m2")) {
|
||||
return SD_TYPE_F8_E5M2;
|
||||
}
|
||||
for (int i = 0; i < std::min<int>(SD_TYPE_COUNT, GGML_TYPE_COUNT); i++) {
|
||||
auto trait = ggml_get_type_traits((ggml_type)i);
|
||||
if (!strcmp(str, trait->type_name)) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "core/util.h"
|
||||
#include "tokenize_util.h"
|
||||
@@ -31,8 +32,37 @@ std::vector<std::pair<int, std::u32string>> BPETokenizer::bytes_to_unicode() {
|
||||
return byte_unicode_pairs;
|
||||
}
|
||||
|
||||
std::vector<std::string> BPETokenizer::token_split(const std::string& text) const {
|
||||
return ::token_split(text);
|
||||
BPETokenizer::BPETokenizer(const std::string& pattern) {
|
||||
if (!pattern.empty()) {
|
||||
split_regex_ = std::make_unique<sd::Regex>();
|
||||
std::string error;
|
||||
if (!split_regex_->compile(pattern, &error)) {
|
||||
throw std::runtime_error("invalid tokenizer regex: " + error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BPETokenizer::token_split(const std::string& text, std::vector<std::string>& tokens, std::string* error) const {
|
||||
tokens.clear();
|
||||
if (error) {
|
||||
error->clear();
|
||||
}
|
||||
if (!split_regex_) {
|
||||
if (!text.empty()) {
|
||||
tokens.push_back(text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
std::vector<sd::Regex::Match> matches;
|
||||
if (!split_regex_->find_matches(text, matches, error)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& match : matches) {
|
||||
if (match.first != match.second) {
|
||||
tokens.push_back(text.substr(match.first, match.second - match.first));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::u32string> BPETokenizer::split_utf32(const std::string& text, char32_t delimiter) {
|
||||
@@ -154,7 +184,10 @@ bool BPETokenizer::encode(const std::string& text, std::vector<int>& result, on_
|
||||
token_strs.push_back(splited_text);
|
||||
continue;
|
||||
}
|
||||
auto tokens = token_split(splited_text);
|
||||
std::vector<std::string> tokens;
|
||||
if (!token_split(splited_text, tokens, error)) {
|
||||
return false;
|
||||
}
|
||||
for (auto& token : tokens) {
|
||||
if (on_new_token_cb != nullptr) {
|
||||
bool skip = on_new_token_cb(token, bpe_tokens);
|
||||
|
||||
@@ -5,15 +5,19 @@
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "core/regex.h"
|
||||
#include "tokenizer.h"
|
||||
|
||||
class BPETokenizer : public Tokenizer {
|
||||
private:
|
||||
std::unique_ptr<sd::Regex> split_regex_;
|
||||
|
||||
protected:
|
||||
std::map<int, std::u32string> byte_encoder;
|
||||
std::map<std::u32string, int> byte_decoder;
|
||||
@@ -28,12 +32,12 @@ protected:
|
||||
protected:
|
||||
static std::vector<std::pair<int, std::u32string>> bytes_to_unicode();
|
||||
static std::vector<std::u32string> split_utf32(const std::string& text, char32_t delimiter = U'\n');
|
||||
virtual std::vector<std::string> token_split(const std::string& text) const;
|
||||
bool token_split(const std::string& text, std::vector<std::string>& tokens, std::string* error = nullptr) const;
|
||||
std::vector<std::u32string> bpe(const std::u32string& token) const;
|
||||
std::string decode_token(int token_id) const override;
|
||||
|
||||
public:
|
||||
BPETokenizer() = default;
|
||||
explicit BPETokenizer(const std::string& pattern);
|
||||
virtual ~BPETokenizer() = default;
|
||||
|
||||
bool encode(const std::string& text, std::vector<int>& tokens, on_new_token_cb_t on_new_token_cb = nullptr, std::string* error = nullptr) override;
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
#include "core/util.h"
|
||||
#include "ggml.h"
|
||||
#include "tokenize_util.h"
|
||||
#include "vocab/vocab.h"
|
||||
|
||||
CLIPTokenizer::CLIPTokenizer(int pad_token_id, const std::string& merges_utf8_str) {
|
||||
CLIPTokenizer::CLIPTokenizer(int pad_token_id, const std::string& merges_utf8_str)
|
||||
: BPETokenizer(R"((?i:'s|'t|'re|'ve|'m|'ll|'d)|\p{L}+|\p{N}|[^\s\p{L}\p{N}]+)") {
|
||||
UNK_TOKEN = "<|endoftext|>";
|
||||
BOS_TOKEN = "<|startoftext|>";
|
||||
EOS_TOKEN = "<|endoftext|>";
|
||||
@@ -101,17 +101,3 @@ std::string CLIPTokenizer::normalize(const std::string& text) const {
|
||||
std::transform(normalized_text.begin(), normalized_text.end(), normalized_text.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return normalized_text;
|
||||
}
|
||||
|
||||
std::vector<std::string> CLIPTokenizer::token_split(const std::string& text) const {
|
||||
std::regex clip_pat(R"('s|'t|'re|'ve|'m|'ll|'d|[[:alpha:]]+|[[:digit:]]|[^[:space:][:alpha:][:digit:]]+)",
|
||||
std::regex::icase);
|
||||
std::sregex_iterator iter(text.begin(), text.end(), clip_pat);
|
||||
std::sregex_iterator end;
|
||||
|
||||
std::vector<std::string> result;
|
||||
for (; iter != end; ++iter) {
|
||||
result.emplace_back(iter->str());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ class CLIPTokenizer : public BPETokenizer {
|
||||
protected:
|
||||
void load_from_merges(const std::string& merges_utf8_str);
|
||||
std::string normalize(const std::string& text) const override;
|
||||
std::vector<std::string> token_split(const std::string& text) const override;
|
||||
|
||||
public:
|
||||
explicit CLIPTokenizer(int pad_token_id = 49407, const std::string& merges_utf8_str = "");
|
||||
|
||||
@@ -46,7 +46,9 @@ void GemmaTokenizer::load_from_merges(const std::string& merges_utf8_str, const
|
||||
bpe_len = rank;
|
||||
}
|
||||
|
||||
GemmaTokenizer::GemmaTokenizer(const std::string& merges_utf8_str, const std::string& vocab_utf8_str) {
|
||||
GemmaTokenizer::GemmaTokenizer(const std::string& merges_utf8_str, const std::string& vocab_utf8_str)
|
||||
: BPETokenizer("") {
|
||||
// Gemma replaces spaces with metaspace before its literal-space Split, so no regex boundaries apply.
|
||||
byte_level_bpe = false;
|
||||
byte_fallback = true;
|
||||
add_bos_token = true;
|
||||
@@ -189,164 +191,3 @@ GemmaTokenizer::GemmaTokenizer(const std::string& merges_utf8_str, const std::st
|
||||
load_from_merges(load_gemma_merges(), load_gemma_vocab_json());
|
||||
}
|
||||
}
|
||||
|
||||
std::string Gemma2Tokenizer::normalize(const std::string& text) const {
|
||||
std::string normalized = text;
|
||||
size_t pos = 0;
|
||||
while ((pos = normalized.find(' ', pos)) != std::string::npos) {
|
||||
normalized.replace(pos, 1, "\xE2\x96\x81");
|
||||
pos += 3;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
void Gemma2Tokenizer::load_from_merges(const std::string& merges_utf8_str, const std::string& vocab_utf8_str) {
|
||||
nlohmann::json vocab;
|
||||
try {
|
||||
vocab = nlohmann::json::parse(vocab_utf8_str);
|
||||
} catch (const nlohmann::json::parse_error&) {
|
||||
GGML_ABORT("invalid vocab json str");
|
||||
}
|
||||
for (const auto& [key, value] : vocab.items()) {
|
||||
std::u32string token = utf8_to_utf32(key);
|
||||
int i = value;
|
||||
encoder[token] = i;
|
||||
decoder[i] = token;
|
||||
}
|
||||
encoder_len = static_cast<int>(vocab.size());
|
||||
LOG_VERBOSE("vocab size: %d", encoder_len);
|
||||
|
||||
std::vector<std::u32string> merges = split_utf32(merges_utf8_str);
|
||||
std::vector<std::pair<std::u32string, std::u32string>> merge_pairs;
|
||||
for (const auto& merge : merges) {
|
||||
size_t space_pos = merge.find(' ');
|
||||
merge_pairs.emplace_back(merge.substr(0, space_pos), merge.substr(space_pos + 1));
|
||||
}
|
||||
LOG_VERBOSE("merges size %zu", merge_pairs.size());
|
||||
|
||||
int rank = 0;
|
||||
for (const auto& merge : merge_pairs) {
|
||||
bpe_ranks[merge] = rank++;
|
||||
}
|
||||
bpe_len = rank;
|
||||
}
|
||||
|
||||
Gemma2Tokenizer::Gemma2Tokenizer(const std::string& merges_utf8_str, const std::string& vocab_utf8_str) {
|
||||
byte_level_bpe = false;
|
||||
byte_fallback = true;
|
||||
add_bos_token = true;
|
||||
PAD_TOKEN = "<pad>";
|
||||
EOS_TOKEN = "<eos>";
|
||||
BOS_TOKEN = "<bos>";
|
||||
UNK_TOKEN = "<unk>";
|
||||
|
||||
PAD_TOKEN_ID = 0;
|
||||
EOS_TOKEN_ID = 1;
|
||||
BOS_TOKEN_ID = 2;
|
||||
UNK_TOKEN_ID = 3;
|
||||
|
||||
std::vector<std::string> special_tokens_before_merge = {
|
||||
PAD_TOKEN,
|
||||
EOS_TOKEN,
|
||||
BOS_TOKEN,
|
||||
UNK_TOKEN,
|
||||
"<mask>",
|
||||
"<2mass>",
|
||||
"[@BOS@]",
|
||||
};
|
||||
for (int i = 0; i <= 98; i++) {
|
||||
special_tokens_before_merge.push_back("<unused" + std::to_string(i) + ">");
|
||||
}
|
||||
special_tokens_before_merge.push_back("<start_of_turn>");
|
||||
special_tokens_before_merge.push_back("<end_of_turn>");
|
||||
for (int i = 1; i <= 31; i++) {
|
||||
special_tokens_before_merge.push_back(std::string(i, '\n'));
|
||||
}
|
||||
for (int i = 2; i <= 31; i++) {
|
||||
std::string whitespace_token;
|
||||
for (int j = 0; j < i; j++) {
|
||||
whitespace_token += "\xE2\x96\x81";
|
||||
}
|
||||
special_tokens_before_merge.push_back(whitespace_token);
|
||||
}
|
||||
std::vector<std::string> html_tokens = {
|
||||
"<table>",
|
||||
"<caption>",
|
||||
"<thead>",
|
||||
"<tbody>",
|
||||
"<tfoot>",
|
||||
"<tr>",
|
||||
"<th>",
|
||||
"<td>",
|
||||
"</table>",
|
||||
"</caption>",
|
||||
"</thead>",
|
||||
"</tbody>",
|
||||
"</tfoot>",
|
||||
"</tr>",
|
||||
"</th>",
|
||||
"</td>",
|
||||
"<h1>",
|
||||
"<h2>",
|
||||
"<h3>",
|
||||
"<h4>",
|
||||
"<h5>",
|
||||
"<h6>",
|
||||
"<blockquote>",
|
||||
"</h1>",
|
||||
"</h2>",
|
||||
"</h3>",
|
||||
"</h4>",
|
||||
"</h5>",
|
||||
"</h6>",
|
||||
"</blockquote>",
|
||||
"<strong>",
|
||||
"<em>",
|
||||
"<b>",
|
||||
"<i>",
|
||||
"<u>",
|
||||
"<s>",
|
||||
"<sub>",
|
||||
"<sup>",
|
||||
"<code>",
|
||||
"</strong>",
|
||||
"</em>",
|
||||
"</b>",
|
||||
"</i>",
|
||||
"</u>",
|
||||
"</s>",
|
||||
"</sub>",
|
||||
"</sup>",
|
||||
"</code>",
|
||||
};
|
||||
special_tokens_before_merge.insert(special_tokens_before_merge.end(),
|
||||
html_tokens.begin(),
|
||||
html_tokens.end());
|
||||
for (int i = 0; i <= 0xFF; i++) {
|
||||
char hex_buf[16];
|
||||
snprintf(hex_buf, sizeof(hex_buf), "<0x%02X>", i);
|
||||
special_tokens_before_merge.push_back(hex_buf);
|
||||
}
|
||||
|
||||
std::vector<std::string> special_tokens_after_merge = {
|
||||
"[toxicity=0]",
|
||||
};
|
||||
for (int i = 1; i <= 31; i++) {
|
||||
special_tokens_after_merge.insert(special_tokens_after_merge.begin() + i - 1,
|
||||
std::string(i, '\t'));
|
||||
}
|
||||
for (int i = 99; i <= 99; i++) {
|
||||
special_tokens_after_merge.push_back("<unused" + std::to_string(i) + ">");
|
||||
}
|
||||
|
||||
special_tokens = special_tokens_before_merge;
|
||||
special_tokens.insert(special_tokens.end(),
|
||||
special_tokens_after_merge.begin(),
|
||||
special_tokens_after_merge.end());
|
||||
|
||||
if (merges_utf8_str.size() > 0 && vocab_utf8_str.size() > 0) {
|
||||
load_from_merges(merges_utf8_str, vocab_utf8_str);
|
||||
} else {
|
||||
load_from_merges(load_gemma2_merges(), load_gemma2_vocab_json());
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,4 @@ public:
|
||||
explicit GemmaTokenizer(const std::string& merges_utf8_str = "", const std::string& vocab_utf8_str = "");
|
||||
};
|
||||
|
||||
class Gemma2Tokenizer : public BPETokenizer {
|
||||
protected:
|
||||
void load_from_merges(const std::string& merges_utf8_str, const std::string& vocab_utf8_str);
|
||||
std::string normalize(const std::string& text) const override;
|
||||
|
||||
public:
|
||||
explicit Gemma2Tokenizer(const std::string& merges_utf8_str = "", const std::string& vocab_utf8_str = "");
|
||||
};
|
||||
|
||||
#endif // __SD_TOKENIZERS_GEMMA_TOKENIZER_H__
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
#include "gpt_oss_tokenizer.h"
|
||||
|
||||
#include "core/util.h"
|
||||
#include "json.hpp"
|
||||
#include "vocab/vocab.h"
|
||||
|
||||
void GPTOSSTokenizer::load_from_merges(const std::string& merges_utf8_str, const std::string& vocab_utf8_str) {
|
||||
auto byte_unicode_pairs = bytes_to_unicode();
|
||||
byte_encoder = std::map<int, std::u32string>(byte_unicode_pairs.begin(), byte_unicode_pairs.end());
|
||||
for (auto& pair : byte_unicode_pairs) {
|
||||
byte_decoder[pair.second] = pair.first;
|
||||
}
|
||||
|
||||
nlohmann::json vocab;
|
||||
try {
|
||||
vocab = nlohmann::json::parse(vocab_utf8_str);
|
||||
} catch (const nlohmann::json::parse_error&) {
|
||||
GGML_ABORT("invalid vocab json str");
|
||||
}
|
||||
for (const auto& [key, value] : vocab.items()) {
|
||||
std::u32string token = utf8_to_utf32(key);
|
||||
int i = value;
|
||||
encoder[token] = i;
|
||||
decoder[i] = token;
|
||||
}
|
||||
encoder_len = static_cast<int>(encoder.size());
|
||||
for (auto& special_token : special_tokens) {
|
||||
auto token = utf8_to_utf32(special_token);
|
||||
encoder[token] = encoder_len;
|
||||
decoder[encoder_len] = token;
|
||||
encoder_len++;
|
||||
}
|
||||
encoder_len = static_cast<int>(encoder.size());
|
||||
LOG_VERBOSE("vocab size: %d", encoder_len);
|
||||
|
||||
std::vector<std::u32string> merges = split_utf32(merges_utf8_str);
|
||||
std::vector<std::pair<std::u32string, std::u32string>> merge_pairs;
|
||||
for (const auto& merge : merges) {
|
||||
size_t space_pos = merge.find(' ');
|
||||
merge_pairs.emplace_back(merge.substr(0, space_pos), merge.substr(space_pos + 1));
|
||||
}
|
||||
LOG_VERBOSE("merges size %zu", merge_pairs.size());
|
||||
|
||||
int rank = 0;
|
||||
for (const auto& merge : merge_pairs) {
|
||||
bpe_ranks[merge] = rank++;
|
||||
}
|
||||
bpe_len = rank;
|
||||
}
|
||||
|
||||
GPTOSSTokenizer::GPTOSSTokenizer(const std::string& merges_utf8_str, const std::string& vocab_utf8_str) {
|
||||
BOS_TOKEN = "<|startoftext|>";
|
||||
UNK_TOKEN = "<|endoftext|>";
|
||||
EOS_TOKEN = "<|endoftext|>";
|
||||
PAD_TOKEN = "<|endoftext|>";
|
||||
|
||||
BOS_TOKEN_ID = 199998;
|
||||
EOS_TOKEN_ID = 199999;
|
||||
UNK_TOKEN_ID = 199999;
|
||||
PAD_TOKEN_ID = 199999;
|
||||
|
||||
special_tokens = {
|
||||
"<|startoftext|>",
|
||||
"<|endoftext|>",
|
||||
"<|reserved_200000|>",
|
||||
"<|reserved_200001|>",
|
||||
"<|return|>",
|
||||
"<|constrain|>",
|
||||
"<|reserved_200004|>",
|
||||
"<|channel|>",
|
||||
"<|start|>",
|
||||
"<|end|>",
|
||||
"<|message|>",
|
||||
"<|reserved_200009|>",
|
||||
"<|reserved_200010|>",
|
||||
"<|reserved_200011|>",
|
||||
"<|call|>",
|
||||
"<|reserved_200013|>",
|
||||
"<|reserved_200014|>",
|
||||
"<|reserved_200015|>",
|
||||
"<|reserved_200016|>",
|
||||
"<|reserved_200017|>",
|
||||
"<|endofprompt|>",
|
||||
};
|
||||
|
||||
if (merges_utf8_str.size() > 0) {
|
||||
load_from_merges(merges_utf8_str, vocab_utf8_str);
|
||||
} else {
|
||||
load_from_merges(load_gpt_oss_merges(), load_gpt_oss_vocab_json());
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#ifndef __SD_TOKENIZERS_GPT_OSS_TOKENIZER_H__
|
||||
#define __SD_TOKENIZERS_GPT_OSS_TOKENIZER_H__
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "bpe_tokenizer.h"
|
||||
|
||||
class GPTOSSTokenizer : public BPETokenizer {
|
||||
protected:
|
||||
void load_from_merges(const std::string& merges_utf8_str, const std::string& vocab_utf8_str);
|
||||
|
||||
public:
|
||||
explicit GPTOSSTokenizer(const std::string& merges_utf8_str = "", const std::string& vocab_utf8_str = "");
|
||||
};
|
||||
|
||||
#endif // __SD_TOKENIZERS_GPT_OSS_TOKENIZER_H__
|
||||
@@ -42,7 +42,8 @@ void MistralTokenizer::load_from_merges(const std::string& merges_utf8_str, cons
|
||||
bpe_len = rank;
|
||||
}
|
||||
|
||||
MistralTokenizer::MistralTokenizer(const std::string& merges_utf8_str, const std::string& vocab_utf8_str) {
|
||||
MistralTokenizer::MistralTokenizer(const std::string& merges_utf8_str, const std::string& vocab_utf8_str)
|
||||
: BPETokenizer(R"([^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]*[\p{Ll}\p{Lm}\p{Lo}\p{M}]+|[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]+[\p{Ll}\p{Lm}\p{Lo}\p{M}]*|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n/]*|\s*[\r\n]+|\s+(?!\S)|\s+)") {
|
||||
add_bos_token = true;
|
||||
|
||||
UNK_TOKEN = "<unk>";
|
||||
|
||||
@@ -87,7 +87,8 @@ Qwen2Tokenizer::Qwen2Tokenizer(const std::string& merges_utf8_str)
|
||||
}
|
||||
|
||||
Qwen2Tokenizer::Qwen2Tokenizer(const std::string& merges_utf8_str,
|
||||
const std::vector<std::string>& special_tokens_override) {
|
||||
const std::vector<std::string>& special_tokens_override)
|
||||
: BPETokenizer(R"((?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+)") {
|
||||
UNK_TOKEN = "<|endoftext|>";
|
||||
EOS_TOKEN = "<|endoftext|>";
|
||||
PAD_TOKEN = "<|endoftext|>";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
std::vector<std::string> token_split(const std::string& text);
|
||||
std::vector<std::string> split_with_special_tokens(const std::string& text, const std::vector<std::string>& special_tokens);
|
||||
|
||||
#endif // __SD_TOKENIZERS_BPE_TOKENIZE_UTIL_H__
|
||||
#endif // __SD_TOKENIZERS_BPE_TOKENIZE_UTIL_H__
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,11 +1,7 @@
|
||||
#include "vocab.h"
|
||||
#include "clip_merges.hpp"
|
||||
#include "gemma2_merges.hpp"
|
||||
#include "gemma2_vocab.hpp"
|
||||
#include "gemma_merges.hpp"
|
||||
#include "gemma_vocab.hpp"
|
||||
#include "gpt_oss_merges.hpp"
|
||||
#include "gpt_oss_vocab.hpp"
|
||||
#include "mistral_merges.hpp"
|
||||
#include "mistral_vocab.hpp"
|
||||
#include "qwen_merges.hpp"
|
||||
@@ -51,23 +47,3 @@ std::string load_gemma_vocab_json() {
|
||||
std::string json_str(reinterpret_cast<const char*>(gemma_vocab_json_utf8_c_str), sizeof(gemma_vocab_json_utf8_c_str));
|
||||
return json_str;
|
||||
}
|
||||
|
||||
std::string load_gemma2_merges() {
|
||||
std::string merges_utf8_str(reinterpret_cast<const char*>(gemma2_merges_utf8_c_str), sizeof(gemma2_merges_utf8_c_str));
|
||||
return merges_utf8_str;
|
||||
}
|
||||
|
||||
std::string load_gemma2_vocab_json() {
|
||||
std::string json_str(reinterpret_cast<const char*>(gemma2_vocab_json_utf8_c_str), sizeof(gemma2_vocab_json_utf8_c_str));
|
||||
return json_str;
|
||||
}
|
||||
|
||||
std::string load_gpt_oss_merges() {
|
||||
std::string merges_utf8_str(reinterpret_cast<const char*>(gpt_oss_merges_utf8_c_str), sizeof(gpt_oss_merges_utf8_c_str));
|
||||
return merges_utf8_str;
|
||||
}
|
||||
|
||||
std::string load_gpt_oss_vocab_json() {
|
||||
std::string json_str(reinterpret_cast<const char*>(gpt_oss_vocab_json_utf8_c_str), sizeof(gpt_oss_vocab_json_utf8_c_str));
|
||||
return json_str;
|
||||
}
|
||||
@@ -11,9 +11,5 @@ std::string load_t5_tokenizer_json();
|
||||
std::string load_umt5_tokenizer_json();
|
||||
std::string load_gemma_merges();
|
||||
std::string load_gemma_vocab_json();
|
||||
std::string load_gemma2_merges();
|
||||
std::string load_gemma2_vocab_json();
|
||||
std::string load_gpt_oss_merges();
|
||||
std::string load_gpt_oss_vocab_json();
|
||||
|
||||
#endif // __SD_TOKENIZERS_VOCAB_VOCAB_H__
|
||||
@@ -14,6 +14,7 @@ UpscalerGGML::UpscalerGGML(int n_threads,
|
||||
std::string backend_spec,
|
||||
std::string params_backend_spec)
|
||||
: n_threads(n_threads),
|
||||
tensor_executor(n_threads > 0 ? n_threads : sd_get_num_physical_cores()),
|
||||
direct(direct),
|
||||
tile_size(tile_size),
|
||||
backend_spec(std::move(backend_spec)),
|
||||
@@ -35,6 +36,7 @@ void UpscalerGGML::set_max_graph_vram_bytes(size_t max_vram_bytes) {
|
||||
|
||||
bool UpscalerGGML::load_from_file(const std::string& esrgan_path,
|
||||
int n_threads) {
|
||||
sd::ParallelScope tensor_scope(&tensor_executor);
|
||||
ggml_log_set(sd_ggml_log_callback, nullptr);
|
||||
|
||||
std::string error;
|
||||
@@ -108,6 +110,7 @@ bool UpscalerGGML::load_from_file(const std::string& esrgan_path,
|
||||
}
|
||||
|
||||
sd::Tensor<float> UpscalerGGML::upscale_tensor(const sd::Tensor<float>& input_tensor) {
|
||||
sd::ParallelScope tensor_scope(&tensor_executor);
|
||||
sd::Tensor<float> upscaled;
|
||||
const int scale = esrgan_upscaler->config.scale;
|
||||
if (tile_size <= 0 || (input_tensor.shape()[0] <= tile_size && input_tensor.shape()[1] <= tile_size)) {
|
||||
@@ -142,6 +145,7 @@ sd::Tensor<float> UpscalerGGML::upscale_tensor(const sd::Tensor<float>& input_te
|
||||
}
|
||||
|
||||
sd_image_t UpscalerGGML::upscale(sd_image_t input_image, uint32_t upscale_factor) {
|
||||
sd::ParallelScope tensor_scope(&tensor_executor);
|
||||
// upscale_factor, unused for RealESRGAN_x4plus_anime_6B.pth
|
||||
sd_image_t upscaled_image = {0, 0, 0, nullptr};
|
||||
const int scale = esrgan_upscaler->config.scale;
|
||||
|
||||
@@ -17,6 +17,7 @@ struct UpscalerGGML {
|
||||
std::shared_ptr<ESRGAN> esrgan_upscaler;
|
||||
std::string esrgan_path;
|
||||
int n_threads;
|
||||
sd::ParallelExecutor tensor_executor;
|
||||
bool direct = false;
|
||||
int tile_size = 128;
|
||||
size_t max_graph_vram_bytes = 0;
|
||||
|
||||
Reference in New Issue
Block a user