refactor: move Vulkan RoPE kernels into extensions

This commit is contained in:
leejet committed 2026-10-31 23:39:19 +08:00
1 parent 5c5535d01d
commit 7ab323ef25
9 files changed
+489 -4

No files matched your search

+4
View File
@@ -366,6 +366,10 @@ if (SD_CUDA)
set_property(SOURCE src/core/ggml_extensions.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_CUDA_EXTENSIONS) set_property(SOURCE src/core/ggml_extensions.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_CUDA_EXTENSIONS)
endif() endif()
endif() endif()
if (SD_VULKAN AND NOT SD_USE_UPSTREAM_GGML)
add_subdirectory(src/core/vulkan)
set_property(SOURCE src/core/ggml_extensions.cpp APPEND PROPERTY COMPILE_DEFINITIONS SD_VULKAN_EXTENSIONS)
endif()
if (SD_HIPBLAS) if (SD_HIPBLAS)
find_package(hip REQUIRED CONFIG HINTS "$ENV{ROCM_PATH}" /opt/rocm) find_package(hip REQUIRED CONFIG HINTS "$ENV{ROCM_PATH}" /opt/rocm)
target_link_libraries(${SD_LIB} PRIVATE hip::host) target_link_libraries(${SD_LIB} PRIVATE hip::host)
+3
View File
@@ -0,0 +1,3 @@
file(READ "${INPUT}" SPIRV HEX)
string(REGEX REPLACE "(..)(..)(..)(..)" "0x\\4\\3\\2\\1," SPIRV_WORDS "${SPIRV}")
file(WRITE "${OUTPUT}" "#include <cstdint>\nnamespace sd_vulkan_shaders {\nstatic const uint32_t ${NAME}[] = {${SPIRV_WORDS}};\n}\n")
+59 -2
View File
@@ -313,7 +313,64 @@ 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 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. provide this plan executor. sd.cpp's CUDA registrations opt in to reusable replay.
Remaining CUDA fusion rules and Vulkan INT8/ConvRot implementations still need ## Vulkan execution services
to be externalized. Vulkan execution services and kernels also remain pending.
`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 custom and builtin RoPE implementations,
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. Existing builtin RoPE graphs use the registered override;
unsupported cases retain scheduler fallback to another supported backend.
Remaining model-specific CUDA paths and Vulkan INT8/ConvRot implementations still
need auditing or migration.
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: 8cc0d5a6ae...64edc3b784.
+14 -1
View File
@@ -9,6 +9,9 @@
#include <mutex> #include <mutex>
#include <unordered_set> #include <unordered_set>
#ifdef SD_VULKAN_EXTENSIONS
void sd_ggml_register_vulkan_rope(ggml_backend_dev_t device);
#endif
#ifdef SD_CUDA_EXTENSIONS #ifdef SD_CUDA_EXTENSIONS
void sd_ggml_register_cuda_affine(ggml_backend_dev_t device); void sd_ggml_register_cuda_affine(ggml_backend_dev_t device);
void sd_ggml_register_cuda_i8(ggml_backend_dev_t device); void sd_ggml_register_cuda_i8(ggml_backend_dev_t device);
@@ -172,7 +175,8 @@ void sd_ggml_register_extensions(ggml_backend_t backend) {
auto reg = ggml_backend_dev_backend_reg(device); auto reg = ggml_backend_dev_backend_reg(device);
const bool cpu = ggml_backend_reg_get_proc_address(reg, "ggml_ext_cpu_parallel_v1") != nullptr; const bool cpu = ggml_backend_reg_get_proc_address(reg, "ggml_ext_cpu_parallel_v1") != nullptr;
const bool cuda = ggml_backend_reg_get_proc_address(reg, "ggml_ext_cuda_context_v1") != nullptr; const bool cuda = ggml_backend_reg_get_proc_address(reg, "ggml_ext_cuda_context_v1") != nullptr;
if (!cpu && !cuda) { const bool vulkan = ggml_backend_reg_get_proc_address(reg, "ggml_ext_vulkan_v1") != nullptr;
if (!cpu && !cuda && !vulkan) {
return; return;
} }
static std::mutex mutex; static std::mutex mutex;
@@ -181,6 +185,12 @@ void sd_ggml_register_extensions(ggml_backend_t backend) {
if (!registered.insert(device).second) { if (!registered.insert(device).second) {
return; return;
} }
if (vulkan) {
#ifdef SD_VULKAN_EXTENSIONS
sd_ggml_register_vulkan_rope(device);
#endif
return;
}
if (cuda) { if (cuda) {
#ifdef SD_CUDA_EXTENSIONS #ifdef SD_CUDA_EXTENSIONS
sd_ggml_register_cuda_affine(device); sd_ggml_register_cuda_affine(device);
@@ -226,6 +236,9 @@ ggml_tensor* sd_ggml_rope_apply(ggml_context* ctx, ggml_backend_t backend, ggml_
if (ggml_backend_reg_get_proc_address(reg, "ggml_ext_cpu_parallel_v1") if (ggml_backend_reg_get_proc_address(reg, "ggml_ext_cpu_parallel_v1")
#ifdef SD_CUDA_EXTENSIONS #ifdef SD_CUDA_EXTENSIONS
|| ggml_backend_reg_get_proc_address(reg, "ggml_ext_cuda_context_v1") || ggml_backend_reg_get_proc_address(reg, "ggml_ext_cuda_context_v1")
#endif
#ifdef SD_VULKAN_EXTENSIONS
|| ggml_backend_reg_get_proc_address(reg, "ggml_ext_vulkan_v1")
#endif #endif
) { ) {
ggml_tensor* inputs[] = {x, pe}; ggml_tensor* inputs[] = {x, pe};
+27
View File
@@ -0,0 +1,27 @@
find_package(Vulkan COMPONENTS glslc REQUIRED)
set(SD_VULKAN_GENERATED "${CMAKE_CURRENT_BINARY_DIR}/shaders")
file(MAKE_DIRECTORY "${SD_VULKAN_GENERATED}")
foreach(NAME rope_apply rms_rope_apply rms_rope_cast)
set(SHADER "${NAME}")
set(FLAGS)
if(NAME STREQUAL "rms_rope_cast")
set(SHADER rms_rope_apply)
set(FLAGS -DCAST_F16)
endif()
set(SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/shaders/${SHADER}.comp")
set(SPV "${SD_VULKAN_GENERATED}/${NAME}.spv")
set(HEADER "${SD_VULKAN_GENERATED}/${NAME}.h")
# Keep native RoPE rounding: spirv-opt changes the subgroup reduction results.
add_custom_command(OUTPUT "${HEADER}"
COMMAND "${Vulkan_GLSLC_EXECUTABLE}" --target-env=vulkan1.2 ${FLAGS} "${SOURCE}" -o "${SPV}"
COMMAND "${CMAKE_COMMAND}" "-DINPUT=${SPV}" "-DOUTPUT=${HEADER}" "-DNAME=${NAME}"
-P "${PROJECT_SOURCE_DIR}/cmake/embed_spirv.cmake"
DEPENDS "${SOURCE}" "${PROJECT_SOURCE_DIR}/cmake/embed_spirv.cmake"
BYPRODUCTS "${SPV}"
VERBATIM)
list(APPEND SD_VULKAN_HEADERS "${HEADER}")
endforeach()
add_custom_target(sd-vulkan-shaders DEPENDS ${SD_VULKAN_HEADERS})
add_dependencies(${SD_LIB} sd-vulkan-shaders)
target_sources(${SD_LIB} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/rope.cpp")
target_include_directories(${SD_LIB} PRIVATE "${SD_VULKAN_GENERATED}")
+225
View File
@@ -0,0 +1,225 @@
#include "core/ggml_extensions.h"
#include "ggml-ext-vulkan.h"
#include "rms_rope_apply.h"
#include "rms_rope_cast.h"
#include "rope_apply.h"
#include <algorithm>
#include <climits>
#include <cstring>
namespace sd_vulkan_rope {
struct Parameters {
uint32_t half_dim, rot_half, heads, tokens, pairs;
uint32_t x_stride[4], pe_stride[4];
uint32_t x_offset, pe_offset, dst_offset, interleaved;
};
struct FusedParameters {
Parameters rope;
float eps;
uint32_t w_offset;
float output_scale;
};
static_assert(sizeof(Parameters) == 68 && sizeof(FusedParameters) == 80, "RoPE push constant layout mismatch");
static const ggml_ext_vulkan_api* api(ggml_backend_dev_t device) {
auto query = reinterpret_cast<ggml_ext_vulkan_api_fn>(ggml_backend_reg_get_proc_address(
ggml_backend_dev_backend_reg(device), "ggml_ext_vulkan_v1"));
return query ? query() : nullptr;
}
static float param(const ggml_tensor* node, int i) {
float value;
std::memcpy(&value, node->op_params + i, sizeof(value));
return value;
}
static bool is_rope(const ggml_tensor* node) {
return node && (node->op == GGML_OP_ROPE_APPLY || ggml_ext_op_get(node) == sd_ggml_rope_op());
}
static bool aligned(const ggml_tensor* tensor, size_t alignment) {
size_t offset = 0;
while (!tensor->data && tensor->view_src) {
offset += tensor->view_offs;
tensor = tensor->view_src;
}
return (reinterpret_cast<uintptr_t>(tensor->data) + offset) % alignment == 0;
}
static bool bounded(const ggml_tensor* node, const ggml_ext_vulkan_device_info& info) {
if (!node || !aligned(node, 4) || ggml_nbytes(node) > info.max_buffer_range - (info.buffer_alignment - 1))
return false;
for (int i = 0; i < 4; ++i)
if (node->nb[i] % 4 || node->nb[i] / 4 > UINT32_MAX || node->ne[i] > UINT32_MAX)
return false;
return true;
}
static bool supports(ggml_backend_dev_t device, const ggml_tensor* node, void*) {
const auto* service = api(device);
ggml_ext_vulkan_device_info info;
if (!service || !service->device_info(device, &info) || info.max_group_size[0] < 256 || info.max_group_invocations < 256)
return false;
const auto* x = node->src[0];
const auto* pe = node->src[1];
return x && pe && x->type == GGML_TYPE_F32 && pe->type == GGML_TYPE_F32 && node->type == GGML_TYPE_F32 &&
x->ne[0] % 2 == 0 && pe->ne[0] == 2 && pe->ne[1] == 2 && pe->ne[2] > 0 && pe->ne[2] <= x->ne[0] / 2 &&
pe->ne[3] == x->ne[2] && node->ne[0] == x->ne[0] && node->ne[1] == x->ne[2] &&
node->ne[2] == x->ne[1] * x->ne[3] && node->ne[3] == 1 && ggml_is_contiguous(node) &&
bounded(x, info) && bounded(pe, info) && bounded(node, info) && ggml_nelements(node) / 2 <= UINT32_MAX &&
(uint64_t(ggml_nelements(node) / 2) + 255) / 256 <= uint64_t(info.max_group_count[0]) * info.max_group_count[1];
}
static bool fused_supports(ggml_backend_dev_t device, const ggml_tensor* norm, const ggml_tensor* mul, const ggml_tensor* rope) {
ggml_ext_vulkan_device_info info;
if (!api(device)->device_info(device, &info) || info.fusion_disabled || !info.subgroup_shuffle || !info.full_subgroups ||
info.subgroup_size != 32 || !is_rope(rope) || !supports(device, rope, nullptr) ||
norm->op != GGML_OP_RMS_NORM || mul->op != GGML_OP_MUL || mul->src[0] != norm || rope->src[0] != mul)
return false;
const auto* x = norm->src[0];
const auto* weight = mul->src[1];
return x && weight && x->type == GGML_TYPE_F32 && x->ne[0] == 128 && x->nb[0] == 4 &&
norm->type == GGML_TYPE_F32 && mul->type == GGML_TYPE_F32 && ggml_are_same_shape(x, norm) && ggml_are_same_shape(norm, mul) &&
weight->type == GGML_TYPE_F32 && weight->ne[0] == 128 && ggml_nelements(weight) == 128 &&
ggml_is_contiguous(weight) && bounded(weight, info) && bounded(x, info) && (x->ne[1] + 3) / 4 <= info.max_group_count[0];
}
static bool cast_supports(ggml_backend_dev_t device, const ggml_tensor* rope, const ggml_tensor* scale, const ggml_tensor* copy) {
ggml_ext_vulkan_device_info info;
api(device)->device_info(device, &info);
// Workspace writeback uses Vulkan's four-byte copy granularity.
return info.float16 && scale->op == GGML_OP_SCALE && scale->src[0] == rope && scale->type == GGML_TYPE_F32 &&
param(scale, 1) == 0.f && copy->op == GGML_OP_CPY && copy->src[0] == scale && copy->type == GGML_TYPE_F16 &&
ggml_is_contiguous(copy) && aligned(copy, 4) && ggml_are_same_shape(scale, copy);
}
static int match(ggml_backend_dev_t device, const ggml_cgraph* graph, int first, void*) {
auto* g = const_cast<ggml_cgraph*>(graph);
if (first + 2 >= ggml_graph_n_nodes(g) || !fused_supports(device, ggml_graph_node(g, first), ggml_graph_node(g, first + 1), ggml_graph_node(g, first + 2)))
return 0;
return first + 4 < ggml_graph_n_nodes(g) && cast_supports(device, ggml_graph_node(g, first + 2), ggml_graph_node(g, first + 3), ggml_graph_node(g, first + 4)) ? 5 : 3;
}
static void optimize(ggml_backend_dev_t device, ggml_cgraph* graph, void*) {
for (int last = 2; last < ggml_graph_n_nodes(graph); ++last) {
const auto* output = ggml_graph_node(graph, last);
const auto* scale = output->op == GGML_OP_CPY ? output->src[0] : nullptr;
const auto* rope = scale && scale->op == GGML_OP_SCALE ? scale->src[0] : output;
if (!is_rope(rope))
continue;
const auto* mul = rope->src[0];
const auto* norm = mul->src[0];
if (!norm || !fused_supports(device, norm, mul, rope))
continue;
const int count = output == rope ? 3 : cast_supports(device, rope, scale, output) ? 5
: 0;
if (!count)
continue;
const ggml_tensor* nodes[] = {norm, mul, rope, scale, output};
int indices[5], next = count - 1;
for (int i = last; i >= std::max(0, last - 64) && next >= 0; --i)
if (ggml_graph_node(graph, i) == nodes[next])
indices[next--] = i;
if (next >= 0)
continue;
int first = indices[0];
for (int i = 1; i < count; ++i) {
if (!ggml_ext_graph_delay(graph, first, i, indices[i]))
break;
first = indices[i] - i;
}
}
}
static ggml_status prepare(ggml_backend_t, ggml_tensor* const* nodes, int count, void**, size_t* bytes, void*) {
*bytes = ggml_nbytes(nodes[count - 1]);
return GGML_STATUS_SUCCESS;
}
static void destroy(void*, void*) {}
static bool overlaps(const ggml_ext_vulkan_buffer_info& a, const ggml_ext_vulkan_buffer_info& b) {
return a.storage == b.storage && a.offset < b.offset + b.size && b.offset < a.offset + a.size;
}
static ggml_status execute(const ggml_ext_execution_context* execution, ggml_tensor* const* nodes, int count, void*, void*) {
auto device = ggml_backend_get_device(execution->backend);
const auto* service = api(device);
ggml_ext_vulkan_device_info info;
if (!service || !service->device_info(device, &info))
return GGML_STATUS_FAILED;
const auto* rope = nodes[count == 1 ? 0 : 2];
const auto* x = count == 1 ? rope->src[0] : nodes[0]->src[0];
const auto* pe = rope->src[1];
const auto* output = nodes[count - 1];
ggml_ext_vulkan_binding bindings[] = {{x}, {pe}, {output}, {count == 1 ? nullptr : nodes[1]->src[1]}};
ggml_ext_vulkan_buffer_info buffers[4];
const int n_buffers = count == 1 ? 3 : 4;
for (int i = 0; i < n_buffers; ++i)
if (!service->buffer_info(execution->backend, bindings + i, buffers + i))
return GGML_STATUS_FAILED;
bool alias = overlaps(buffers[2], buffers[0]) || overlaps(buffers[2], buffers[1]) || (count != 1 && overlaps(buffers[2], buffers[3]));
const auto destination = bindings[2];
if (alias) {
bindings[2] = {nullptr, execution->workspace, 0, ggml_nbytes(output)};
if (!service->buffer_info(execution->backend, bindings + 2, buffers + 2))
return GGML_STATUS_FAILED;
}
FusedParameters params = {};
auto& pc = params.rope;
pc.half_dim = uint32_t(x->ne[0] / 2);
pc.rot_half = uint32_t(pe->ne[2]);
pc.heads = uint32_t(x->ne[1]);
pc.tokens = uint32_t(x->ne[2]);
pc.pairs = uint32_t(ggml_nelements(x) / 2);
for (int i = 0; i < 4; ++i) {
pc.x_stride[i] = uint32_t(x->nb[i] / 4);
pc.pe_stride[i] = uint32_t(pe->nb[i] / 4);
}
pc.x_offset = buffers[0].shader_offset / 4;
pc.pe_offset = buffers[1].shader_offset / 4;
pc.dst_offset = buffers[2].shader_offset / uint32_t(ggml_type_size(output->type));
std::memcpy(&pc.interleaved, ggml_ext_op_get(rope) ? ggml_ext_op_attributes(rope) : rope->op_params, 4);
uint32_t groups[3] = {1, 1, 1};
ggml_ext_vulkan_pipeline pipeline = {};
pipeline.name = "sd.rope_apply.vulkan";
pipeline.spirv = sd_vulkan_shaders::rope_apply;
pipeline.spirv_size = sizeof(sd_vulkan_shaders::rope_apply);
pipeline.binding_count = n_buffers;
pipeline.push_constant_size = sizeof(Parameters);
if (count == 1) {
const uint32_t n_groups = uint32_t((uint64_t(pc.pairs) + 255) / 256);
groups[0] = std::min(n_groups, info.max_group_count[0]);
groups[1] = (n_groups + groups[0] - 1) / groups[0];
} else {
pipeline.name = count == 5 ? "sd.rms_rope_cast.vulkan" : "sd.rms_rope_apply.vulkan";
pipeline.spirv = count == 5 ? sd_vulkan_shaders::rms_rope_cast : sd_vulkan_shaders::rms_rope_apply;
pipeline.spirv_size = count == 5 ? sizeof(sd_vulkan_shaders::rms_rope_cast) : sizeof(sd_vulkan_shaders::rms_rope_apply);
pipeline.push_constant_size = sizeof(FusedParameters);
pipeline.full_subgroups = true;
pipeline.required_subgroup_size = info.subgroup_size_control ? 32 : 0;
params.eps = param(nodes[0], 0);
params.w_offset = buffers[3].shader_offset / 4;
params.output_scale = count == 5 ? param(nodes[3], 0) : 1.f;
groups[0] = (pc.heads + 3) / 4;
groups[1] = std::min(pc.tokens, info.max_group_count[1]);
groups[2] = std::min(uint32_t(x->ne[3]), info.max_group_count[2]);
}
auto status = service->dispatch(execution->backend, &pipeline, bindings, &params, groups);
if (status == GGML_STATUS_SUCCESS && alias)
status = service->copy(execution->backend, bindings + 2, &destination);
return status;
}
}
void sd_ggml_register_vulkan_rope(ggml_backend_dev_t device) {
ggml_ext_kernel_desc desc = {};
desc.abi_version = GGML_EXT_ABI_VERSION;
desc.name = "sd.rope_apply.vulkan.v1";
desc.op = GGML_OP_CUSTOM;
desc.custom_op = sd_ggml_rope_op();
desc.priority = 100;
desc.supports = sd_vulkan_rope::supports;
desc.prepare = sd_vulkan_rope::prepare;
desc.execute = sd_vulkan_rope::execute;
desc.destroy = sd_vulkan_rope::destroy;
GGML_ASSERT(ggml_ext_register_kernel(device, &desc));
desc.name = "sd.rope_apply.vulkan.override.v1";
desc.op = GGML_OP_ROPE_APPLY;
desc.custom_op = nullptr;
GGML_ASSERT(ggml_ext_register_kernel(device, &desc));
desc.name = "sd.rms_rope_apply.vulkan.v1";
desc.op = GGML_OP_RMS_NORM;
desc.priority = 200;
desc.supports = nullptr;
desc.match = sd_vulkan_rope::match;
desc.optimize = sd_vulkan_rope::optimize;
GGML_ASSERT(ggml_ext_register_kernel(device, &desc));
}
@@ -0,0 +1,97 @@
#version 450
#extension GL_KHR_shader_subgroup_basic : require
#extension GL_KHR_shader_subgroup_shuffle : require
#extension GL_EXT_control_flow_attributes : require
layout(local_size_x = 128, local_size_y = 1, local_size_z = 1) in;
layout(binding = 0) readonly buffer X { float data_x[]; };
layout(binding = 1) readonly buffer PE { float data_pe[]; };
#ifdef CAST_F16
#extension GL_EXT_shader_16bit_storage : require
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
layout(binding = 2) writeonly buffer D { float16_t data_d[]; };
#else
layout(binding = 2) writeonly buffer D { float data_d[]; };
#endif
layout(binding = 3) readonly buffer W { float data_w[]; };
layout(push_constant) uniform parameter {
uint half_dim;
uint rot_half;
uint heads;
uint tokens;
uint pairs;
uint x_stride[4];
uint pe_stride[4];
uint x_offset;
uint pe_offset;
uint dst_offset;
uint interleaved;
float eps;
uint w_offset;
float output_scale;
} pc;
float shuffle_channel(vec4 values, uint channel) {
const uint lane = channel & 31u;
const vec4 shuffled = vec4(subgroupShuffle(values.x, lane), subgroupShuffle(values.y, lane),
subgroupShuffle(values.z, lane), subgroupShuffle(values.w, lane));
return shuffled[channel / 32];
}
void main() {
const uint head = gl_WorkGroupID.x * 4 + gl_SubgroupID;
if (head >= pc.heads) {
return;
}
const uint lane = gl_SubgroupInvocationID;
const uint batches = pc.pairs / (64 * pc.heads * pc.tokens);
for (uint batch = gl_WorkGroupID.z; batch < batches; batch += gl_NumWorkGroups.z) {
for (uint token = gl_WorkGroupID.y; token < pc.tokens; token += gl_NumWorkGroups.y) {
const uint base = pc.x_offset + head * pc.x_stride[1] + token * pc.x_stride[2] + batch * pc.x_stride[3];
vec4 x = vec4(data_x[base + lane], data_x[base + lane + 32],
data_x[base + lane + 64], data_x[base + lane + 96]);
precise vec4 squares = x * x;
precise float sum = (squares.x + squares.z) + (squares.y + squares.w);
[[unroll]] for (uint stride = 16; stride > 0; stride >>= 1) {
sum += subgroupShuffle(sum, lane ^ stride);
}
sum = subgroupShuffle(sum, 0);
const float scale = inversesqrt(sum / 128.0 + pc.eps);
precise vec4 weighted = scale * x;
[[unroll]] for (uint i = 0; i < 4; ++i) {
weighted[i] *= data_w[pc.w_offset + lane + i * 32];
}
[[unroll]] for (uint i = 0; i < 4; ++i) {
const uint col = lane + i * 32;
precise float value = weighted[i];
if (pc.interleaved != 0) {
const float a = subgroupShuffle(value, lane & ~1u);
const float b = subgroupShuffle(value, lane | 1u);
if (col / 2 < pc.rot_half) {
const uint matrix = pc.pe_offset + (col / 2) * pc.pe_stride[2] + token * pc.pe_stride[3] + (col & 1u) * pc.pe_stride[1];
value = a * data_pe[matrix] + b * data_pe[matrix + pc.pe_stride[0]];
}
} else {
const uint pair = col % pc.rot_half;
const float a = shuffle_channel(weighted, pair);
const float b = shuffle_channel(weighted, pair + pc.rot_half);
if (col < 2 * pc.rot_half) {
const uint matrix = pc.pe_offset + pair * pc.pe_stride[2] + token * pc.pe_stride[3];
const float c = data_pe[matrix];
const float s = data_pe[matrix + pc.pe_stride[1]];
value = col < pc.rot_half ? a * c - b * s : a * s + b * c;
}
}
#ifdef CAST_F16
value *= pc.output_scale;
value += 0.0;
data_d[pc.dst_offset + ((batch * pc.heads + head) * pc.tokens + token) * 128 + col] = float16_t(value);
#else
data_d[pc.dst_offset + ((batch * pc.heads + head) * pc.tokens + token) * 128 + col] = value;
#endif
}
}
}
}
+59
View File
@@ -0,0 +1,59 @@
#version 450
layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
layout(binding = 0) readonly buffer X { float data_x[]; };
layout(binding = 1) readonly buffer PE { float data_pe[]; };
layout(binding = 2) writeonly buffer D { float data_d[]; };
layout(push_constant) uniform parameter {
uint half_dim;
uint rot_half;
uint heads;
uint tokens;
uint pairs;
uint x_stride[4];
uint pe_stride[4];
uint x_offset;
uint pe_offset;
uint dst_offset;
uint interleaved;
} pc;
void main() {
const uint index = gl_GlobalInvocationID.x + gl_NumWorkGroups.x*gl_WorkGroupSize.x*gl_GlobalInvocationID.y;
if (index >= pc.pairs) {
return;
}
const uint j = index % pc.half_dim;
const uint row = index / pc.half_dim;
const uint token = row % pc.tokens;
const uint head = (row / pc.tokens) % pc.heads;
const uint batch = row / (pc.tokens*pc.heads);
const uint i0 = pc.interleaved != 0 || j >= pc.rot_half ? 2*j : j;
const uint i1 = pc.interleaved != 0 || j >= pc.rot_half ? 2*j + 1 : j + pc.rot_half;
const uint x_row = pc.x_offset + head*pc.x_stride[1] + token*pc.x_stride[2] + batch*pc.x_stride[3];
const uint out_row = pc.dst_offset + row*pc.half_dim*2;
const float a = data_x[x_row + i0*pc.x_stride[0]];
const float b = data_x[x_row + i1*pc.x_stride[0]];
if (j >= pc.rot_half) {
data_d[out_row + i0] = a;
data_d[out_row + i1] = b;
return;
}
const uint matrix = pc.pe_offset + j*pc.pe_stride[2] + token*pc.pe_stride[3];
const float c = data_pe[matrix];
const float s = data_pe[matrix + pc.pe_stride[1]];
// Preserve the separate multiply/add rounding of the unfused graph.
precise float y0;
precise float y1;
if (pc.interleaved != 0) {
y0 = a*c + b*data_pe[matrix + pc.pe_stride[0]];
y1 = a*s + b*data_pe[matrix + pc.pe_stride[0] + pc.pe_stride[1]];
} else {
y0 = a*c - b*s;
y1 = a*s + b*c;
}
data_d[out_row + i0] = y0;
data_d[out_row + i1] = y1;
}