mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-11-11 03:24:30 +00:00
refactor: move model-specific operators and fusions into backend extensions (#2253)
This commit is contained in:
1 parent
c6dfa9def1
commit
45a63ac460
68 files changed
+12173
-151
No files matched your search
+20
-13
@@ -32,8 +32,10 @@ cmake -S . -B build-upstream -DSD_USE_UPSTREAM_GGML=ON -DSD_GGML_SOURCE_DIR=../g
|
||||
cmake --build build-upstream --config Release
|
||||
```
|
||||
|
||||
The selected source tree supplies both the library and its private headers.
|
||||
Backend options such as `-DSD_CUDA=ON` apply to the selected tree as usual.
|
||||
Upstream compatibility mode uses the selected library and matching private
|
||||
headers. The patched GGML build uses public headers, including `ggml-ext.h` and
|
||||
`ggml-graph.h`. Backend options such as `-DSD_CUDA=ON` apply to the selected tree
|
||||
as usual.
|
||||
|
||||
`SD_USE_UPSTREAM_GGML` defaults to `OFF`, which enables the patched GGML
|
||||
extensions. Set it to `ON` when using upstream GGML; it selects the compatibility
|
||||
@@ -50,10 +52,12 @@ creating an inference context. Ordinary floating-point and shared GGML
|
||||
quantization types remain available, subject to backend operator support.
|
||||
|
||||
`SD_USE_SYSTEM_GGML=ON` instead links an installed GGML CMake package, located
|
||||
with `ggml_DIR` or `CMAKE_PREFIX_PATH`. In that mode, `SD_GGML_SOURCE_DIR` must
|
||||
point to the matching source tree for private headers. The installed library
|
||||
must use the same ABI settings as sd.cpp, including `GGML_MAX_NAME`.
|
||||
Set `SD_USE_UPSTREAM_GGML=ON` as well if the installed package is upstream GGML.
|
||||
with `ggml_DIR` or `CMAKE_PREFIX_PATH`. An installed patched package must include
|
||||
the extension and graph APIs; its source tree is not required for private
|
||||
headers. The installed library must use the same ABI settings as sd.cpp,
|
||||
including `GGML_MAX_NAME`. For an upstream package, also set
|
||||
`SD_USE_UPSTREAM_GGML=ON` and point `SD_GGML_SOURCE_DIR` to its matching source
|
||||
tree for the compatibility layer's private headers.
|
||||
|
||||
## WebP and WebM Support in Examples
|
||||
|
||||
@@ -95,8 +99,8 @@ cmake --build . --config Release
|
||||
|
||||
## Build with CUDA
|
||||
|
||||
Native SageAttention is included when using CUDA with patched GGML
|
||||
(`SD_USE_UPSTREAM_GGML=OFF`).
|
||||
The sd.cpp SageAttention extension is included when using CUDA with patched
|
||||
GGML (`SD_USE_UPSTREAM_GGML=OFF`).
|
||||
See [SageAttention](sage_attention.md) for GPU requirements and `--sage-attn` usage.
|
||||
|
||||
This provides GPU acceleration using NVIDIA GPU. Make sure to have the CUDA toolkit installed. You can download it from your Linux distro's package manager (e.g. `apt install nvidia-cuda-toolkit`) or from here: [CUDA Toolkit](https://developer.nvidia.com/cuda-downloads). Recommended to have at least 4 GB of VRAM.
|
||||
@@ -107,7 +111,7 @@ cmake .. -DSD_CUDA=ON
|
||||
cmake --build . --config Release
|
||||
```
|
||||
|
||||
CUDA builds with the bundled patched GGML use the CUTLASS submodule by default
|
||||
CUDA extensions use sd.cpp's `thirdparty/cutlass` submodule by default
|
||||
for eligible INT8 GEMM and CuTe attention operations. If its headers are missing,
|
||||
CMake stops with an initialization hint; it does not download them automatically.
|
||||
Run `git submodule update --init --recursive` from the repository root before
|
||||
@@ -117,14 +121,17 @@ To build with the existing CUDA implementations without CUTLASS, configure from
|
||||
the repository root with:
|
||||
|
||||
```shell
|
||||
cmake -S . -B build -DSD_CUDA=ON -DGGML_CUDA_CUTLASS_PATH:PATH=
|
||||
cmake -S . -B build -DSD_CUDA=ON -DSD_CUDA_CUTLASS_PATH:PATH=
|
||||
cmake --build build --config Release
|
||||
```
|
||||
|
||||
Alternatively, set `GGML_CUDA_CUTLASS_PATH` to another local CUTLASS source
|
||||
Alternatively, set `SD_CUDA_CUTLASS_PATH` to another local CUTLASS source
|
||||
directory (tested with 3.9.2). Existing CMake caches retain their configured path;
|
||||
pass `-UGGML_CUDA_CUTLASS_PATH` when reconfiguring to restore the bundled default.
|
||||
Non-CUDA builds do not require CUTLASS.
|
||||
pass `-USD_CUDA_CUTLASS_PATH` when reconfiguring to restore the bundled default.
|
||||
Existing `GGML_CUDA_CUTLASS_PATH` caches are migrated on first configuration:
|
||||
the old bundled path becomes `thirdparty/cutlass`, while custom paths and an
|
||||
empty disabled value are preserved. An explicitly set `SD_CUDA_CUTLASS_PATH`
|
||||
takes precedence. Non-CUDA builds do not require CUTLASS.
|
||||
|
||||
## Build with HipBLAS
|
||||
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
# ggml operator extensions
|
||||
|
||||
The patched ggml exposes `ggml-ext.h` for application-owned operators, kernel
|
||||
overrides, and graph fusion rules. Registrations are in-process and scoped to a
|
||||
backend device. Register them before capability queries or graph allocation;
|
||||
callback code and user data must remain alive until all dependent graphs and
|
||||
backends have been destroyed. Registration names are unique, and duplicate
|
||||
registrations fail rather than replacing live implementations.
|
||||
|
||||
## Operator definitions
|
||||
|
||||
`ggml_ext_register_op` registers a versioned name, attribute size, and an output
|
||||
metadata inference callback. `ggml_ext_op_create` creates an ordinary ggml tensor
|
||||
with explicit input dependencies. The initial interface supports one contiguous,
|
||||
out-of-place output with an existing `ggml_type`.
|
||||
|
||||
Attributes are copied into the unused portion of `op_params`. They are small,
|
||||
plain values; use `memcpy` when reading attributes that need stricter alignment.
|
||||
Auxiliary tensors must appear in `src`, not in an attribute pointer or user data.
|
||||
Registered operators use `GGML_OP_CUSTOM` internally, with an identifiable
|
||||
callback marker. Unsupported devices do not mistake them for CPU callbacks.
|
||||
These process-local descriptors are not a graph serialization format.
|
||||
|
||||
## Kernel selection
|
||||
|
||||
`ggml_ext_register_kernel(device, &desc)` associates a kernel with a built-in
|
||||
`ggml_op`, or with a registered custom operator. `supports` describes correctness
|
||||
constraints; `preferred` optionally decides whether to override an available
|
||||
native implementation. Higher priorities are considered first. When native
|
||||
execution is unsupported, a supported extension can run regardless of its
|
||||
preference callback.
|
||||
|
||||
Capability queries include registered single-node implementations, so the
|
||||
scheduler can select a device even when its native implementation rejects the
|
||||
operator. Callbacks inspect metadata only and must not recursively call the
|
||||
public capability query for the same device and operator. A fusion-only
|
||||
registration does not claim single-node support.
|
||||
|
||||
`ggml_ext_dev_supports_native_op` queries the original device implementation
|
||||
without extension dispatch. Fusion policies can use it to select a supported
|
||||
extension even when their performance preference normally favors native kernels.
|
||||
|
||||
`prepare` creates optional host-side plan state and reports workspace bytes.
|
||||
It can run during memory measurement, before tensor addresses are assigned, and
|
||||
must not launch work or allocate device buffers. Workspace is allocated by ggml
|
||||
before execution and included in scheduler memory estimates and buffer-size
|
||||
reports. `execute` receives the backend, workspace buffer, and current tensor
|
||||
addresses. `destroy` releases prepared state. A preparation or execution error
|
||||
is propagated; execution never retries a native implementation after launch.
|
||||
|
||||
Workspace is scratch storage shared by sequential extension steps in a plan;
|
||||
its capacity is the maximum request, not the sum across nodes. Contents are not
|
||||
preserved between calls. Queue work in backend execution order, and finish any
|
||||
private-stream use before subsequent backend work can access the workspace.
|
||||
Persistent state belongs in `prepare`/`destroy` state or explicit tensor inputs.
|
||||
|
||||
Overriding a built-in operator preserves its numerical and aliasing contract,
|
||||
including allocator reuse of input storage. Explicit views/in-place nodes are
|
||||
not intercepted by this first interface. Rebuild the scheduler allocation after
|
||||
changing graph metadata, selection policy, or registrations.
|
||||
|
||||
## Fusion
|
||||
|
||||
A `match` callback returns the number of consecutive nodes it can replace.
|
||||
The scheduler freezes this selection before allocating tensors. ggml checks
|
||||
compute flags, external uses, output flags, and views; the initial implementation
|
||||
accepts a conservative set of pure operators with only the final node exposed
|
||||
as the fused output. The rule remains responsible for mathematical equivalence,
|
||||
dtype, shape, layout, and hardware constraints. Internal views are accepted when
|
||||
their storage belongs to the fused subgraph. In-place intermediates are accepted
|
||||
only when their storage owner is an earlier node in the same match. External
|
||||
consumers and output flags still prevent eliminating those results. The final
|
||||
output must own storage, use a terminal `CPY`, or write back to an earlier owning
|
||||
node within the match. Internal writeback must cover exactly the owner's full
|
||||
shape, type, strides and storage offset, without external consumers of that
|
||||
owner. Output flags propagated to that owner are retained. Terminal `CPY` keeps
|
||||
its original destination and write semantics. The extension must handle input/output storage overlap, including
|
||||
allocator reuse, with scratch space when necessary. Other in-place writes are
|
||||
not eligible for extension fusion when they target storage outside the match.
|
||||
|
||||
An optional `optimize` callback runs before matching and allocation. Use
|
||||
`ggml_ext_graph_delay` to move a producer range after independent nodes. The
|
||||
helper rejects dependencies, unknown side effects, and writes that alias moved
|
||||
inputs or outputs. It can cross independent copies and known in-place operators
|
||||
when their storage does not alias the moved inputs or outputs. Owning dtype
|
||||
conversions can also be moved or crossed.
|
||||
Reordering must preserve edges and tensor metadata. Fusion legality is checked
|
||||
again after reordering.
|
||||
|
||||
External inputs stay allocated through the final node. Native graph optimization
|
||||
and execution operate on the remaining contiguous ranges, so native fusion cannot
|
||||
consume nodes selected by an extension. Intermediate allocations are retained
|
||||
in this initial implementation; only their computation is removed.
|
||||
|
||||
Automatic extension fusion is enabled in scheduler execution with one pipeline
|
||||
copy and without evaluation callbacks. Multiple pipeline copies and evaluation
|
||||
callbacks retain individual operations. Direct backend graph computation supports
|
||||
single-node extensions; it does not fuse an already allocated graph. Direct calls
|
||||
use temporary plans and synchronize before releasing them; scheduler plans and
|
||||
workspaces are reused across executions.
|
||||
|
||||
Use the backend graph APIs or the scheduler. The legacy CPU `ggml_graph_compute`
|
||||
and backend `graph_plan_*` interfaces do not dispatch these extensions.
|
||||
|
||||
## Backend services
|
||||
|
||||
Services are discovered through `ggml_backend_reg_get_proc_address`:
|
||||
|
||||
| Service | Purpose |
|
||||
| --- | --- |
|
||||
| `ggml_ext_cpu_parallel_v1` | Run an application callback on the CPU backend's configured thread pool, with its abort callback. |
|
||||
| `ggml_ext_cuda_context_v1` | Borrow the current CUDA device and stream, ordered with native work. |
|
||||
| `ggml_ext_cuda_device_v1` | Get the physical CUDA ordinal for capability checks before backend creation, including virtual ggml devices. |
|
||||
| `ggml_ext_cuda_blas_v1` | Borrow the current stream's cuBLAS handle during execution, with host scalar pointers. |
|
||||
| `ggml_ext_gpu_device_v1` | Query CUDA/HIP/MUSA runtime kind, physical ordinal, shared-memory limit, warp size, and INT8 BLAS capability. |
|
||||
| `ggml_ext_gpu_context_v1` | Borrow the matching runtime's device and execution stream. |
|
||||
| `ggml_ext_gpu_blas_v1` | Borrow the matching cuBLAS, hipBLAS, or muBLAS handle on that stream. |
|
||||
|
||||
The borrowed cuBLAS handle remains owned by the backend. Extensions must not retain
|
||||
or destroy it, or change its stream, pointer mode, math mode, or workspace. This
|
||||
CUDA-specific BLAS service is retained for existing callers; the runtime-aware
|
||||
services support CUDA, HIP, and MUSA with the same ownership rules. Extensions
|
||||
call vendor runtime and BLAS APIs directly. These services do not execute GEMM,
|
||||
quantization, or model fusion on the application's behalf.
|
||||
|
||||
Workspace is a backend buffer. Its base is a CUDA device pointer on CUDA; callers
|
||||
must use the representation appropriate to their backend. Vulkan extensions use
|
||||
the command recording services described below.
|
||||
|
||||
## sd.cpp integration
|
||||
|
||||
`src/core/ggml_extensions.cpp` and `.h` contain the shared operator schemas,
|
||||
validation, graph construction helpers, and backend registration entry point.
|
||||
CPU kernels and fusion rules live in `src/core/cpu/`, with registration collected
|
||||
in `cpu/registration.cpp`. CUDA-style and Vulkan implementations live in their
|
||||
respective backend directories.
|
||||
|
||||
`src/core/cpu/rope.cpp` registers the CPU implementation of
|
||||
`sd.rope_apply.v1` and a
|
||||
`RMSNorm -> Mul -> RoPE` fusion. The fusion accepts a one-dimensional weight
|
||||
vector. Both interleaved and split-half RoPE, partial rotation, and strided input
|
||||
rows are supported. Model construction uses the custom operator on CPU, CUDA,
|
||||
HIP, MUSA, and Vulkan. Other backends can use the scheduler's CPU fallback.
|
||||
The former `ggml_rope_apply` API, builtin opcode, and CPU implementation have
|
||||
been removed from ggml.
|
||||
|
||||
`src/core/cuda/rope.cu` supplies standalone CUDA RoPE
|
||||
and `RMSNorm -> Mul -> RoPE [-> Scale -> FP16 Copy]` fusion. CuTe attention shares
|
||||
its RMSNorm/RoPE kernel. Partial rotation and arbitrary float-aligned input and
|
||||
position-encoding strides are supported by the standalone kernel; RMSNorm fusion
|
||||
requires contiguous position encoding and weights. Copy fusion uses workspace
|
||||
when the destination overlaps an input. These registrations do not require
|
||||
CUTLASS. The old CUDA/HIP/MUSA RoPE dispatch is removed.
|
||||
|
||||
Shared CUDA-style kernels live in `src/core/cuda/` and are compiled separately
|
||||
for each enabled runtime. `SD_CUDA_NAMESPACE` expands to `sd_cuda`, `sd_hip`, or
|
||||
`sd_musa`, so implementations can coexist. `runtime.h` handles the small runtime
|
||||
differences, including logical 32-lane shuffles on HIP. The application defines
|
||||
and launches its kernels directly; it includes only public ggml headers.
|
||||
CMake generates the registration table from enabled backend options. The shared
|
||||
entry point uses one `SD_BACKEND_EXTENSIONS` macro; backend registrars verify the
|
||||
device kind, and disabled backends add no symbol references.
|
||||
|
||||
`SD_USE_UPSTREAM_GGML` excludes these registrations. The stable-diffusion public C
|
||||
API is unchanged. INT8 ConvRot retains its existing packed activation layout and
|
||||
separate weight scale tensor.
|
||||
|
||||
`src/core/cuda/cute_attention.cu` supplies CuTe Flash Attention on CUDA when
|
||||
CUTLASS headers are configured. It registers an override of `FLASH_ATTN_EXT`,
|
||||
with the existing sequence-length preference and native fallback. It also
|
||||
registers Q preparation (`RMSNorm -> Mul -> RoPE`), output scaling, and packed
|
||||
ConvRot output fusion. These kernels use only the public extension API, borrowed
|
||||
CUDA stream, and scheduler-owned workspace; they do not use ggml's CUDA pool or
|
||||
private conversion helpers. The existing CUDA fusion and CuTe disable environment
|
||||
variables are honored.
|
||||
|
||||
`src/core/cuda/i8_projection.cu` supplies the INT8 GEMM projection epilogues:
|
||||
`GEMM -> Reshape -> RMSNorm -> Mul -> RoPE`, optional scaling and FP16 copy,
|
||||
and K/V prefix concatenation with FP16 output. CuTe can consume a directly
|
||||
produced FP16 Q without materializing the FP32 GEMM or RoPE intermediates.
|
||||
Independent affine operands, including broadcasted constants, are computed before
|
||||
their normalization/projection
|
||||
chain when graph dependencies allow it. The attention optimizer can then move
|
||||
the complete five-node projection when safe.
|
||||
These rules accept custom RoPE nodes. They preserve the original
|
||||
large-matrix selection thresholds, full interleaved head dimension 128, packed
|
||||
activation layout, and separate weight scales. External consumers prevent fusion;
|
||||
aliasing terminal copies use scheduler workspace. The CUDA projection kernels
|
||||
and matchers now live in sd.cpp. `GGML_CUDA_FORCE_CUBLAS` disables these epilogues.
|
||||
|
||||
`src/core/ggml_extensions.cpp` defines `sd.linear_i8.v1` and
|
||||
`sd.quantize_i8_convrot.v1`. `src/core/cpu/int8.cpp` supplies their CPU
|
||||
implementations. The CPU kernels use the
|
||||
configured thread pool and scheduler workspace; the old CPU INT8 GEMM and ConvRot
|
||||
implementations have been removed from ggml.
|
||||
Model code keeps calling `ggml_ext_linear_i8_tensorwise`,
|
||||
`ggml_mul_mat_i8_tensorwise`, and `ggml_quantize_i8_convrot`. The latter two
|
||||
constructors are declared and implemented in sd.cpp and create custom nodes.
|
||||
Their former ggml declarations, implementations, ConvRot opcode, and native
|
||||
backend paths have been removed. CPU, CUDA/HIP/MUSA, and Vulkan register kernels
|
||||
and fusion rules through the extension interfaces.
|
||||
|
||||
For already quantized activations, `ggml_mul_mat_i8_tensorwise` accepts ordinary
|
||||
I8 weight and activation tensors. Its optional final `input_scale` parameter is a
|
||||
separate F32 tensor with one value per activation row. Weight scales have one
|
||||
value per output channel; weight scales and bias can be null. Existing calls
|
||||
omit `input_scale` and retain their original argument order. It computes `float(int32_dot(W, X)) * X_scale * W_scale + bias`, in that
|
||||
order. All scales are explicit graph dependencies; there is no quantized-tensor
|
||||
wrapper. Shapes use ggml's low-to-high dimension order. Call
|
||||
`sd_ggml_register_extensions` before scheduling these custom nodes.
|
||||
|
||||
`ggml_mul_mat_i8_tensorwise` also accepts F32 activations (quantized into temporary
|
||||
workspace) or the output of `ggml_quantize_i8_convrot`. ConvRot keeps the existing
|
||||
packed I8 activation rows, padded to a multiple of four rows, followed by F32 row
|
||||
scales. Weight scales remain separate. CPU ConvRot groups are 1, 4, 16, 64, or 256.
|
||||
Unsupported layouts and malformed scale shapes are rejected. Separate CPU quantize
|
||||
and GEMM phases propagate cancellation, and overlapping outputs use workspace.
|
||||
|
||||
`src/core/cuda/int8.cu` implements the same custom schemas on CUDA. It uses the
|
||||
borrowed cuBLAS handle for I8-by-I8-to-I32 GEMM and application-owned quantization,
|
||||
ConvRot, and rescaling kernels. F32 inputs are quantized into workspace; ordinary
|
||||
I8 inputs retain separate scale tensors and use a padded temporary only when
|
||||
needed by cuBLAS. Packed ConvRot inputs are consumed directly. Large ConvRot rows
|
||||
use multiple kernels when they exceed shared memory. Aliasing outputs use scratch.
|
||||
CUDA GEMM requires Turing or newer, contiguous tensors, K/N divisible by four,
|
||||
float-aligned inputs, and row counts that fit a padded signed 32-bit dimension;
|
||||
CUDA ConvRot uses groups of 256.
|
||||
Unsupported custom nodes can fall back to CPU through the scheduler.
|
||||
|
||||
When CUTLASS is configured, `src/core/cuda/i8_gemm.cu` supplies dense and SwiGLU
|
||||
INT8 GEMM kernels on Ampere or newer. It preserves the native tile selection,
|
||||
small-row policy, Stream-K scheduling, Ada column bands, and separate scaling
|
||||
order. The same extension matches `GEMM -> SwiGLU [-> ConvRot]` and row
|
||||
concatenation for custom GEMM nodes. Weight scales remain explicit
|
||||
inputs, including the gate/up scale permutation recorded on every SwiGLU launch.
|
||||
Plans reserve bounded Stream-K workspace, cache occupancy on first execution,
|
||||
and initialize barriers on every launch, so CUDA Graph replay observes updated
|
||||
inputs and scales. External consumers retain ordinary execution; overlapping
|
||||
outputs use workspace. The CUTLASS SwiGLU/ConvRot chain can write its packed
|
||||
output directly after GEMM, because quantization reads only its workspace
|
||||
intermediate. Shapes or alignments unsuitable for CUTLASS use cuBLAS.
|
||||
Custom I8 nodes also retain cuBLAS when the reduction could overflow I32, to
|
||||
preserve their previous accumulation behavior. Fusion disable and force-cuBLAS
|
||||
settings are honored. No CUTLASS headers are needed for the cuBLAS path.
|
||||
|
||||
The native CUDA/HIP INT8 GEMM, standalone ConvRot, and associated fusion paths
|
||||
have been removed from ggml. The shared application implementation calls cuBLAS
|
||||
or hipBLAS directly. HIP retains the existing architecture eligibility policy;
|
||||
MUSA does not advertise INT8 BLAS support and uses scheduler fallback for GEMM.
|
||||
Standalone ConvRot kernels are compiled for all three runtimes. CUTLASS
|
||||
implementations remain CUDA-specific.
|
||||
|
||||
`src/core/cuda/i8_gated_residual.cu` implements
|
||||
`INT8 GEMM -> [Scale] -> Mul(gate) -> Add(residual) -> [Concat]` as a registered
|
||||
CUDA fusion, for packed ConvRot inputs and ordinary I8 nodes with separate activation
|
||||
scales. The corresponding native CUDA fusion rules and CUTLASS residual epilogue
|
||||
have been removed from ggml. Its optimizer moves independent gate producers ahead
|
||||
of GEMM and optional scaling through `ggml_ext_graph_delay`, before fusion matching.
|
||||
The former native INT8 affine reorder rule has also moved into this extension.
|
||||
It preserves the original tile selection, small-row
|
||||
threshold, Stream-K scheduling, Ada column bands, and separate FP32 rounding of
|
||||
gate multiplication and residual addition. The plan bounds Stream-K workspace
|
||||
over possible occupancy values using host metadata; execution verifies the
|
||||
actual requirement against that bound. Device occupancy is cached on first
|
||||
execution; each launch rebuilds parameters for the current tensor addresses and
|
||||
initializes its Stream-K barriers. Overlapping outputs use scratch, except
|
||||
for an exact in-place residual without concatenation. External consumers and
|
||||
unsupported layouts retain ordinary operator execution. Fusion and force-cuBLAS
|
||||
disable settings are honored.
|
||||
|
||||
`src/core/cuda/convrot.cu` registers CUDA fusion for `Scale -> ConvRot`,
|
||||
`Norm -> Mul -> ConvRot`, `RMSNorm -> Mul(weight) -> Mul(modulation) -> Add ->
|
||||
Add(shift) -> ConvRot`, `SwiGLU -> ConvRot`, and `SiLU/Sigmoid -> Mul -> ConvRot`.
|
||||
ConvRot outputs are custom nodes. SwiGLU accepts packed,
|
||||
swapped, or separate gate/up inputs. Independent modulation and up-branch
|
||||
producers are moved ahead of their consumers through the safe graph-delay API.
|
||||
Reordering keeps an owning producer together with in-place consumers, such as
|
||||
Krea2's RMSNorm weight multiplication, so the alias checks can preserve their
|
||||
storage relationship.
|
||||
The application kernel preserves the original reduction order, thread selection,
|
||||
ConvRot row padding, and separate scale tail. Prepared plans cache operation
|
||||
metadata, use the borrowed CUDA stream, and reserve scheduler workspace for
|
||||
overlapping outputs. Long rows or external intermediate consumers retain
|
||||
ordinary execution. These fusions do not require CUTLASS and honor
|
||||
`GGML_CUDA_DISABLE_FUSION`.
|
||||
|
||||
The corresponding scale, normalization, and unary-activation fusion rules have
|
||||
been removed from ggml for CUDA and HIP. GEMM/SwiGLU/ConvRot chains are selected by the
|
||||
application INT8 extension before the standalone activation fusion.
|
||||
|
||||
`src/core/cuda/affine.cu` registers FP32 `Mul(gate) -> Add(residual)`,
|
||||
`Concat -> [Permute] -> FP16 Copy`, and `Contiguous -> Scale -> Permute -> FP16
|
||||
Copy` fusions. Gate multiplication and residual addition retain separate FP32
|
||||
rounding. Copy layouts and alignment requirements match the original CUDA
|
||||
kernels; unsupported layouts, external intermediate consumers, self-residual
|
||||
Add inputs, and fusion disabling retain ordinary operators. Internal in-place
|
||||
Add outputs are accepted when they overwrite the complete fused Mul allocation. Exact in-place residual
|
||||
or input addresses are safe for the pointwise kernel; partial overlap, broadcast
|
||||
gate overlap, and FP16 copy overlap use scheduler workspace. These registrations
|
||||
require no CUTLASS, compile for CUDA/HIP/MUSA, and support reusable CUDA Graph
|
||||
replay. Their former native fusion implementations have been removed from ggml.
|
||||
|
||||
CUDA kernels can opt into device replay with `ggml_ext_register_kernel_ex` and
|
||||
`GGML_EXT_KERNEL_CAPTURE_SAFE`. The original registration function retains its
|
||||
per-execution callback behavior, and the descriptor ABI is unchanged. Opting in
|
||||
means the callback enqueues replayable device work: it has no required
|
||||
per-execution host side effects or decisions based on tensor contents, and any
|
||||
private streams rejoin the borrowed stream. Lazy setup must complete during the
|
||||
first ordinary execution. Tensor metadata, attributes, and host plan parameters
|
||||
remain frozen until scheduler reallocation; tensor contents may change normally.
|
||||
|
||||
When every selected extension opts in, the CUDA backend can capture the whole
|
||||
scheduled plan, including its native fragments, into one CUDA Graph. The first
|
||||
execution warms up kernels and library state while retaining native fragment
|
||||
caches. The next stable execution captures
|
||||
and launches the graph; later calls replay it. Changing a tensor address or the
|
||||
execution stream invalidates the capture and repeats warmup, even when the graph
|
||||
UID is unchanged. Capture failures propagate without retrying partially executed
|
||||
work. An enclosing stream capture records the plan directly instead of starting
|
||||
a nested capture.
|
||||
|
||||
`GGML_EXT_KERNEL_REPLAY_REUSABLE` additionally permits reuse across scheduler
|
||||
plan rebuilds and requires `CAPTURE_SAFE`. Recorded work must depend only on
|
||||
complete tensor metadata, addresses, the device and immutable registration data.
|
||||
Captured resources must belong to the backend or the supplied workspace, never
|
||||
per-plan host state. Workspace contents must be initialized by recorded device
|
||||
work on each execution. Prepare/destroy still run for every rebuilt host plan.
|
||||
The backend cache retains only the executor and workspace; it compares graph
|
||||
structure, tensor metadata/addresses, selected kernels and workspace requirements
|
||||
exactly, without dereferencing old graph objects. A new metadata arena can reuse
|
||||
an equivalent capture. Each backend retains at most 16 retired plans and 256 MiB
|
||||
of their workspaces, evicting the oldest entries first. Larger workspaces retain
|
||||
plan-local capture only. Backend destruction synchronizes and releases the cache.
|
||||
This allows sd.cpp's per-step scheduler rebuilds to retain device replay.
|
||||
|
||||
Mixed plans containing an extension without the flag retain ordinary dispatch.
|
||||
Evaluation callbacks, multiple pipeline copies, and direct backend graph calls
|
||||
also retain their previous behavior. `GGML_CUDA_DISABLE_GRAPHS` disables whole-plan
|
||||
capture as well as native CUDA Graph caching. Native operations that cannot be
|
||||
captured retain the existing fragment execution path. Whole-plan replay requires
|
||||
CUDA VMM so native scratch addresses remain valid for the backend lifetime;
|
||||
legacy scratch pools retain the existing fragment path. HIP and MUSA do not yet
|
||||
provide this plan executor. sd.cpp's CUDA registrations opt in to reusable replay.
|
||||
|
||||
## Vulkan execution services
|
||||
|
||||
`ggml-ext-vulkan.h` exposes `ggml_ext_vulkan_v1` through the Vulkan backend
|
||||
registry. The versioned function table reports device limits and subgroup
|
||||
capabilities, resolves tensor/workspace byte ranges, dispatches application
|
||||
SPIR-V, and copies nonoverlapping buffer ranges. Extensions do not include
|
||||
private ggml Vulkan headers or own backend command buffers and descriptors.
|
||||
|
||||
Shaders use the `main` entry point, consecutive storage-buffer bindings starting
|
||||
at zero, and consecutive 32-bit specialization constants. Dispatch accepts actual
|
||||
workgroup counts. The backend validates ranges and dispatch limits, caches the
|
||||
complete shader/configuration per device, and releases pipelines with that
|
||||
device. Push constants and specialization values are copied; the backend does
|
||||
not retain application execution state. Pipeline creation occurs on execution,
|
||||
never during host-only plan preparation.
|
||||
|
||||
Bindings identify either a tensor or a backend buffer, with a byte offset and
|
||||
optional byte count. `buffer_info` returns an opaque storage identity and byte
|
||||
range for alias checks. Vulkan descriptor offsets are rounded down to the
|
||||
device's required alignment; shaders must add the returned `shader_offset`.
|
||||
Workspace bindings follow the same rules. Invalid or foreign-device ranges are
|
||||
rejected before recording commands. Kernel bounds and shader capability
|
||||
requirements remain the extension's responsibility.
|
||||
|
||||
Dispatch and copy use the backend's existing compute command context, descriptor
|
||||
pool, transfer waits, and memory barriers. They preserve ordering with native
|
||||
fragments and invalidate cached converted operands after writes. Normal backend
|
||||
synchronization completes queued work before host access or resource release.
|
||||
Vulkan copy ranges must be equal in size, nonoverlapping, and aligned to four
|
||||
bytes. There is no per-dispatch host wait or extension-owned queue.
|
||||
|
||||
Scheduled Vulkan plans share submission counters and FLOP thresholds across
|
||||
native fragments and extension callbacks. Native scratch is initialized once
|
||||
per plan invocation, with its offsets retained across fragments. Fragment
|
||||
boundaries do not force a
|
||||
queue submission, and the final pending commands are submitted when the plan
|
||||
returns. Extension dispatches and copies count toward the device's node limit.
|
||||
Every callback still executes on every invocation; this batching neither
|
||||
captures device replay nor retains plan state across scheduler rebuilds.
|
||||
Callback errors propagate without retry. Evaluation callbacks, direct backend
|
||||
calls, synchronous devices, serialized diagnostics, and timestamp profiling
|
||||
retain fragment execution.
|
||||
|
||||
`src/core/vulkan/rope.cpp` registers the custom RoPE implementation,
|
||||
plus `RMSNorm -> Mul -> RoPE` and `RMSNorm -> Mul -> RoPE -> Scale -> FP16 Copy`
|
||||
fusion. These kernels retain the original strides, partial-rotation behavior,
|
||||
128-channel subgroup reduction, and separate arithmetic rounding. Their SPIR-V
|
||||
is generated from application shaders with the native RoPE compilation policy;
|
||||
SPIR-V optimization is disabled because it changes the reduction results.
|
||||
Safe graph-delay rules bring independent producers ahead of fusion candidates.
|
||||
External intermediate consumers retain ordinary operators, and overlapping
|
||||
outputs use plan workspace. `GGML_VK_DISABLE_FUSION` is honored.
|
||||
|
||||
The corresponding Vulkan shader sources and native fusion rules have been
|
||||
removed from ggml. Unsupported custom nodes retain scheduler fallback to
|
||||
another supported backend.
|
||||
|
||||
RoPE Apply, INT8/ConvRot, CuTe attention, SageAttention, and Sol-Attn use
|
||||
application-owned implementations. This interface does not add dynamic dtypes.
|
||||
|
||||
`src/core/vulkan/int8.cpp` owns Vulkan INT8 GEMM, ConvRot, and their fusion rules.
|
||||
The Vulkan compute metadata service exposes shared-memory limits, subgroup
|
||||
arithmetic, and cooperative-matrix properties; shape selection stays in sd.cpp.
|
||||
Shader variants preserve the former tiling and compilation policy, including
|
||||
SwiGLU, residual, row-concatenation, and modulation paths. Kernels use public
|
||||
Vulkan dispatch, buffer, copy, and workspace services without private backend
|
||||
headers. Vulkan currently handles packed group-256 activations with separate
|
||||
weight scales; unsupported custom nodes fall back to CPU.
|
||||
|
||||
## SageAttention
|
||||
|
||||
The existing `ggml_sage_attn` entry point is declared and implemented in sd.cpp's
|
||||
common extension files. It constructs `sd.sage_attn.v1` with explicit Q/K/V inputs
|
||||
and scale/mode attributes. ggml has no Sage-specific opcode, allocation rule, or
|
||||
backend dispatch branch.
|
||||
|
||||
`src/core/cuda/sage_attention.cu` and its `sageattention/` device headers own the
|
||||
CUDA implementation. Quantization and temporary attention output use scheduler
|
||||
workspace, rather than an oversized output tensor. The implementation obtains
|
||||
the backend stream through the public GPU service and converts the final FP16
|
||||
output with a local CUDA kernel. It retains the SM80 compatibility path and the
|
||||
SageAttention2/2++ toolkit and architecture requirements. Capability checks use
|
||||
the application's compiled CUDA target as well as the physical device.
|
||||
|
||||
CPU, HIP, MUSA, and Vulkan do not implement this NVIDIA-specific kernel. Existing
|
||||
model-level capability checks retain flash/default attention for unsupported
|
||||
layers; a custom Sage node itself does not silently change algorithms.
|
||||
|
||||
## Sol-Attn
|
||||
|
||||
The existing `ggml_sol_attn` entry point now lives in the common sd.cpp extension
|
||||
files and constructs `sd.sol_attn.v1`. It accepts contiguous F32 Q/K/V tensors
|
||||
with shape `[128, tokens, heads, batch]`, a positive finite scale, and finite tau.
|
||||
The output remains F32 `[128, heads, tokens, batch]`. No current sd.cpp model or
|
||||
CLI path calls this entry point; the migration preserves its low-level behavior.
|
||||
|
||||
`src/core/cuda/sol_attention.cu` owns packing, block summaries, threshold
|
||||
selection, and the BF16 tensor-core kernel. Temporary storage is separate
|
||||
scheduler workspace, so the output allocation contains only the output tensor.
|
||||
The kernel uses the public backend stream service and direct CUDA/PTX operations.
|
||||
Registration requires CUDA 12 or newer, an SM80-or-newer device, and an application
|
||||
kernel compiled for SM80 or newer. CPU, HIP, MUSA, and Vulkan do not advertise
|
||||
support. ggml no longer contains a Sol-Attn opcode, constructor, allocation rule,
|
||||
or backend kernel.
|
||||
|
||||
## Public graph access
|
||||
|
||||
`ggml-graph.h` exposes leaf enumeration, append-only leaf construction, recorded
|
||||
use counts, mapped inference-graph copying, use-count copying for segments, and
|
||||
backend execution of a node range. Mapped copies retain node order and consumer
|
||||
counts from the original graph, including consumers outside a graph view. The
|
||||
caller owns tensor copies and remaps their storage and edges; invalid mappings
|
||||
leave the destination graph unchanged. Range execution uses already allocated
|
||||
tensors and retains the original graph's consumer counts.
|
||||
|
||||
Workspace measurement, graph cuts, and evaluation callbacks use these APIs.
|
||||
Normal sd.cpp builds require only ggml's public headers. Compatibility with an
|
||||
upstream ggml lacking this header is isolated in `src/core/ggml_compat.cpp`; that
|
||||
optional build still requires matching upstream private headers. The compatibility
|
||||
layer also retains the workaround for old Metal backends wrapping failed host
|
||||
mappings. The patched Metal backend returns null directly when mapping fails.
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
sd.cpp can load and execute ComfyUI `int8_tensorwise` safetensors with `convrot` metadata directly. The stored INT8 weights are not converted to another weight type at load time.
|
||||
|
||||
This requires the INT8 tensorwise/convrot extensions in the patched GGML.
|
||||
This uses sd.cpp's INT8 tensorwise/convrot operators through the patched GGML
|
||||
extension API. Schemas and graph builders live in sd.cpp, alongside the
|
||||
backend kernels and fusion rules; GGML supplies generic dispatch and workspace
|
||||
services. See [operator extensions](ggml_extensions.md) for the implementation
|
||||
and the preserved INT8 API entry points.
|
||||
Builds with `SD_USE_UPSTREAM_GGML=ON` reject these files during loading.
|
||||
|
||||
## Checkpoint format
|
||||
@@ -89,13 +93,14 @@ The packed runtime activation tensor contains the I8 activation rows and their f
|
||||
## Backend support
|
||||
|
||||
- CPU provides the portable regular Hadamard, activation quantization, INT8 matrix multiplication, and scale restoration implementations.
|
||||
- NVIDIA CUDA devices with compute capability 7.5 or newer use the native accelerated path. For H256, CUDA fuses the rotation, row-wise maximum reduction, and activation quantization. It uses cuBLAS for I8 x I8 to I32 GEMM and a CUDA kernel for scale restoration and bias addition.
|
||||
- NVIDIA CUDA devices with compute capability 7.5 or newer use the native accelerated path. For H256, CUDA fuses the rotation, row-wise maximum reduction, and activation quantization. Eligible shapes use the CUTLASS INT8 implementation when enabled; other supported shapes use cuBLAS for I8 x I8 to I32 GEMM and a CUDA kernel for scale restoration and bias addition.
|
||||
- AMD HIP devices in the CDNA, RDNA3 (including RDNA3.5), and RDNA4 families use the same INT8/H256 kernels with hipBLAS for I8 x I8 to I32 GEMM. Other AMD architectures fall back to CPU for INT8 matrix multiplication.
|
||||
- Vulkan provides native H256 activation quantization and INT8 matrix multiplication when the build and device support accelerated packed INT8 dot products. Unsupported configurations and GPU backends without these kernels use the backend scheduler to fall back to CPU.
|
||||
- MUSA shares the CUDA-style H256 activation kernels. INT8 GEMM falls back to CPU because the MUSA backend does not advertise INT8 BLAS support.
|
||||
- Vulkan provides H256 activation quantization and INT8 matrix multiplication when the build and device support accelerated packed INT8 dot products. Unsupported configurations and GPU backends without these kernels use the backend scheduler to fall back to CPU.
|
||||
|
||||
LoRA adapters are applied at runtime without modifying the INT8 weights. The INT8 convrot path computes the base linear output, while LoRA, LoHa, LoKr, and raw weight-difference adapters compute their output corrections from the original, unrotated activation and add them to the base output. `--lora-apply-mode auto` selects this path for models containing INT8 tensorwise weights. If `immediately` is requested, sd.cpp falls back to runtime application because merging an adapter would require dequantizing and rotating its weight update, then recalculating the per-row scales and requantizing the result.
|
||||
|
||||
The dedicated CUDA and HIP convrot activation paths currently require a group size of `256`; other supported group sizes use CPU execution.
|
||||
The shared CUDA/HIP/MUSA convrot activation paths currently require a group size of `256`; other supported group sizes use CPU execution.
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
the high-noise diffusion model when present. Python, PyTorch, and Triton are not
|
||||
required at build time or runtime.
|
||||
|
||||
The CUDA backend automatically selects a kernel supported by both the GPU and
|
||||
the compiled CUDA toolkit:
|
||||
The sd.cpp CUDA extension registers SageAttention through GGML's generic
|
||||
operator API. It selects a kernel supported by the GPU, compiled CUDA target,
|
||||
and toolkit:
|
||||
|
||||
| GPU / toolkit | Implementation |
|
||||
| --- | --- |
|
||||
|
||||
Reference in new issue
Block a user