refactor: register INT8 projection and attention fusion

This commit is contained in:
leejet committed 2026-10-31 20:01:41 +08:00
1 parent 1d2d31ffb6
commit ab232a474b
8 files changed
+491 -19

No files matched your search

+6 -3
View File
@@ -348,12 +348,15 @@ if (SD_CUDA)
target_sources(${SD_LIB} PRIVATE src/core/cuda/rope.cu) target_sources(${SD_LIB} PRIVATE src/core/cuda/rope.cu)
set_source_files_properties(src/core/cuda/rope.cu PROPERTIES COMPILE_OPTIONS "--use_fast_math") set_source_files_properties(src/core/cuda/rope.cu PROPERTIES COMPILE_OPTIONS "--use_fast_math")
if (GGML_CUDA_CUTLASS_PATH) if (GGML_CUDA_CUTLASS_PATH)
target_sources(${SD_LIB} PRIVATE src/core/cuda/cute_attention.cu) target_sources(${SD_LIB} PRIVATE src/core/cuda/cute_attention.cu src/core/cuda/i8_projection.cu)
set_source_files_properties(src/core/cuda/cute_attention.cu PROPERTIES set_source_files_properties(src/core/cuda/cute_attention.cu src/core/cuda/i8_projection.cu PROPERTIES
INCLUDE_DIRECTORIES "${GGML_CUDA_CUTLASS_PATH}/include" INCLUDE_DIRECTORIES "${GGML_CUDA_CUTLASS_PATH}/include"
COMPILE_OPTIONS "--use_fast_math;--extended-lambda;--expt-relaxed-constexpr") COMPILE_OPTIONS "--use_fast_math;--extended-lambda;--expt-relaxed-constexpr")
if (MSVC) if (MSVC)
set_property(SOURCE src/core/cuda/cute_attention.cu APPEND PROPERTY COMPILE_OPTIONS -Xcompiler=/Zc:preprocessor,/Zc:__cplusplus) set_property(SOURCE src/core/cuda/cute_attention.cu src/core/cuda/i8_projection.cu APPEND PROPERTY COMPILE_OPTIONS -Xcompiler=/Zc:preprocessor,/Zc:__cplusplus)
endif()
if (GGML_CUDA_FORCE_CUBLAS)
set_property(SOURCE src/core/cuda/i8_projection.cu APPEND PROPERTY COMPILE_DEFINITIONS SD_CUDA_FORCE_CUBLAS)
endif() endif()
set_property(SOURCE src/core/ggml_extensions.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_CUDA_CUTE_ATTENTION) set_property(SOURCE src/core/ggml_extensions.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_CUDA_CUTE_ATTENTION)
endif() endif()
+24 -11
View File
@@ -67,16 +67,20 @@ compute flags, external uses, output flags, and views; the initial implementatio
accepts a conservative set of pure operators with only the final node exposed accepts a conservative set of pure operators with only the final node exposed
as the fused output. The rule remains responsible for mathematical equivalence, as the fused output. The rule remains responsible for mathematical equivalence,
dtype, shape, layout, and hardware constraints. Internal views are accepted when dtype, shape, layout, and hardware constraints. Internal views are accepted when
their storage belongs to the fused subgraph. The final output must own storage, their storage belongs to the fused subgraph. In-place intermediates are accepted
except for a terminal `CPY`, which retains its original destination and write 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, except for a terminal `CPY`, which retains its original destination and write
semantics. The extension must handle input/output storage overlap, including semantics. The extension must handle input/output storage overlap, including
allocator reuse, with scratch space when necessary. Other in-place writes are allocator reuse, with scratch space when necessary. Other in-place writes are
not eligible for extension fusion. not eligible for extension fusion when they target storage outside the match.
An optional `optimize` callback runs before matching and allocation. Use An optional `optimize` callback runs before matching and allocation. Use
`ggml_ext_graph_delay` to move a producer range after independent nodes. The `ggml_ext_graph_delay` to move a producer range after independent nodes. The
helper rejects dependencies, unknown side effects, and writes that alias moved helper rejects dependencies, unknown side effects, and writes that alias moved
inputs or outputs. It can cross independent copies, including dtype conversions. 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 Reordering must preserve edges and tensor metadata. Fusion legality is checked
again after reordering. again after reordering.
@@ -140,12 +144,21 @@ 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 private conversion helpers. The existing CUDA fusion and CuTe disable environment
variables are honored. variables are honored.
The combined INT8 GEMM epilogue feeding FP16 Q directly into attention still needs `src/core/cuda/i8_projection.cu` supplies the INT8 GEMM projection epilogues:
to be externalized. The current extension can fuse RMSNorm/RoPE with attention, `GEMM -> Reshape -> RMSNorm -> Mul -> RoPE`, optional scaling and FP16 copy,
while other INT8 GEMM and standalone projection fusions retain their native and K/V prefix concatenation with FP16 output. CuTe can consume a directly
implementations. Native INT8 projection matchers do not recognize custom RoPE produced FP16 Q without materializing the FP32 GEMM or RoPE intermediates.
yet, so those graphs currently run GEMM followed by the external RoPE/attention Independent affine operands, including broadcasted constants, are computed before
path. Restoring their combined epilogues is part of the remaining migration. their normalization/projection
Vulkan execution services and kernels also remain pending. chain when graph dependencies allow it. The attention optimizer can then move
the complete five-node projection when safe.
These rules accept both builtin and 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.
Ordinary INT8 GEMM, ConvRot quantization, and their other fusion rules still need
to be externalized. Vulkan execution services and kernels also remain pending.
This implementation does not yet externalize all model-specific kernels or add This implementation does not yet externalize all model-specific kernels or add
dynamic dtypes. dynamic dtypes.
+1 -1
Submodule ggml updated: 7ffa0f0fe1...1223c158c8.
+22 -3
View File
@@ -44,6 +44,7 @@
#include <mutex> #include <mutex>
#include <type_traits> #include <type_traits>
#include <unordered_map> #include <unordered_map>
#include "core/cuda/i8_projection.h"
#include "core/cuda/rope.h" #include "core/cuda/rope.h"
#include "ggml-ext.h" #include "ggml-ext.h"
@@ -578,7 +579,14 @@ static int attention_match(ggml_backend_dev_t device, const ggml_cgraph* graph,
if (!fusion_enabled()) if (!fusion_enabled())
return 0; return 0;
int offset = 0; int offset = 0;
if (graph_node(graph, first)->op == GGML_OP_RMS_NORM) { if (graph_node(graph, first)->op == GGML_OP_MUL_MAT) {
if (first + 5 >= ggml_graph_n_nodes(const_cast<ggml_cgraph*>(graph)) ||
!sd_cuda_i8_rope_supports(device, graph_node(graph, first), graph_node(graph, first + 1),
graph_node(graph, first + 2), graph_node(graph, first + 3), graph_node(graph, first + 4)) ||
graph_node(graph, first + 5)->src[0] != graph_node(graph, first + 4))
return 0;
offset = 5;
} else if (graph_node(graph, first)->op == GGML_OP_RMS_NORM) {
if (first + 3 >= ggml_graph_n_nodes(const_cast<ggml_cgraph*>(graph)) || if (first + 3 >= ggml_graph_n_nodes(const_cast<ggml_cgraph*>(graph)) ||
!sd_cuda_rope_fusion_supports(graph_node(graph, first), graph_node(graph, first + 1), graph_node(graph, first + 2)) || !sd_cuda_rope_fusion_supports(graph_node(graph, first), graph_node(graph, first + 1), graph_node(graph, first + 2)) ||
graph_node(graph, first + 3)->src[0] != graph_node(graph, first + 2)) graph_node(graph, first + 3)->src[0] != graph_node(graph, first + 2))
@@ -601,6 +609,9 @@ static void attention_optimize(ggml_backend_dev_t device, ggml_cgraph* graph, vo
for (int j = i - 3; j >= 0 && j >= i - 32; --j) { for (int j = i - 3; j >= 0 && j >= i - 32; --j) {
if (attn->src[0] == graph_node(graph, j + 2) && if (attn->src[0] == graph_node(graph, j + 2) &&
sd_cuda_rope_fusion_supports(graph_node(graph, j), graph_node(graph, j + 1), graph_node(graph, j + 2))) { sd_cuda_rope_fusion_supports(graph_node(graph, j), graph_node(graph, j + 1), graph_node(graph, j + 2))) {
if (j >= 2 && sd_cuda_i8_rope_supports(device, graph_node(graph, j - 2), graph_node(graph, j - 1), graph_node(graph, j), graph_node(graph, j + 1), graph_node(graph, j + 2)) &&
ggml_ext_graph_delay(graph, j - 2, 5, i))
break;
ggml_ext_graph_delay(graph, j, 3, i); ggml_ext_graph_delay(graph, j, 3, i);
break; break;
} }
@@ -616,7 +627,8 @@ struct AttentionPlan {
static ggml_status attention_prepare(ggml_backend_t, ggml_tensor* const* nodes, int count, void** state, size_t* workspace_size, void*) { static ggml_status attention_prepare(ggml_backend_t, ggml_tensor* const* nodes, int count, void** state, size_t* workspace_size, void*) {
auto* plan = new AttentionPlan{}; auto* plan = new AttentionPlan{};
*state = plan; *state = plan;
plan->index = nodes[0]->op == GGML_OP_RMS_NORM ? 3 : 0; plan->index = nodes[0]->op == GGML_OP_MUL_MAT ? 5 : nodes[0]->op == GGML_OP_RMS_NORM ? 3
: 0;
GGML_ASSERT(plan->index < count); GGML_ASSERT(plan->index < count);
const auto* attn = nodes[plan->index]; const auto* attn = nodes[plan->index];
size_t size = 0; size_t size = 0;
@@ -671,7 +683,10 @@ static ggml_status attention_execute(const ggml_ext_execution_context* execution
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
const auto* src = attn->src[i]; const auto* src = attn->src[i];
const float scale = i == 0 ? param_f32(attn, 0) : 1.0f; const float scale = i == 0 ? param_f32(attn, 0) : 1.0f;
if (i == 0 && plan.index == 3) { if (i == 0 && plan.index == 5) {
if (sd_cuda_i8_rope_launch(stream, nodes, work[0], scale) != cudaSuccess)
return GGML_STATUS_FAILED;
} else if (i == 0 && plan.index == 3) {
if (sd_cuda_rms_rope_launch(stream, nodes, work[0], true, scale) != cudaSuccess) if (sd_cuda_rms_rope_launch(stream, nodes, work[0], true, scale) != cudaSuccess)
return GGML_STATUS_FAILED; return GGML_STATUS_FAILED;
} else if (i != 0 && src->type == GGML_TYPE_F16 && ggml_is_contiguous(src) && uintptr_t(src->data) % 16 == 0) { } else if (i != 0 && src->type == GGML_TYPE_F16 && ggml_is_contiguous(src) && uintptr_t(src->data) % 16 == 0) {
@@ -766,4 +781,8 @@ void sd_ggml_register_cuda_attention(ggml_backend_dev_t device) {
desc.preferred = nullptr; desc.preferred = nullptr;
desc.optimize = nullptr; desc.optimize = nullptr;
GGML_ASSERT(ggml_ext_register_kernel(device, &desc)); GGML_ASSERT(ggml_ext_register_kernel(device, &desc));
desc.name = "sd.i8_rope_cute_attention.cuda.v1";
desc.op = GGML_OP_MUL_MAT;
desc.priority = 400;
GGML_ASSERT(ggml_ext_register_kernel(device, &desc));
} }
+389
View File
@@ -0,0 +1,389 @@
#include "core/cuda/i8_projection.h"
#include "core/cuda/rope.h"
#include <cuda_fp16.h>
#include <cutlass/gemm/device/gemm_universal_adapter.h>
#include <cutlass/gemm/kernel/default_gemm_universal_with_visitor.h>
#include <algorithm>
#include <climits>
#include <cstdlib>
#include <cstring>
#include <cutlass/epilogue/threadblock/fusion/visitors.hpp>
#include <mutex>
#include <type_traits>
#include <unordered_map>
static __device__ __forceinline__ float warp_sum8(float value) {
#pragma unroll
for (int offset = 4; offset > 0; offset >>= 1)
value = __fadd_rn(value, __shfl_xor_sync(0xffffffff, value, offset, 8));
return value;
}
template <class Map, class Out, bool rms = true>
struct I8RopeStore : cutlass::epilogue::threadblock::VisitorAuxStore<Map, float, cutlass::FloatRoundStyle::round_to_nearest, cute::Stride<int64_t, cute::_1, int64_t>> {
using Base = cutlass::epilogue::threadblock::VisitorAuxStore<Map, float, cutlass::FloatRoundStyle::round_to_nearest, cute::Stride<int64_t, cute::_1, int64_t>>;
struct Arguments : Base::Arguments {
const float* weight;
const float* pe;
float eps;
float scale;
int64_t tokens;
half* half_output;
int64_t prefix_tokens;
};
using Params = Arguments;
using SharedStorage = typename Base::SharedStorage;
const Params* extra;
template <class Shape>
static constexpr Params to_underlying_arguments(Shape const&, Arguments const& args, void*) { return args; }
CUTLASS_HOST_DEVICE I8RopeStore(Params const& p, SharedStorage const& s)
: Base(p, s), extra(&p) {}
template <class CB>
struct Callbacks : CB {
const Params* extra;
CUTLASS_DEVICE Callbacks(CB const& base, const Params* p)
: CB(base), extra(p) {}
CUTLASS_DEVICE float sum32(float4 v) {
float a = warp_sum8(__fmul_rn(v.x, v.x));
float b = warp_sum8(__fmul_rn(v.y, v.y));
float c = warp_sum8(__fmul_rn(v.z, v.z));
float d = warp_sum8(__fmul_rn(v.w, v.w));
return __fadd_rn(__fadd_rn(a, c), __fadd_rn(b, d));
}
CUTLASS_DEVICE void end_step(int step) {
static_assert(Map::Detail::kAccessWidth == 16);
auto src = cute::filter(this->tC_rAux);
auto coords = cute::filter(this->tC_cAux(cute::_, cute::_, cute::_, step));
static_assert(cute::size(src) == 4);
CUTLASS_PRAGMA_UNROLL
for (int i = 0; i < cute::size(src); i += 2) {
float4 va = *reinterpret_cast<const float4*>(&src(i)), vb = *reinterpret_cast<const float4*>(&src(i + 1));
float norm = 1.0f;
if constexpr (rms) {
// Preserve the standalone RMSNorm reduction tree across four 32-element groups.
float sum = __fadd_rn(sum32(va), sum32(vb));
sum = __fadd_rn(sum, __shfl_xor_sync(0xffffffff, sum, 8));
norm = rsqrtf(sum / 128.f + extra->eps);
}
CUTLASS_PRAGMA_UNROLL
for (int j = 0; j < 2; ++j) {
auto coord = coords(i + j);
int row = cute::get<0>(coord), col = cute::get<1>(coord);
int heads = cute::get<1>(this->problem_shape) / 128;
if (cute::elem_less(coord, this->problem_shape)) {
float4 value = j ? vb : va;
int local = col % 128;
int64_t token = row % extra->tokens, batch = row / extra->tokens, head = col / 128;
float4 r = value;
if constexpr (rms) {
float4 weight = ((const float4*)extra->weight)[local / 4];
value.x = __fmul_rn(__fmul_rn(norm, value.x), weight.x);
value.y = __fmul_rn(__fmul_rn(norm, value.y), weight.y);
value.z = __fmul_rn(__fmul_rn(norm, value.z), weight.z);
value.w = __fmul_rn(__fmul_rn(norm, value.w), weight.w);
float4 p0 = ((const float4*)extra->pe)[token * 64 + local / 2], p1 = ((const float4*)extra->pe)[token * 64 + local / 2 + 1];
r.x = __fadd_rn(__fmul_rn(value.x, p0.x), __fmul_rn(value.y, p0.y));
r.y = __fadd_rn(__fmul_rn(value.x, p0.z), __fmul_rn(value.y, p0.w));
r.z = __fadd_rn(__fmul_rn(value.z, p1.x), __fmul_rn(value.w, p1.y));
r.w = __fadd_rn(__fmul_rn(value.z, p1.z), __fmul_rn(value.w, p1.w));
}
int64_t offset = ((batch * heads + head) * (extra->tokens + extra->prefix_tokens) + token + extra->prefix_tokens) * 128 + local;
if constexpr (std::is_same<Out, half>::value) {
half2 a = __floats2half2_rn(__fmul_rn(r.x, extra->scale), __fmul_rn(r.y, extra->scale));
half2 b = __floats2half2_rn(__fmul_rn(r.z, extra->scale), __fmul_rn(r.w, extra->scale));
half2* dst = (half2*)(extra->half_output + offset);
dst[0] = a;
dst[1] = b;
} else {
*((float4*)(this->params_ptr->ptr_aux + offset)) = r;
}
}
}
}
}
};
template <class Shape>
CUTLASS_DEVICE auto get_callbacks(cutlass::gemm::GemmCoord tile, int tid, Shape shape) {
auto cb = Base::get_callbacks(tile, tid, shape);
return Callbacks<decltype(cb)>(cb, extra);
}
};
template <class Out, bool RMS = true>
struct ProjectionKernel {
using Tile = cutlass::gemm::GemmShape<128, 256, 64>;
using Warp = cutlass::gemm::GemmShape<64, 64, 64>;
using Swizzle = cutlass::gemm::threadblock::GemmIdentityThreadblockSwizzle<>;
using Map = cutlass::epilogue::threadblock::OutputTileThreadLayout<Tile, Warp, float, 4, 1>;
using Col = cutlass::epilogue::threadblock::VisitorColBroadcast<Map, float, cute::Stride<cute::_1, cute::_0, int>>;
using Row = cutlass::epilogue::threadblock::VisitorRowBroadcast<Map, float, cute::Stride<cute::_0, cute::_1, int>>;
using Mul = cutlass::epilogue::threadblock::VisitorCompute<cutlass::multiplies, float, float, cutlass::FloatRoundStyle::round_to_nearest>;
using ScaleX = cutlass::epilogue::threadblock::Sm80EVT<Mul, cutlass::epilogue::threadblock::VisitorAccFetch, Col>;
using ScaleW = cutlass::epilogue::threadblock::Sm80EVT<Mul, ScaleX, Row>;
using Store = I8RopeStore<Map, Out, RMS>;
using EVT = cutlass::epilogue::threadblock::Sm80EVT<Store, ScaleW>;
using Kernel = typename cutlass::gemm::kernel::DefaultGemmWithVisitor<
int8_t,
cutlass::layout::RowMajor,
cutlass::ComplexTransform::kNone,
16,
int8_t,
cutlass::layout::ColumnMajor,
cutlass::ComplexTransform::kNone,
16,
float,
cutlass::layout::RowMajor,
4,
int,
float,
cutlass::arch::OpClassTensorOp,
cutlass::arch::Sm80,
Tile,
Warp,
cutlass::gemm::GemmShape<16, 8, 32>,
EVT,
Swizzle,
4,
cutlass::arch::OpMultiplyAddSaturate>::GemmKernel;
using Gemm = cutlass::gemm::device::GemmUniversalAdapter<Kernel>;
};
static float param_f32(const ggml_tensor* tensor, int index) {
float result;
std::memcpy(&result, tensor->op_params + index, sizeof(result));
return result;
}
static bool device_supports(ggml_backend_dev_t device) {
#ifdef SD_CUDA_FORCE_CUBLAS
(void)device;
return false;
#else
static std::mutex mutex;
static std::unordered_map<ggml_backend_dev_t, bool> cache;
std::lock_guard<std::mutex> lock(mutex);
auto found = cache.find(device);
if (found != cache.end())
return found->second;
auto reg = ggml_backend_dev_backend_reg(device);
auto query = reinterpret_cast<ggml_ext_cuda_device_fn>(ggml_backend_reg_get_proc_address(reg, "ggml_ext_cuda_device_v1"));
int ordinal = query ? query(device) : -1;
cudaDeviceProp props = {};
size_t shared = std::max({sizeof(typename ProjectionKernel<float>::Kernel::SharedStorage),
sizeof(typename ProjectionKernel<half>::Kernel::SharedStorage), sizeof(typename ProjectionKernel<half, false>::Kernel::SharedStorage)});
bool supported = ordinal >= 0 && cudaGetDeviceProperties(&props, ordinal) == cudaSuccess && props.major >= 8 && props.sharedMemPerBlockOptin >= shared;
cache.emplace(device, supported);
return supported;
#endif
}
static bool aligned(const ggml_tensor* tensor) {
size_t offset = 0;
while (tensor->view_src) {
offset += tensor->view_offs;
tensor = tensor->view_src;
}
return (reinterpret_cast<uintptr_t>(tensor->data) + offset) % 16 == 0;
}
static bool projection_supports(ggml_backend_dev_t device, const ggml_tensor* mat, const ggml_tensor* view) {
const auto* w = mat->src[0];
const auto* x = mat->src[1];
const auto* ws = mat->src[2];
if (mat->op != GGML_OP_MUL_MAT || mat->type != GGML_TYPE_F32 || mat->op_params[4] <= 0 || !w || !x || !ws || mat->src[3] ||
w->type != GGML_TYPE_I8 || x->type != GGML_TYPE_I8 || ws->type != GGML_TYPE_F32 ||
w->ne[2] != 1 || w->ne[3] != 1 || mat->ne[0] != w->ne[1] || ggml_nelements(ws) != mat->ne[0] ||
!ggml_is_contiguous(mat) || !ggml_is_contiguous(w) || !ggml_is_contiguous(x) || !ggml_is_contiguous(ws) ||
!aligned(w) || !aligned(x) || !aligned(ws) || !aligned(mat) ||
view->op != GGML_OP_RESHAPE || view->src[0] != mat || view->view_offs != 0 || !ggml_is_contiguous(view) ||
view->ne[0] != 128 || view->ne[0] * view->ne[1] != mat->ne[0] || ggml_nelements(view) != ggml_nelements(mat))
return false;
const int64_t rows = ggml_nrows(mat), n = mat->ne[0], k = w->ne[0];
if (rows < 4096 || rows > INT_MAX || n < 4096 || n > INT_MAX || k < 4096 || k > INT_MAX || n % 256 || k % 64 || n / 256 > 65535 ||
x->ne[0] != k || x->ne[1] != (rows + 3) / 4 * 4 + (rows * 4 + k - 1) / k || x->ne[2] != 1 || x->ne[3] != 1)
return false;
return device_supports(device);
}
bool sd_cuda_i8_rope_supports(ggml_backend_dev_t device, const ggml_tensor* mat, const ggml_tensor* view, const ggml_tensor* norm, const ggml_tensor* mul, const ggml_tensor* rope) {
if (!projection_supports(device, mat, view) || norm->src[0] != view ||
!sd_cuda_rope_fusion_supports(norm, mul, rope) || rope->src[1]->ne[2] != 64 || !aligned(mul->src[1]) || !aligned(rope->src[1]))
return false;
int32_t interleaved;
std::memcpy(&interleaved, ggml_ext_op_get(rope) ? ggml_ext_op_attributes(rope) : rope->op_params, sizeof(interleaved));
return interleaved != 0;
}
template <class Out, bool RMS = true>
static cudaError_t projection_launch(cudaStream_t stream, const ggml_tensor* mat, const ggml_tensor* view, const ggml_tensor* norm, const ggml_tensor* mul, const ggml_tensor* rope, void* output, float scale, int64_t prefix_tokens) {
using P = ProjectionKernel<Out, RMS>;
using Gemm = typename P::Gemm;
const int rows = int(ggml_nrows(mat)), n = int(mat->ne[0]), k = int(mat->src[0]->ne[0]);
const auto* x = static_cast<const int8_t*>(mat->src[1]->data);
const auto* w = static_cast<const int8_t*>(mat->src[0]->data);
const auto* xs = reinterpret_cast<const float*>(x + int64_t(k) * ((int64_t(rows) + 3) / 4 * 4));
const auto* ws = static_cast<const float*>(mat->src[2]->data);
const auto* nw = RMS ? static_cast<const float*>(mul->src[1]->data) : nullptr;
const auto* pe = RMS ? static_cast<const float*>(rope->src[1]->data) : nullptr;
// The visitor forms FP32 addresses even when its only writes target the FP16 output.
float* address_space = std::is_same<Out, half>::value ? static_cast<float*>(mat->data) : static_cast<float*>(output);
if ((reinterpret_cast<uintptr_t>(x) | reinterpret_cast<uintptr_t>(w) | reinterpret_cast<uintptr_t>(xs) |
reinterpret_cast<uintptr_t>(ws) | reinterpret_cast<uintptr_t>(nw) | reinterpret_cast<uintptr_t>(pe) |
reinterpret_cast<uintptr_t>(address_space)) %
16 ||
reinterpret_cast<uintptr_t>(output) % 8)
return cudaErrorInvalidValue;
typename P::EVT::Arguments evt = {{{{}, {xs, 0.f, {}}, {}}, {ws, 0.f, {}}, {}},
{{address_space, {n, cute::_1{}, int64_t(rows) * n}}, nw, pe, RMS ? param_f32(norm, 0) : 0.f, scale, view->ne[2], static_cast<half*>(output), prefix_tokens}};
typename Gemm::Arguments args(cutlass::gemm::GemmUniversalMode::kGemm, {rows, n, k}, 1, evt, x, w, nullptr, nullptr,
int64_t(rows) * k, int64_t(n) * k, 0, 0, k, k, 0, 0);
Gemm gemm;
if (gemm.can_implement(args) != cutlass::Status::kSuccess)
return cudaErrorInvalidValue;
if (gemm.initialize(args, nullptr, stream) != cutlass::Status::kSuccess || gemm(stream) != cutlass::Status::kSuccess)
return cudaErrorUnknown;
return cudaGetLastError();
}
cudaError_t sd_cuda_i8_rope_launch(cudaStream_t stream, ggml_tensor* const* nodes, void* output, float scale) {
return projection_launch<half>(stream, nodes[0], nodes[1], nodes[2], nodes[3], nodes[4], output, scale, 0);
}
static const ggml_tensor* node_at(const ggml_cgraph* graph, int index) {
return ggml_graph_node(const_cast<ggml_cgraph*>(graph), index);
}
static int concat_match(const ggml_cgraph* graph, int first, bool key) {
const int offset = key ? 5 : 2, count = key ? 7 : 5;
if (first + count > ggml_graph_n_nodes(const_cast<ggml_cgraph*>(graph)))
return 0;
const auto* concat = node_at(graph, first + offset);
const auto* copy = node_at(graph, first + count - 1);
if (concat->op != GGML_OP_CONCAT || concat->op_params[0] != (key ? 1 : 2) || concat->src[1] != node_at(graph, first + offset - 1) ||
copy->op != GGML_OP_CPY || copy->src[0] != node_at(graph, first + count - 2) || copy->type != GGML_TYPE_F16 ||
!ggml_is_contiguous(copy) || !ggml_are_same_shape(copy, copy->src[0]))
return 0;
for (int i = 0; i < 2; ++i)
if (!concat->src[i] || concat->src[i]->type != GGML_TYPE_F32 || !ggml_is_contiguous(concat->src[i]))
return 0;
const auto* view = node_at(graph, first + 1);
const auto* prefix = concat->src[0];
if (prefix->ne[key ? 1 : 2] > INT_MAX - view->ne[2] || (ggml_nelements(prefix) + 255) / 256 > INT_MAX)
return 0;
if (!key) {
const auto* permute = node_at(graph, first + 3);
if (permute->op != GGML_OP_PERMUTE || permute->src[0] != concat || permute->op_params[0] != 0 || permute->op_params[1] != 2 ||
permute->op_params[2] != 1 || permute->op_params[3] != 3)
return 0;
}
return count;
}
static int projection_match(ggml_backend_dev_t device, const ggml_cgraph* graph, int first, void*) {
static const bool disabled = [] {const char* value=std::getenv("GGML_CUDA_DISABLE_FUSION");return value&&std::atoi(value)!=0; }();
if (disabled || first + 4 >= ggml_graph_n_nodes(const_cast<ggml_cgraph*>(graph)))
return 0;
const auto* mat = node_at(graph, first);
const auto* view = node_at(graph, first + 1);
const auto* norm = node_at(graph, first + 2);
const bool key = norm->op == GGML_OP_RMS_NORM;
if (key) {
if (!sd_cuda_i8_rope_supports(device, mat, view, norm, node_at(graph, first + 3), node_at(graph, first + 4)))
return 0;
if (int count = concat_match(graph, first, true))
return count;
if (first + 6 < ggml_graph_n_nodes(const_cast<ggml_cgraph*>(graph))) {
const auto* rope = node_at(graph, first + 4);
const auto* scale = node_at(graph, first + 5);
const auto* copy = node_at(graph, first + 6);
if (scale->op == GGML_OP_SCALE && scale->src[0] == rope && param_f32(scale, 1) == 0.f && copy->op == GGML_OP_CPY && copy->src[0] == scale &&
copy->type == GGML_TYPE_F16 && ggml_are_same_shape(rope, scale) && ggml_are_same_shape(rope, copy) && ggml_is_contiguous(copy))
return 7;
}
return 5;
}
return projection_supports(device, mat, view) ? concat_match(graph, first, false) : 0;
}
template <bool Aligned>
static __global__ void prefix_copy(const float* input, half2* output, int64_t count4, int64_t prefix, int64_t tokens, int64_t heads, bool permuted) {
const int64_t i = int64_t(blockIdx.x) * blockDim.x + threadIdx.x;
if (i >= count4)
return;
const int64_t col = i % 32, row = i / 32;
const int64_t token = row % prefix, head = (row / prefix) % heads, batch = row / (prefix * heads);
const int64_t source_row = permuted ? (batch * prefix + token) * heads + head : (batch * heads + head) * prefix + token;
const float* source = input + source_row * 128 + col * 4;
float4 value;
if constexpr (Aligned)
value = *reinterpret_cast<const float4*>(source);
else
value = make_float4(source[0], source[1], source[2], source[3]);
const int64_t target = ((batch * heads + head) * (prefix + tokens) + token) * 32 + col;
output[2 * target] = __floats2half2_rn(value.x, value.y);
output[2 * target + 1] = __floats2half2_rn(value.z, value.w);
}
static ggml_status projection_prepare(ggml_backend_t, ggml_tensor* const* nodes, int count, void**, size_t* workspace, void*) {
*workspace = ggml_nbytes(nodes[count - 1]);
return GGML_STATUS_SUCCESS;
}
static void projection_destroy(void*, void*) {}
static bool overlaps(const ggml_tensor* a, const ggml_tensor* b) {
const auto x = reinterpret_cast<uintptr_t>(a->data), y = reinterpret_cast<uintptr_t>(b->data);
return x <= y ? y - x < ggml_nbytes(a) : x - y < ggml_nbytes(b);
}
static ggml_status projection_execute(const ggml_ext_execution_context* execution, ggml_tensor* const* nodes, int count, void*, void*) {
const auto* mat = nodes[0];
const auto* view = nodes[1];
const bool key = nodes[2]->op == GGML_OP_RMS_NORM;
const auto* concat = nodes[key ? count == 7 ? 5 : 4 : 2];
const bool joined = concat->op == GGML_OP_CONCAT;
auto* dst = nodes[count - 1];
auto reg = ggml_backend_dev_backend_reg(ggml_backend_get_device(execution->backend));
auto query = reinterpret_cast<ggml_ext_cuda_context_fn>(ggml_backend_reg_get_proc_address(reg, "ggml_ext_cuda_context_v1"));
ggml_ext_cuda_context context;
if (!query || !query(execution->backend, &context))
return GGML_STATUS_FAILED;
auto stream = static_cast<cudaStream_t>(context.stream);
bool alias = reinterpret_cast<uintptr_t>(dst->data) % (dst->type == GGML_TYPE_F32 ? 16 : 8) != 0;
for (const auto* input : {mat->src[0], mat->src[1], mat->src[2], key ? nodes[3]->src[1] : nullptr, key ? nodes[4]->src[1] : nullptr, joined ? concat->src[0] : nullptr})
if (input)
alias |= overlaps(dst, input);
void* output = alias ? ggml_backend_buffer_get_base(execution->workspace) : dst->data;
int64_t prefix = joined ? concat->src[0]->ne[key ? 1 : 2] : 0;
float scale = key && count == 7 && !joined ? param_f32(nodes[5], 0) : 1.f;
cudaError_t error;
if (dst->type == GGML_TYPE_F32)
error = projection_launch<float>(stream, mat, view, nodes[2], nodes[3], nodes[4], output, scale, 0);
else if (key)
error = projection_launch<half>(stream, mat, view, nodes[2], nodes[3], nodes[4], output, scale, prefix);
else
error = projection_launch<half, false>(stream, mat, view, nullptr, nullptr, nullptr, output, scale, prefix);
if (error == cudaSuccess && joined && prefix > 0) {
const auto* input = concat->src[0];
const int64_t count4 = ggml_nelements(input) / 4;
auto kernel = reinterpret_cast<uintptr_t>(input->data) % 16 == 0 ? prefix_copy<true> : prefix_copy<false>;
kernel<<<static_cast<unsigned int>((count4 + 255) / 256), 256, 0, stream>>>(static_cast<const float*>(input->data), static_cast<half2*>(output), count4, prefix, view->ne[2], view->ne[1], !key);
error = cudaGetLastError();
}
if (error == cudaSuccess && alias)
error = cudaMemcpyAsync(dst->data, output, ggml_nbytes(dst), cudaMemcpyDeviceToDevice, stream);
return error == cudaSuccess ? GGML_STATUS_SUCCESS : GGML_STATUS_FAILED;
}
void sd_ggml_register_cuda_i8_projection(ggml_backend_dev_t device) {
ggml_ext_kernel_desc desc = {};
desc.abi_version = GGML_EXT_ABI_VERSION;
desc.name = "sd.i8_projection.cuda.v1";
desc.op = GGML_OP_MUL_MAT;
desc.priority = 350;
desc.match = projection_match;
desc.prepare = projection_prepare;
desc.execute = projection_execute;
desc.destroy = projection_destroy;
GGML_ASSERT(ggml_ext_register_kernel(device, &desc));
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef __SD_CORE_CUDA_I8_PROJECTION_H__
#define __SD_CORE_CUDA_I8_PROJECTION_H__
#include <cuda_runtime.h>
#include "ggml-ext.h"
bool sd_cuda_i8_rope_supports(ggml_backend_dev_t device, const ggml_tensor* mat, const ggml_tensor* view, const ggml_tensor* norm, const ggml_tensor* mul, const ggml_tensor* rope);
cudaError_t sd_cuda_i8_rope_launch(cudaStream_t stream, ggml_tensor* const* nodes, void* output, float scale);
void sd_ggml_register_cuda_i8_projection(ggml_backend_dev_t device);
#endif
+36 -1
View File
@@ -235,6 +235,40 @@ static int rope_match(ggml_backend_dev_t, const ggml_cgraph* graph, int first, v
return 3; return 3;
} }
static void rope_optimize(ggml_backend_dev_t, ggml_cgraph* graph, void*) {
static const bool disabled = [] {
const char* value = std::getenv("GGML_CUDA_DISABLE_FUSION");
return value && std::atoi(value) != 0;
}();
if (disabled)
return;
const int total = ggml_graph_n_nodes(graph);
for (int i = 2; i + 1 < total; ++i) {
const auto* mul = ggml_graph_node(graph, i);
const auto* rope = ggml_graph_node(graph, i + 1);
if (mul->op != GGML_OP_MUL || !sd_cuda_rope_is_node(rope) || rope->src[0] != mul ||
!mul->src[0] || mul->src[0]->op != GGML_OP_RMS_NORM)
continue;
const auto* norm = mul->src[0];
for (int j = i - 1; j >= 0 && j >= i - 64; --j) {
if (ggml_graph_node(graph, j) != norm)
continue;
if (j + 1 < i) {
if (j >= 2) {
const auto* mat = ggml_graph_node(graph, j - 2);
const auto* view = ggml_graph_node(graph, j - 1);
if (mat->op == GGML_OP_MUL_MAT && mat->src[0]->type == GGML_TYPE_I8 &&
view->op == GGML_OP_RESHAPE && view->src[0] == mat && norm->src[0] == view &&
ggml_ext_graph_delay(graph, j - 2, 3, i))
break;
}
ggml_ext_graph_delay(graph, j, 1, i);
}
break;
}
}
}
static ggml_status rope_prepare(ggml_backend_t, ggml_tensor* const* nodes, int count, void**, size_t* workspace, void*) { static ggml_status rope_prepare(ggml_backend_t, ggml_tensor* const* nodes, int count, void**, size_t* workspace, void*) {
*workspace = ggml_nbytes(nodes[count - 1]); *workspace = ggml_nbytes(nodes[count - 1]);
return GGML_STATUS_SUCCESS; return GGML_STATUS_SUCCESS;
@@ -298,8 +332,9 @@ void sd_ggml_register_cuda_rope(ggml_backend_dev_t device) {
GGML_ASSERT(ggml_ext_register_kernel(device, &desc)); GGML_ASSERT(ggml_ext_register_kernel(device, &desc));
desc.name = "sd.rms_norm_mul_rope.cuda.v1"; desc.name = "sd.rms_norm_mul_rope.cuda.v1";
desc.op = GGML_OP_RMS_NORM; desc.op = GGML_OP_RMS_NORM;
desc.priority = 200; desc.priority = 250;
desc.supports = nullptr; desc.supports = nullptr;
desc.match = rope_match; desc.match = rope_match;
desc.optimize = rope_optimize;
GGML_ASSERT(ggml_ext_register_kernel(device, &desc)); GGML_ASSERT(ggml_ext_register_kernel(device, &desc));
} }
+2
View File
@@ -12,6 +12,7 @@
void sd_ggml_register_cuda_rope(ggml_backend_dev_t device); void sd_ggml_register_cuda_rope(ggml_backend_dev_t device);
#endif #endif
#ifdef SD_CUDA_CUTE_ATTENTION #ifdef SD_CUDA_CUTE_ATTENTION
void sd_ggml_register_cuda_i8_projection(ggml_backend_dev_t device);
void sd_ggml_register_cuda_attention(ggml_backend_dev_t device); void sd_ggml_register_cuda_attention(ggml_backend_dev_t device);
#endif #endif
@@ -180,6 +181,7 @@ void sd_ggml_register_extensions(ggml_backend_t backend) {
sd_ggml_register_cuda_rope(device); sd_ggml_register_cuda_rope(device);
#endif #endif
#ifdef SD_CUDA_CUTE_ATTENTION #ifdef SD_CUDA_CUTE_ATTENTION
sd_ggml_register_cuda_i8_projection(device);
sd_ggml_register_cuda_attention(device); sd_ggml_register_cuda_attention(device);
#endif #endif
return; return;