refactor: implement SageAttention through backend extensions

This commit is contained in:
leejet committed 2026-11-01 02:25:21 +08:00
1 parent f0204a7eaa
commit 15da7f4fe1
17 files changed
+4515 -5

No files matched your search

+21 -4
View File
@@ -399,10 +399,8 @@ The corresponding Vulkan shader sources and native fusion rules have been
removed from ggml. Unsupported custom nodes retain scheduler fallback to removed from ggml. Unsupported custom nodes retain scheduler fallback to
another supported backend. another supported backend.
Remaining model-specific CUDA paths and Vulkan INT8/ConvRot implementations still Remaining model-specific CUDA paths still need auditing or migration. This
need auditing or migration. implementation does not add dynamic dtypes.
This implementation does not yet externalize all model-specific kernels or add
dynamic dtypes.
`src/core/vulkan/int8.cpp` owns Vulkan INT8 GEMM, ConvRot, and their fusion rules. `src/core/vulkan/int8.cpp` owns Vulkan INT8 GEMM, ConvRot, and their fusion rules.
The Vulkan compute metadata service exposes shared-memory limits, subgroup The Vulkan compute metadata service exposes shared-memory limits, subgroup
@@ -412,3 +410,22 @@ SwiGLU, residual, row-concatenation, and modulation paths. Kernels use public
Vulkan dispatch, buffer, copy, and workspace services without private backend Vulkan dispatch, buffer, copy, and workspace services without private backend
headers. Vulkan currently handles packed group-256 activations with separate headers. Vulkan currently handles packed group-256 activations with separate
weight scales; unsupported custom nodes fall back to CPU. 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.
+1 -1
Submodule ggml updated: 9201d070ff...62da2f1b7e.
+2
View File
@@ -23,6 +23,8 @@ endfunction()
if(SD_CUDA) if(SD_CUDA)
enable_language(CUDA) enable_language(CUDA)
sd_cuda_sources(cuda CUDA) sd_cuda_sources(cuda CUDA)
target_sources(${SD_CUDA_TARGET} PRIVATE sage_attention.cu)
set_source_files_properties(sage_attention.cu PROPERTIES COMPILE_OPTIONS "--use_fast_math")
set_source_files_properties(${SD_CUDA_SOURCES} PROPERTIES COMPILE_OPTIONS "--use_fast_math") set_source_files_properties(${SD_CUDA_SOURCES} PROPERTIES COMPILE_OPTIONS "--use_fast_math")
if(GGML_CUDA_CUTLASS_PATH) if(GGML_CUDA_CUTLASS_PATH)
set_property(SOURCE "${CMAKE_CURRENT_BINARY_DIR}/int8_cuda.cu" APPEND PROPERTY COMPILE_DEFINITIONS SD_CUDA_I8_CUTLASS) set_property(SOURCE "${CMAKE_CURRENT_BINARY_DIR}/int8_cuda.cu" APPEND PROPERTY COMPILE_DEFINITIONS SD_CUDA_I8_CUTLASS)
+6
View File
@@ -11,6 +11,9 @@ namespace SD_CUDA_NAMESPACE {
void register_i8(ggml_backend_dev_t device); void register_i8(ggml_backend_dev_t device);
void register_convrot(ggml_backend_dev_t device); void register_convrot(ggml_backend_dev_t device);
void register_affine(ggml_backend_dev_t device); void register_affine(ggml_backend_dev_t device);
#if !defined(SD_CUDA_VENDOR_HIP) && !defined(SD_CUDA_VENDOR_MUSA)
void register_sage_attention(ggml_backend_dev_t device);
#endif
void register_extensions(ggml_backend_dev_t device) { void register_extensions(ggml_backend_dev_t device) {
if (cuda_device_info(device).device < 0) if (cuda_device_info(device).device < 0)
@@ -19,6 +22,9 @@ namespace SD_CUDA_NAMESPACE {
register_i8(device); register_i8(device);
register_convrot(device); register_convrot(device);
register_rope(device); register_rope(device);
#if !defined(SD_CUDA_VENDOR_HIP) && !defined(SD_CUDA_VENDOR_MUSA)
register_sage_attention(device);
#endif
#ifdef SD_CUDA_CUTE_ATTENTION #ifdef SD_CUDA_CUTE_ATTENTION
sd_ggml_register_cuda_i8_projection(device); sd_ggml_register_cuda_i8_projection(device);
sd_ggml_register_cuda_i8_gated_residual(device); sd_ggml_register_cuda_i8_gated_residual(device);
+387
View File
@@ -0,0 +1,387 @@
#include "core/ggml_extensions.h"
#include "runtime.h"
#if __CUDACC_VER_MAJOR__ >= 12
#include <algorithm>
#include <climits>
#include <cstring>
#include <map>
#include "sageattention/attn.cuh"
#include "sageattention/attn_fp8.cuh"
static constexpr int sage_cuda_version = __CUDACC_VER_MAJOR__ * 1000 + __CUDACC_VER_MINOR__ * 10;
namespace sd_cuda {
struct SageDevice {
int cc;
int compiled_cc;
};
static ggml_sage_attn_mode sage_mode(const SageDevice& device, ggml_sage_attn_mode requested) {
if (requested != GGML_SAGE_ATTN_AUTO)
return requested;
if (sage_cuda_version >= 12040 && device.cc >= 89 && device.compiled_cc >= 89) {
if (sage_cuda_version >= 12080 && device.cc != 90)
return GGML_SAGE_ATTN_2_PLUS_PLUS;
return GGML_SAGE_ATTN_2;
}
return GGML_SAGE_ATTN_AUTO;
}
static __device__ __forceinline__ float warp_reduce_max(float x) {
#pragma unroll
for (int offset = 16; offset > 0; offset >>= 1)
x = fmaxf(x, __shfl_xor_sync(0xffffffff, x, offset));
return x;
}
static __global__ void sage_output(const half* input, float* output, int count) {
const int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < count)
output[i] = __half2float(input[i]);
}
struct SageWorkspace {
size_t q;
size_t k;
size_t q_scale;
size_t k_scale;
size_t k_mean;
size_t v;
size_t v_scale;
size_t output;
size_t size;
explicit SageWorkspace(const ggml_tensor* dst) {
const ggml_tensor* qt = dst->src[0];
const ggml_tensor* kt = dst->src[1];
size = 0;
q = reserve(ggml_nelements(qt));
k = reserve(ggml_nelements(kt));
q_scale = reserve(GGML_PAD(qt->ne[1], 128) / 32 * 8 * qt->ne[2] * qt->ne[3] * sizeof(float));
k_scale = reserve(GGML_PAD(kt->ne[1], 64) / 64 * 4 * kt->ne[2] * kt->ne[3] * sizeof(float));
k_mean = reserve(kt->ne[0] * kt->ne[2] * kt->ne[3] * sizeof(float));
v = reserve(kt->ne[0] * GGML_PAD(kt->ne[1], 64) * kt->ne[2] * kt->ne[3]);
v_scale = reserve(kt->ne[0] * kt->ne[2] * kt->ne[3] * sizeof(float));
output = reserve(ggml_nelements(dst) * sizeof(half));
}
size_t reserve(size_t bytes) {
const size_t offset = size;
size += GGML_PAD(bytes, 256);
return offset;
}
};
template <int D>
static __global__ void sage_key_mean(const float* k, float* mean, int length) {
const int channel = blockIdx.x * 32 + threadIdx.x % 32;
const int row = threadIdx.x / 32;
const size_t base = size_t(blockIdx.y) * length * D;
float sum = 0.0f;
for (int token = row; token < length; token += 8) {
sum += k[base + size_t(token) * D + channel];
}
__shared__ float sums[256];
sums[threadIdx.x] = sum;
__syncthreads();
if (row == 0) {
for (int i = 1; i < 8; ++i) {
sum += sums[threadIdx.x + i * 32];
}
mean[size_t(blockIdx.y) * D + channel] = sum / length;
}
}
template <int D, int TOKENS, bool SMOOTH>
static __global__ void sage_quantize(const float* input, const float* mean, int8_t* output, float* scales, int length) {
constexpr int VALUES = D * TOKENS / 256;
const int head = blockIdx.y;
const int first = blockIdx.x * TOKENS * D;
const size_t base = size_t(head) * length * D;
float values[VALUES];
float amax = 1e-7f;
#pragma unroll
for (int i = 0; i < VALUES; ++i) {
const int index = first + threadIdx.x + i * 256;
float value = 0.0f;
if (index < length * D) {
value = input[base + index];
if constexpr (SMOOTH) {
value -= mean[size_t(head) * D + index % D];
}
}
values[i] = value;
amax = fmaxf(amax, fabsf(value));
}
amax = warp_reduce_max(amax);
__shared__ float maxima[8];
if (threadIdx.x % 32 == 0) {
maxima[threadIdx.x / 32] = amax;
}
__syncthreads();
if (threadIdx.x < 32) {
amax = threadIdx.x < 8 ? maxima[threadIdx.x] : 1e-7f;
amax = warp_reduce_max(amax);
if (threadIdx.x == 0) {
maxima[0] = amax;
scales[size_t(head) * gridDim.x + blockIdx.x] = amax / 127.0f;
}
}
__syncthreads();
const float inverse_scale = 127.0f / maxima[0];
#pragma unroll
for (int i = 0; i < VALUES; ++i) {
const int index = first + threadIdx.x + i * 256;
if (index < length * D) {
output[base + index] = int8_t(__float2int_rn(values[i] * inverse_scale));
}
}
}
template <int D, bool KEY>
static __global__ void sage_quantize_per_thread(const float* input, const float* mean, int8_t* output, float* scales, int length) {
constexpr int GROUPS = KEY ? 4 : 8;
constexpr int TOKENS = KEY ? 64 : 32;
constexpr int VALUES = D * TOKENS / GROUPS / 32;
const int group = threadIdx.x / 32;
const int lane = threadIdx.x % 32;
const size_t base = size_t(blockIdx.y) * length * D;
float values[VALUES];
float amax = 0.0f;
#pragma unroll
for (int i = 0; i < VALUES; ++i) {
const int index = lane + i * 32;
const int row = index / D;
const int token = blockIdx.x * TOKENS + (KEY ? row / 2 * 8 + group * 2 + row % 2 : row * 8 + group);
float value = token < length ? input[base + size_t(token) * D + index % D] : 0.0f;
if constexpr (KEY) {
if (token < length) {
value -= mean[size_t(blockIdx.y) * D + index % D];
}
}
values[i] = value;
amax = fmaxf(amax, fabsf(value));
}
const float scale = warp_reduce_max(amax) / 127.0f + 1e-7f;
if (lane == 0) {
scales[(size_t(blockIdx.y) * gridDim.x + blockIdx.x) * GROUPS + group] = scale;
}
#pragma unroll
for (int i = 0; i < VALUES; ++i) {
const int index = lane + i * 32;
const int row = index / D;
const int token = blockIdx.x * TOKENS + (KEY ? row / 2 * 8 + group * 2 + row % 2 : row * 8 + group);
if (token < length) {
const float value = values[i] / scale;
output[base + size_t(token) * D + index % D] = int8_t(__float2int_rz(value + copysignf(0.5f, value)));
}
}
}
template <int D>
static __global__ void sage_value_scale(const half* v, float* scales, int length, float scale_max) {
const int channel = blockIdx.x * 32 + threadIdx.x % 32;
const int row = threadIdx.x / 32;
const size_t base = size_t(blockIdx.y) * length * D;
float amax = 1e-7f;
for (int token = row; token < length; token += 8) {
amax = fmaxf(amax, fabsf(__half2float(v[base + size_t(token) * D + channel])));
}
__shared__ float maxima[256];
maxima[threadIdx.x] = amax;
__syncthreads();
if (row == 0) {
for (int i = 1; i < 8; ++i) {
amax = fmaxf(amax, maxima[threadIdx.x + i * 32]);
}
scales[size_t(blockIdx.y) * D + channel] = amax / scale_max;
}
}
template <int D>
static __global__ void sage_quantize_value(const half* v, const float* scales, int8_t* output, int length, int padded) {
__shared__ uint8_t tile[32][D + 4];
const int head = blockIdx.y;
const int first = blockIdx.x * 32;
const size_t base = size_t(head) * length * D;
for (int i = threadIdx.x; i < 32 * D; i += 256) {
const int token = first + i / D;
const int channel = i % D;
const float value = token < length ? __half2float(v[base + size_t(token) * D + channel]) / scales[size_t(head) * D + channel] : 0.0f;
// FP8 MMA consumes pairs of tokens in a permuted, transposed V tile.
const int row = i / D;
const int permuted = row / 16 * 16 + row % 16 / 8 * 2 + row % 8 / 2 * 4 + row % 2;
tile[permuted][channel] = __nv_cvt_float_to_fp8(value, __NV_SATFINITE, __NV_E4M3);
}
__syncthreads();
for (int i = threadIdx.x; i < 32 * D; i += 256) {
output[(size_t(head) * D + i / 32) * padded + first + i % 32] = tile[i % 32][i / 32];
}
}
template <int D, bool PLUS_PLUS>
static void sage_attn_fp8(cudaStream_t stream, ggml_tensor* dst, char* data, const SageWorkspace& work, float scale) {
const ggml_tensor* q = dst->src[0];
const ggml_tensor* k = dst->src[1];
const ggml_tensor* v = dst->src[2];
const int lq = q->ne[1], lk = k->ne[1], padded = GGML_PAD(lk, 64);
const int hq = q->ne[2], hk = k->ne[2], batch = q->ne[3];
auto* qi = reinterpret_cast<int8_t*>(data + work.q);
auto* ki = reinterpret_cast<int8_t*>(data + work.k);
auto* vi = reinterpret_cast<int8_t*>(data + work.v);
auto* qs = reinterpret_cast<float*>(data + work.q_scale);
auto* ks = reinterpret_cast<float*>(data + work.k_scale);
auto* vs = reinterpret_cast<float*>(data + work.v_scale);
auto* km = reinterpret_cast<float*>(data + work.k_mean);
auto* out = reinterpret_cast<half*>(data + work.output);
sage_key_mean<D><<<dim3(D / 32, hk * batch), 256, 0, stream>>>(static_cast<const float*>(k->data), km, lk);
sage_quantize_per_thread<D, false><<<dim3(GGML_PAD(lq, 128) / 32, hq * batch), 256, 0, stream>>>(
static_cast<const float*>(q->data), nullptr, qi, qs, lq);
sage_quantize_per_thread<D, true><<<dim3(padded / 64, hk * batch), 128, 0, stream>>>(
static_cast<const float*>(k->data), km, ki, ks, lk);
// The smaller V range keeps the FP16 instruction accumulator finite in SageAttention2++.
sage_value_scale<D><<<dim3(D / 32, hk * batch), 256, 0, stream>>>(static_cast<const half*>(v->data), vs, lk, PLUS_PLUS ? 2.25f : 448.0f);
sage_quantize_value<D><<<dim3(padded / 32, hk * batch), 256, 0, stream>>>(static_cast<const half*>(v->data), vs, vi, lk, padded);
auto kernel = sageattention::qk_int_sv_f8_attn_kernel<128, 64, 32, 64, D,
sageattention::DataType::kInt8, sageattention::QuantGranularity::kPerThread,
sageattention::QuantGranularity::kPerThread, float, true, half,
sageattention::ComputeUnit::kCudaCore, sageattention::MaskMode::kNone, false, true, false, PLUS_PLUS>;
kernel<<<dim3(GGML_PAD(lq, 128) / 128, hq, batch), dim3(32, 4), 256 * D, stream>>>(
qi, ki, vi, out, nullptr, qs, ks, vs, nullptr, lq, lk, hq / hk,
D * lq * hq, D, D * lq, D * lk * hk, D, D * lk,
D * padded * hk, D * padded, padded, D * lq * hq, D * hq, D, scale);
sage_output<<<(ggml_nelements(dst) + 255) / 256, 256, 0, stream>>>(out, static_cast<float*>(dst->data), ggml_nelements(dst));
}
template <int D>
static void sage_attn(cudaStream_t stream, ggml_tensor* dst, char* data, ggml_sage_attn_mode mode, float scale) {
const ggml_tensor* q = dst->src[0];
const ggml_tensor* k = dst->src[1];
const ggml_tensor* v = dst->src[2];
const int lq = q->ne[1], lk = k->ne[1];
const int hq = q->ne[2], hk = k->ne[2], batch = q->ne[3];
const SageWorkspace work(dst);
if (mode == GGML_SAGE_ATTN_2_PLUS_PLUS) {
sage_attn_fp8<D, true>(stream, dst, data, work, scale);
return;
}
if (mode == GGML_SAGE_ATTN_2) {
sage_attn_fp8<D, false>(stream, dst, data, work, scale);
return;
}
auto* qi = reinterpret_cast<int8_t*>(data + work.q);
auto* ki = reinterpret_cast<int8_t*>(data + work.k);
auto* qs = reinterpret_cast<float*>(data + work.q_scale);
auto* ks = reinterpret_cast<float*>(data + work.k_scale);
auto* km = reinterpret_cast<float*>(data + work.k_mean);
auto* out = reinterpret_cast<half*>(data + work.output);
sage_key_mean<D><<<dim3(D / 32, hk * batch), 256, 0, stream>>>(static_cast<const float*>(k->data), km, lk);
sage_quantize<D, 32, false><<<dim3(GGML_PAD(lq, 128) / 32, hq * batch), 256, 0, stream>>>(
static_cast<const float*>(q->data), nullptr, qi, qs, lq);
sage_quantize<D, 64, true><<<dim3(GGML_PAD(lk, 64) / 64, hk * batch), 256, 0, stream>>>(
static_cast<const float*>(k->data), km, ki, ks, lk);
constexpr size_t shared_bytes = (128 + 64 + 64 * 2) * D;
auto kernel = sageattention::qk_int_sv_f16_attn_kernel<128, 64, 32, 64, D,
sageattention::DataType::kInt8, sageattention::QuantGranularity::kPerWarp,
sageattention::QuantGranularity::kPerWarp, float, false, half,
sageattention::ComputeUnit::kTensorCore, sageattention::MaskMode::kNone, false, false>;
kernel<<<dim3(GGML_PAD(lq, 128) / 128, hq, batch), dim3(32, 4), shared_bytes, stream>>>(
qi, ki, static_cast<half*>(v->data), out, nullptr, qs, ks, nullptr,
lq, lk, hq / hk,
D * lq * hq, D, D * lq,
D * lk * hk, D, D * lk,
D * lk * hk, D, D * lk,
D * lq * hq, D * hq, D,
scale);
sage_output<<<(ggml_nelements(dst) + 255) / 256, 256, 0, stream>>>(out, static_cast<float*>(dst->data), ggml_nelements(dst));
}
static bool sage_supports(ggml_backend_dev_t, const ggml_tensor* op, void* user_data) {
const auto& device = *static_cast<const SageDevice*>(user_data);
SDSageAttentionParams params;
if (!sd_ggml_sage_attn_describe(op, &params) || device.cc < 80 || device.compiled_cc < 80)
return false;
const auto mode = sage_mode(device, params.mode);
if (mode != GGML_SAGE_ATTN_AUTO && (sage_cuda_version < 12040 || device.cc < 89 || device.compiled_cc < 89))
return false;
if (mode == GGML_SAGE_ATTN_2_PLUS_PLUS && sage_cuda_version < 12080)
return false;
const auto* q = op->src[0];
const auto* k = op->src[1];
const auto* v = op->src[2];
if (q->ne[0] != 64 && q->ne[0] != 128)
return false;
for (const auto* t : {q, k, v, op}) {
if (ggml_nelements(t) > INT_MAX || t->ne[2] * t->ne[3] > 65535 ||
(reinterpret_cast<uintptr_t>(t->data) & 15))
return false;
}
return k->ne[0] * GGML_PAD(k->ne[1], 64) * k->ne[2] * k->ne[3] <= INT_MAX;
}
static ggml_status sage_prepare(ggml_backend_t, ggml_tensor* const* nodes, int, void**, size_t* workspace, void*) {
*workspace = SageWorkspace(nodes[0]).size;
return GGML_STATUS_SUCCESS;
}
static void sage_destroy(void*, void*) {}
static ggml_status sage_execute(const ggml_ext_execution_context* execution, ggml_tensor* const* nodes, int, void*, void* user_data) {
ggml_ext_gpu_context context;
if (!cuda_context(execution->backend, &context))
return GGML_STATUS_FAILED;
SDSageAttentionParams params;
if (!sd_ggml_sage_attn_describe(nodes[0], &params))
return GGML_STATUS_FAILED;
const auto mode = sage_mode(*static_cast<const SageDevice*>(user_data), params.mode);
auto* data = static_cast<char*>(ggml_backend_buffer_get_base(execution->workspace));
auto stream = static_cast<cudaStream_t>(context.stream);
if (nodes[0]->src[0]->ne[0] == 64)
sage_attn<64>(stream, nodes[0], data, mode, params.scale);
else
sage_attn<128>(stream, nodes[0], data, mode, params.scale);
return cudaGetLastError() == cudaSuccess ? GGML_STATUS_SUCCESS : GGML_STATUS_FAILED;
}
void register_sage_attention(ggml_backend_dev_t device) {
const auto info = cuda_device_info(device);
if (info.device < 0)
return;
int previous;
cudaDeviceProp properties;
cudaFuncAttributes attributes;
if (cudaGetDeviceProperties(&properties, info.device) != cudaSuccess || cudaGetDevice(&previous) != cudaSuccess ||
cudaSetDevice(info.device) != cudaSuccess)
return;
const auto error = cudaFuncGetAttributes(&attributes, sage_output);
if (cudaSetDevice(previous) != cudaSuccess || error != cudaSuccess)
return;
// PTX compiled for an older target retains that target's instruction guards after JIT.
static std::map<int, SageDevice> devices;
auto& data = devices[info.device];
data = {properties.major * 10 + properties.minor, std::min(attributes.binaryVersion, attributes.ptxVersion)};
ggml_ext_kernel_desc desc = {};
desc.abi_version = GGML_EXT_ABI_VERSION;
desc.name = "sd.sage_attn.cuda.v1";
desc.op = GGML_OP_CUSTOM;
desc.custom_op = sd_ggml_sage_attn_op();
desc.priority = 100;
desc.supports = sage_supports;
desc.prepare = sage_prepare;
desc.destroy = sage_destroy;
desc.execute = sage_execute;
desc.user_data = &data;
GGML_ASSERT(ggml_ext_register_kernel_ex(device, &desc, GGML_EXT_KERNEL_CAPTURE_SAFE | GGML_EXT_KERNEL_REPLAY_REUSABLE));
}
}
#else
namespace sd_cuda {
void register_sage_attention(ggml_backend_dev_t) {}
}
#endif
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2024 Jintao Zhang, Haofeng Huang
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+28
View File
@@ -0,0 +1,28 @@
# SageAttention CUDA device code
Adapted from [thu-ml/SageAttention](https://github.com/thu-ml/SageAttention),
revision `d1a57a546c3d395b1ffcbeecc66d81db76f3b4b5`, under Apache-2.0 (see LICENSE).
`attn.cuh` contains the device kernel from
`csrc/qattn/qk_int_sv_f16_cuda_sm80.cu`; `attn_fp8.cuh` adapts
`csrc/qattn/qk_int_sv_f8_cuda_sm89.cuh`. The remaining headers come from `csrc/`
and `csrc/qattn/attn_utils.cuh`. PyTorch includes and host wrappers were removed;
include paths and guards were adjusted, device helpers were placed in the
`sageattention` namespace, and `div_ceil` was renamed to avoid macro collisions.
Unused `<cuda/pipeline>` includes were removed so multi-architecture builds can
also compile targets below SM70; the backend rejects SageAttention below SM80.
Predicated loads now zero unused FP16 V lanes: masked probabilities cannot
suppress NaNs left in shared memory by previous kernels.
The sd.cpp adapter in `../sage_attention.cu` supplies FP32 K smoothing and native CUDA
per-thread INT8 quantization, matching the token grouping in upstream
`sageattention/triton/quant_per_thread.py`. V is quantized per channel and
transposed, padded with zeros, and permuted according to
`csrc/fused/fused.cu`. Its FP8 range is 448 for SageAttention2 and 2.25 for
SageAttention2++. Zero channels use a positive scale to avoid division by zero.
SageAttention2 uses an FP32 instruction buffer; 2++ uses an FP16 instruction
buffer added to FP32 accumulators. The SM80 compatibility path retains per-warp
INT8 Q/K and FP16 V with FP32 accumulation.
The adapter requests workspace and the current stream through public ggml extension APIs; it has no Python, PyTorch,
or Triton dependency. The public output is converted back to FP32.
+686
View File
@@ -0,0 +1,686 @@
/*
* Copyright (c) 2024 by SageAttention team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SD_CORE_CUDA_SAGEATTENTION_ATTN_CUH
#define SD_CORE_CUDA_SAGEATTENTION_ATTN_CUH
#include "math.cuh"
#include "attn_utils.cuh"
namespace sageattention {
#define PACK_SIZE_QK 16 // as if it is int8
#define PACK_SIZE_V 8 // fp16
#define PACK_SIZE_O 8 // fp16
// treat as if int8 tensor core
#define MMA_QK_M 16
#define MMA_QK_N 16
#define MMA_QK_K 32
// fp16 tensor core
#define MMA_SV_M 16
#define MMA_SV_N 16
#define MMA_SV_K 16
template<uint32_t CTA_Q, uint32_t CTA_K, uint32_t WARP_Q, uint32_t WARP_K, uint32_t head_dim, DataType DTypeQK, QuantGranularity Q_GRAN, QuantGranularity K_GRAN,
typename DTypeSVAccum = float, bool use_inst_buffer = false, typename DTypeOut = half, ComputeUnit DenominatorAccumUnit, MaskMode mask_mode = MaskMode::kNone, bool return_lse = false, bool fuse_v_mean=false>
__global__ void qk_int_sv_f16_attn_kernel(int8_t *__restrict__ Q, int8_t *__restrict__ K, half *__restrict__ V, DTypeOut *__restrict__ O, float *__restrict__ Lse,
float *__restrict__ Q_scale, float *__restrict__ K_scale, DTypeOut *__restrict__ V_mean,
const uint32_t qo_len, const uint32_t kv_len, const uint32_t num_kv_groups,
const uint32_t stride_bz_q, const uint32_t stride_seq_q, const uint32_t stride_h_q,
const uint32_t stride_bz_k, const uint32_t stride_seq_k, const uint32_t stride_h_k,
const uint32_t stride_bz_v, const uint32_t stride_seq_v, const uint32_t stride_h_v,
const uint32_t stride_bz_o, const uint32_t stride_seq_o, const uint32_t stride_h_o,
float sm_scale)
{
// Older CUDA headers omit BF16 intrinsics in pre-Ampere device passes.
#if !defined(__CUDA_ARCH__) || __CUDA_ARCH__ >= 800
// compile time check
static_assert(DTypeQK == DataType::kInt8 || DTypeQK == DataType::kInt4, "DTypeQK must be int8 or int4");
static_assert(Q_GRAN == QuantGranularity::kPerBlock || Q_GRAN == QuantGranularity::kPerWarp || Q_GRAN == QuantGranularity::kPerThread, "Q_GRAN must be kPerBlock, kPerWarp or kPerThread");
static_assert(K_GRAN == QuantGranularity::kPerBlock || K_GRAN == QuantGranularity::kPerWarp || K_GRAN == QuantGranularity::kPerThread, "K_GRAN must be kPerBlock, kPerWarp or kPerThread");
static_assert(std::is_same<DTypeSVAccum, float>::value || !use_inst_buffer, "use_inst_buffer only supports DTypeSVAccum as float");
static_assert(std::is_same<DTypeSVAccum, float>::value || std::is_same<DTypeSVAccum, half>::value, "DTypeSVAccum must be float or half");
static_assert(std::is_same<DTypeOut, half>::value || std::is_same<DTypeOut, nv_bfloat16>::value, "DTypeOut must be half or nv_bfloat16");
static_assert(head_dim % 64 == 0, "head_dim must be a multiple of 64");
static_assert(!fuse_v_mean || std::is_same<DTypeSVAccum, half>::value, "fuse_v_mean only supports half");
static_assert(CTA_Q / CTA_K <= 2); // for efficient causal implementation
using DTypeOut2 = typename std::conditional<std::is_same<DTypeOut, half>::value, half2, nv_bfloat162>::type;
constexpr uint32_t num_warps_q = CTA_Q / WARP_Q;
constexpr uint32_t num_warps_k = CTA_K / WARP_K;
constexpr uint32_t num_warps = num_warps_q * num_warps_k;
constexpr uint32_t num_tiles_q = WARP_Q / MMA_QK_M;
constexpr uint32_t num_tiles_k = WARP_K / MMA_QK_N;
constexpr uint32_t num_tiles_qk_inner = (DTypeQK == DataType::kInt8) ? (head_dim / MMA_QK_K) : (head_dim / 2 / MMA_QK_K);
constexpr uint32_t num_tiles_v = head_dim / MMA_SV_N;
constexpr uint32_t QK_SMEM_STRIDE = (DTypeQK == DataType::kInt8) ? (head_dim) : (head_dim / 2);
constexpr uint32_t O_SMEM_STRIDE = head_dim;
constexpr uint32_t V_SMEM_STRIDE = head_dim;
extern __shared__ int8_t smem[];
const uint32_t lane_id = get_lane_id();
const uint32_t warp_id = get_warp_id();
// maximize L2 hit rate
const uint32_t batch_id = blockIdx.z;
const uint32_t bx = blockIdx.x;
const uint32_t num_qo_heads = gridDim.y;
const uint32_t head_id = blockIdx.y;
// transfer to base 2 instead of base e with better numerical efficiency
sm_scale *= math::log2e;
// RS holds the fragment of S
int32_t RS[num_tiles_q][num_tiles_k][8];
DTypeSVAccum RO[num_tiles_q][num_tiles_v][8];
float m[num_tiles_q][2]; // max
float d[num_tiles_q][2]; // denominator
uint32_t q_scale_idx, k_scale_idx;
if constexpr (Q_GRAN == QuantGranularity::kPerBlock)
{
const uint32_t num_block_q = gridDim.x;
q_scale_idx = batch_id * num_qo_heads * num_block_q + head_id * num_block_q + bx;
}
else if constexpr (Q_GRAN == QuantGranularity::kPerWarp)
{
const uint32_t num_warp_block_q = gridDim.x * num_warps_q;
q_scale_idx = batch_id * num_qo_heads * num_warp_block_q + head_id * num_warp_block_q + bx * num_warps_q + get_warp_idx_q<num_warps_q, num_warps_k>();
}
else if constexpr (Q_GRAN == QuantGranularity::kPerThread)
{
const uint32_t num_warp_block_q = gridDim.x * num_warps_q;
q_scale_idx = batch_id * num_qo_heads * (num_warp_block_q * 8) + head_id * (num_warp_block_q * 8) + bx * (num_warps_q * 8) + get_warp_idx_q<num_warps_q, num_warps_k>() * 8 + lane_id / 4;
}
if constexpr (K_GRAN == QuantGranularity::kPerBlock)
{
const uint32_t num_block_k = SAGE_DIV_CEIL(kv_len, CTA_K);
k_scale_idx = batch_id * (num_qo_heads / num_kv_groups) * num_block_k + (head_id / num_kv_groups) * num_block_k;
}
else if constexpr (K_GRAN == QuantGranularity::kPerWarp)
{
const uint32_t num_warp_block_k = SAGE_DIV_CEIL(kv_len, CTA_K) * (CTA_K / WARP_K);
k_scale_idx = batch_id * (num_qo_heads / num_kv_groups) * num_warp_block_k + (head_id / num_kv_groups) * num_warp_block_k + get_warp_idx_k<num_warps_q, num_warps_k>();
}
else if constexpr (K_GRAN == QuantGranularity::kPerThread)
{
const uint32_t num_warp_block_k = SAGE_DIV_CEIL(kv_len, CTA_K) * (CTA_K / WARP_K);
k_scale_idx = batch_id * (num_qo_heads / num_kv_groups) * (num_warp_block_k * 4) + (head_id / num_kv_groups) * (num_warp_block_k * 4) + get_warp_idx_k<num_warps_q, num_warps_k>() * 4 + lane_id % 4;
}
constexpr uint32_t k_scale_advance_offset = (K_GRAN == QuantGranularity::kPerBlock) ? 1 : (K_GRAN == QuantGranularity::kPerWarp) ? (CTA_K / WARP_K) : (CTA_K / WARP_K) * 4;
// initialize o, m, d
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
#pragma unroll
for (uint32_t k = 0; k < 8; k++)
{
RO[fq][fv][k] = 0.0f;
}
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
#pragma unroll
for (uint32_t k = 0; k < 4; k++)
{
((int32_t*)RO[fq][fv])[k] = 0;
}
}
}
}
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t k = 0; k < 2; k++)
{
m[fq][k] = -5000000.0f;
d[fq][k] = 1.0f;
}
}
constexpr uint32_t K_smem_idx_offset = CTA_Q;
constexpr uint32_t V_smem_idx_offset = CTA_Q + CTA_K;
constexpr SwizzleMode swizzle_mode_QK = (QK_SMEM_STRIDE == 32) ? SwizzleMode::k32B : (QK_SMEM_STRIDE == 64) ? SwizzleMode::k64B : SwizzleMode::k128B;
smem_t<swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK> smem_Q(smem);
smem_t<swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK> smem_K(smem + K_smem_idx_offset * QK_SMEM_STRIDE);
constexpr SwizzleMode swizzle_mode_V = (V_SMEM_STRIDE == 32) ? SwizzleMode::k64B : SwizzleMode::k128B;
smem_t<swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V> smem_V(smem + V_smem_idx_offset * QK_SMEM_STRIDE);
constexpr SwizzleMode swizzle_mode_O = (O_SMEM_STRIDE == 32) ? SwizzleMode::k64B : SwizzleMode::k128B;
smem_t<swizzle_mode_O, O_SMEM_STRIDE / PACK_SIZE_O> smem_O(smem);
constexpr uint32_t global_to_shared_line_lanes_QK = (QK_SMEM_STRIDE == 32) ? 2 : (QK_SMEM_STRIDE == 64) ? 4 : 8;
constexpr uint32_t global_to_shared_copy_lines_per_warp_QK = (QK_SMEM_STRIDE == 32) ? 16 : (QK_SMEM_STRIDE == 64) ? 8 : 4;
constexpr uint32_t global_to_shared_line_lanes_V = (V_SMEM_STRIDE == 32) ? 4 : 8;
constexpr uint32_t global_to_shared_copy_lines_per_warp_V = (V_SMEM_STRIDE == 32) ? 8 : 4;
constexpr uint32_t global_to_shared_line_lanes_O = (O_SMEM_STRIDE == 32) ? 4 : 8;
constexpr uint32_t global_to_shared_copy_lines_per_warp_O = (O_SMEM_STRIDE == 32) ? 8 : 4;
constexpr uint32_t QK_smem_iters_row = QK_SMEM_STRIDE / (global_to_shared_line_lanes_QK * PACK_SIZE_QK);
constexpr uint32_t Q_smem_iters_col = CTA_Q / (num_warps * global_to_shared_copy_lines_per_warp_QK);
constexpr uint32_t K_smem_iters_col = CTA_K / (num_warps * global_to_shared_copy_lines_per_warp_QK);
constexpr uint32_t V_smem_iters_row = V_SMEM_STRIDE / (global_to_shared_line_lanes_V * PACK_SIZE_V);
constexpr uint32_t V_smem_iters_col = CTA_K / (num_warps * global_to_shared_copy_lines_per_warp_V);
constexpr uint32_t O_smem_iters_row = O_SMEM_STRIDE / (global_to_shared_line_lanes_O * PACK_SIZE_O);
constexpr uint32_t O_smem_iters_col = CTA_Q / (num_warps * global_to_shared_copy_lines_per_warp_O);
int8_t *Q_lane_base_ptr = Q + batch_id * stride_bz_q + head_id * stride_h_q + (bx * CTA_Q + CTA_Q / num_warps * warp_id + lane_id / global_to_shared_line_lanes_QK) * stride_seq_q + (lane_id % global_to_shared_line_lanes_QK) * PACK_SIZE_QK;
int8_t *K_lane_base_ptr = K + batch_id * stride_bz_k + (head_id / num_kv_groups) * stride_h_k + (CTA_K / num_warps * warp_id + lane_id / global_to_shared_line_lanes_QK) * stride_seq_k + (lane_id % global_to_shared_line_lanes_QK) * PACK_SIZE_QK;
half *V_lane_base_ptr = V + batch_id * stride_bz_v + (head_id / num_kv_groups) * stride_h_v + (CTA_K / num_warps * warp_id + lane_id / global_to_shared_line_lanes_V) * stride_seq_v + (lane_id % global_to_shared_line_lanes_V) * PACK_SIZE_V;
uint32_t Q_smem_offset_load = smem_Q.get_permuted_offset(warp_id * global_to_shared_copy_lines_per_warp_QK * Q_smem_iters_col + lane_id / global_to_shared_line_lanes_QK, lane_id % global_to_shared_line_lanes_QK);
uint32_t K_smem_offset_load = smem_K.get_permuted_offset(warp_id * global_to_shared_copy_lines_per_warp_QK * K_smem_iters_col + lane_id / global_to_shared_line_lanes_QK, lane_id % global_to_shared_line_lanes_QK);
uint32_t V_smem_offset_load = smem_V.get_permuted_offset(warp_id * global_to_shared_copy_lines_per_warp_V * V_smem_iters_col + lane_id / global_to_shared_line_lanes_V, lane_id % global_to_shared_line_lanes_V);
uint32_t Q_smem_offset_mma = smem_Q.get_permuted_offset(get_warp_idx_q<num_warps_q, num_warps_k>() * WARP_Q + lane_id % 16, lane_id / 16);
uint32_t K_smem_offset_mma = smem_K.get_permuted_offset(get_warp_idx_k<num_warps_q, num_warps_k>() * WARP_K + lane_id % 8 + (lane_id / 16) * 8, (lane_id / 8) % 2);
uint32_t V_smem_offset_mma = smem_V.get_permuted_offset(get_warp_idx_k<num_warps_q, num_warps_k>() * WARP_K + lane_id % 16, lane_id / 16);
// for causal masking
uint32_t Q_idx_lane_base = bx * CTA_Q + get_warp_idx_q<num_warps_q, num_warps_k>() * WARP_Q + lane_id / 4;
uint32_t K_idx_lane_base = get_warp_idx_k<num_warps_q, num_warps_k>() * WARP_K + 2 * (lane_id % 4);
// for loading
uint32_t Q_load_idx_lane_base = bx * CTA_Q + CTA_Q / num_warps * warp_id + lane_id / global_to_shared_line_lanes_QK;
uint32_t K_load_idx_lane_base = CTA_K / num_warps * warp_id + lane_id / global_to_shared_line_lanes_QK;
uint32_t V_load_idx_lane_base = CTA_K / num_warps * warp_id + lane_id / global_to_shared_line_lanes_V;
const uint32_t num_iterations = SAGE_DIV_CEIL(
mask_mode == MaskMode::kCausal
? min(kv_len, (bx + 1) * CTA_Q)
: kv_len,
CTA_K);
// load Q with predicate
load_global_to_share<global_to_shared_line_lanes_QK, global_to_shared_copy_lines_per_warp_QK, QK_smem_iters_row, Q_smem_iters_col, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, CTA_Q>(
&Q_lane_base_ptr, Q_smem_offset_load, stride_seq_q, smem_Q, Q_load_idx_lane_base, qo_len);
cp_async::commit_group();
cp_async::wait_group<0>();
__syncthreads();
// for num_tiles_qk_inner = 1, we load all Qs in register
uint32_t RQ[num_tiles_q][4];
if constexpr (num_tiles_qk_inner == 1)
{
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
smem_Q.ldmatrix_m8n8x4(Q_smem_offset_mma, RQ[fq]);
Q_smem_offset_mma = smem_Q.advance_offset_by_row<16>(Q_smem_offset_mma);
}
}
// load K with predicate
load_global_to_share<global_to_shared_line_lanes_QK, global_to_shared_copy_lines_per_warp_QK, QK_smem_iters_row, K_smem_iters_col, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, CTA_K>(
&K_lane_base_ptr, K_smem_offset_load, stride_seq_k, smem_K, K_load_idx_lane_base, kv_len);
cp_async::commit_group();
float q_scale = Q_scale[q_scale_idx];
float original_sm_scale = sm_scale;
float dequant_scale = q_scale * K_scale[k_scale_idx + 0 * k_scale_advance_offset];
sm_scale = original_sm_scale * dequant_scale;
// load V with predicate
load_global_to_share<global_to_shared_line_lanes_V, global_to_shared_copy_lines_per_warp_V, V_smem_iters_row, V_smem_iters_col, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, CTA_K>(
&V_lane_base_ptr, V_smem_offset_load, stride_seq_v, smem_V, V_load_idx_lane_base, kv_len);
cp_async::commit_group();
K_load_idx_lane_base += CTA_K;
V_load_idx_lane_base += CTA_K;
#pragma unroll
for (uint32_t iter = 1; iter < num_iterations - 1; iter++)
{
// ensure K is ready
cp_async::wait_group<1>();
__syncthreads();
// compute QK^T
if constexpr (num_tiles_qk_inner == 1)
{
compute_int_qk<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_qk_inner, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, DTypeQK>(
smem_K, RS, RQ, K_smem_offset_mma);
}
else
{
compute_int_qk<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_qk_inner, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, DTypeQK>(
smem_Q, smem_K, RS, Q_smem_offset_mma, K_smem_offset_mma);
}
float RS_f32[num_tiles_q][num_tiles_k][8];
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
#pragma unroll
for (uint32_t k = 0; k < 8; k++)
{
RS_f32[fq][fk][k] = __int2float_rz(RS[fq][fk][k]);
}
}
}
// do not apply causal mask and out of bound mask for these iterations
K_idx_lane_base += CTA_K;
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
update_mdo<num_tiles_q, num_tiles_k, num_tiles_v, false, false, false>(RS_f32, RO, m, d, sm_scale);
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
update_mdo<num_tiles_q, num_tiles_k, num_tiles_v, true, false, false>(RS_f32, RO, m, d, sm_scale);
}
if constexpr (DenominatorAccumUnit == ComputeUnit::kCudaCore)
{
accumulate_d<num_tiles_q, num_tiles_k, ComputeUnit::kCudaCore>(RS_f32, d);
}
uint32_t RS_f16[num_tiles_q][num_tiles_k][4];
RS_32_to_16<num_tiles_q, num_tiles_k>(RS_f32, RS_f16);
if constexpr (DenominatorAccumUnit == ComputeUnit::kTensorCore)
{
accumulate_d<num_tiles_q, num_tiles_k, ComputeUnit::kTensorCore>(RS_f16, d);
}
__syncthreads();
// load K
load_global_to_share<global_to_shared_line_lanes_QK, global_to_shared_copy_lines_per_warp_QK, QK_smem_iters_row, K_smem_iters_col, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, CTA_K>(
&K_lane_base_ptr, K_smem_offset_load, stride_seq_k, smem_K);
cp_async::commit_group();
dequant_scale = q_scale * K_scale[k_scale_idx + iter * k_scale_advance_offset];
sm_scale = original_sm_scale * dequant_scale;
// ensure V is ready
cp_async::wait_group<1>();
__syncthreads();
if constexpr (!use_inst_buffer)
{
compute_fp16_sv_permuted<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, 4>(
smem_V, RS_f16, RO, d, V_smem_offset_mma);
}
else
{
compute_fp16_sv_permuted_inst_buf<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, 4>(
smem_V, RS_f16, RO, d, V_smem_offset_mma);
}
__syncthreads();
// load V
load_global_to_share<global_to_shared_line_lanes_V, global_to_shared_copy_lines_per_warp_V, V_smem_iters_row, V_smem_iters_col, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, CTA_K>(
&V_lane_base_ptr, V_smem_offset_load, stride_seq_v, smem_V);
cp_async::commit_group();
K_load_idx_lane_base += CTA_K;
V_load_idx_lane_base += CTA_K;
}
// second last iter, apply causal mask
if (num_iterations > 1)
{
// ensure K is ready
cp_async::wait_group<1>();
__syncthreads();
// compute QK^T
if constexpr (num_tiles_qk_inner == 1)
{
compute_int_qk<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_qk_inner, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, DTypeQK>(
smem_K, RS, RQ, K_smem_offset_mma);
}
else
{
compute_int_qk<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_qk_inner, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, DTypeQK>(
smem_Q, smem_K, RS, Q_smem_offset_mma, K_smem_offset_mma);
}
float RS_f32[num_tiles_q][num_tiles_k][8];
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
#pragma unroll
for (uint32_t k = 0; k < 8; k++)
{
RS_f32[fq][fk][k] = __int2float_rz(RS[fq][fk][k]) * dequant_scale;
}
}
}
if constexpr (mask_mode == MaskMode::kCausal)
{
apply_causal_mask<num_tiles_q, num_tiles_k>(Q_idx_lane_base, K_idx_lane_base, RS_f32);
}
// apply_out_of_bound_mask<num_tiles_q, num_tiles_k>(K_idx_lane_base, RS_f32, kv_len);
K_idx_lane_base += CTA_K;
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
update_mdo<num_tiles_q, num_tiles_k, num_tiles_v, false, false, false>(RS_f32, RO, m, d, original_sm_scale);
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
update_mdo<num_tiles_q, num_tiles_k, num_tiles_v, true, false, false>(RS_f32, RO, m, d, original_sm_scale);
}
if constexpr (DenominatorAccumUnit == ComputeUnit::kCudaCore)
{
accumulate_d<num_tiles_q, num_tiles_k, ComputeUnit::kCudaCore>(RS_f32, d);
}
uint32_t RS_f16[num_tiles_q][num_tiles_k][4];
RS_32_to_16<num_tiles_q, num_tiles_k>(RS_f32, RS_f16);
if constexpr (DenominatorAccumUnit == ComputeUnit::kTensorCore)
{
accumulate_d<num_tiles_q, num_tiles_k, ComputeUnit::kTensorCore>(RS_f16, d);
}
__syncthreads();
// load K with predicate
load_global_to_share<global_to_shared_line_lanes_QK, global_to_shared_copy_lines_per_warp_QK, QK_smem_iters_row, K_smem_iters_col, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, CTA_K>(
&K_lane_base_ptr, K_smem_offset_load, stride_seq_k, smem_K, K_load_idx_lane_base, kv_len);
cp_async::commit_group();
dequant_scale = q_scale * K_scale[k_scale_idx + (num_iterations - 1) * k_scale_advance_offset];
sm_scale = original_sm_scale * dequant_scale;
// ensure V is ready
cp_async::wait_group<1>();
__syncthreads();
if constexpr (!use_inst_buffer)
{
compute_fp16_sv_permuted<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, 4>(
smem_V, RS_f16, RO, d, V_smem_offset_mma);
}
else
{
compute_fp16_sv_permuted_inst_buf<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, 4>(
smem_V, RS_f16, RO, d, V_smem_offset_mma);
}
__syncthreads();
// load V with predicate
load_global_to_share<global_to_shared_line_lanes_V, global_to_shared_copy_lines_per_warp_V, V_smem_iters_row, V_smem_iters_col, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, CTA_K>(
&V_lane_base_ptr, V_smem_offset_load, stride_seq_v, smem_V, V_load_idx_lane_base, kv_len);
cp_async::commit_group();
K_load_idx_lane_base += CTA_K;
V_load_idx_lane_base += CTA_K;
}
// last iter, apply causal mask and out of bound mask
{
// ensure K is ready
cp_async::wait_group<1>();
__syncthreads();
// compute QK^T
if constexpr (num_tiles_qk_inner == 1)
{
compute_int_qk<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_qk_inner, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, DTypeQK>(
smem_K, RS, RQ, K_smem_offset_mma);
}
else
{
compute_int_qk<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_qk_inner, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, DTypeQK>(
smem_Q, smem_K, RS, Q_smem_offset_mma, K_smem_offset_mma);
}
float RS_f32[num_tiles_q][num_tiles_k][8];
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
#pragma unroll
for (uint32_t k = 0; k < 8; k++)
{
RS_f32[fq][fk][k] = __int2float_rz(RS[fq][fk][k]) * dequant_scale;
}
}
}
if constexpr (mask_mode == MaskMode::kCausal)
{
apply_causal_mask<num_tiles_q, num_tiles_k>(Q_idx_lane_base, K_idx_lane_base, RS_f32);
}
// check out of bound in the last iter
apply_out_of_bound_mask<num_tiles_q, num_tiles_k>(K_idx_lane_base, RS_f32, kv_len);
K_idx_lane_base += CTA_K;
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
update_mdo<num_tiles_q, num_tiles_k, num_tiles_v, false, false, false>(RS_f32, RO, m, d, original_sm_scale);
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
update_mdo<num_tiles_q, num_tiles_k, num_tiles_v, true, false, false>(RS_f32, RO, m, d, original_sm_scale);
}
if constexpr (DenominatorAccumUnit == ComputeUnit::kCudaCore)
{
accumulate_d<num_tiles_q, num_tiles_k, ComputeUnit::kCudaCore>(RS_f32, d);
}
uint32_t RS_f16[num_tiles_q][num_tiles_k][4];
RS_32_to_16<num_tiles_q, num_tiles_k>(RS_f32, RS_f16);
if constexpr (DenominatorAccumUnit == ComputeUnit::kTensorCore)
{
accumulate_d<num_tiles_q, num_tiles_k, ComputeUnit::kTensorCore>(RS_f16, d);
}
// ensure V is ready
cp_async::wait_group<0>();
__syncthreads();
if constexpr (!use_inst_buffer)
{
compute_fp16_sv_permuted<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, 4>(
smem_V, RS_f16, RO, d, V_smem_offset_mma);
}
else
{
compute_fp16_sv_permuted_inst_buf<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, 4>(
smem_V, RS_f16, RO, d, V_smem_offset_mma);
}
__syncthreads();
}
// TODO: thread block sync mdo state for num_warps_k > 0
normalize_d<num_tiles_q, num_tiles_v, DenominatorAccumUnit>(RO, m, d);
// save the result
// if (get_warp_idx_k<num_warps_q, num_warps_k>() == 0)
// {
// convert half to bfloat16
if constexpr (std::is_same<DTypeSVAccum, half>::value && std::is_same<DTypeOut, nv_bfloat16>::value)
{
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
((nv_bfloat162*)RO[fq][fv])[0] = __float22bfloat162_rn(__half22float2(((half2*)RO[fq][fv])[0]));
((nv_bfloat162*)RO[fq][fv])[1] = __float22bfloat162_rn(__half22float2(((half2*)RO[fq][fv])[1]));
((nv_bfloat162*)RO[fq][fv])[2] = __float22bfloat162_rn(__half22float2(((half2*)RO[fq][fv])[2]));
((nv_bfloat162*)RO[fq][fv])[3] = __float22bfloat162_rn(__half22float2(((half2*)RO[fq][fv])[3]));
}
}
}
// add v_mean
if constexpr (fuse_v_mean)
{
DTypeOut2 v_mean[2];
DTypeOut *V_mean_lane_ptr = V_mean + batch_id * (num_qo_heads / num_kv_groups) * head_dim + (head_id / num_kv_groups) * head_dim + lane_id % 4 * 2;
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
v_mean[0] = *((DTypeOut2*)(V_mean_lane_ptr + fv * 16));
v_mean[1] = *((DTypeOut2*)(V_mean_lane_ptr + 8 + fv * 16));
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
((DTypeOut2*)RO[fq][fv])[0] = __hadd2(((DTypeOut2*)RO[fq][fv])[0], v_mean[0]);
((DTypeOut2*)RO[fq][fv])[1] = __hadd2(((DTypeOut2*)RO[fq][fv])[1], v_mean[0]);
((DTypeOut2*)RO[fq][fv])[2] = __hadd2(((DTypeOut2*)RO[fq][fv])[2], v_mean[1]);
((DTypeOut2*)RO[fq][fv])[3] = __hadd2(((DTypeOut2*)RO[fq][fv])[3], v_mean[1]);
}
}
}
// save the result to shared memory
uint32_t smem_O_row_base = get_warp_idx_q<num_warps_q, num_warps_k>() * WARP_Q + lane_id / 4;
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
uint32_t offset_O = smem_O.get_permuted_offset(smem_O_row_base + fq * MMA_QK_M, fv * (MMA_SV_N / PACK_SIZE_O));
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
// convert RO to half
uint32_t RO_f16[4];
#pragma unroll
for (uint32_t k = 0; k < 4; k++)
{
if constexpr (std::is_same<DTypeOut, half>::value)
{
((half2*)RO_f16)[k] = __float22half2_rn(((float2*)RO[fq][fv])[k]);
}
else if constexpr (std::is_same<DTypeOut, nv_bfloat16>::value)
{
((nv_bfloat162*)RO_f16)[k] = __float22bfloat162_rn(((float2*)RO[fq][fv])[k]);
}
}
((uint32_t*)(smem_O.base + offset_O))[lane_id % 4] = RO_f16[0];
((uint32_t*)(smem_O.base + offset_O + 8 * (O_SMEM_STRIDE / PACK_SIZE_O)))[lane_id % 4] = RO_f16[1];
// ! permuted, make sure you know what you are doing
((uint32_t*)(smem_O.base + (offset_O ^ 0x1)))[lane_id % 4] = RO_f16[2];
((uint32_t*)(smem_O.base + (offset_O ^ 0x1) + 8 * (O_SMEM_STRIDE / PACK_SIZE_O)))[lane_id % 4] = RO_f16[3];
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
((uint32_t*)(smem_O.base + offset_O))[lane_id % 4] = ((uint32_t*)RO[fq][fv])[0];
((uint32_t*)(smem_O.base + offset_O + 8 * (O_SMEM_STRIDE / PACK_SIZE_O)))[lane_id % 4] = ((uint32_t*)RO[fq][fv])[1];
// ! permuted, make sure you know what you are doing
((uint32_t*)(smem_O.base + (offset_O ^ 0x1)))[lane_id % 4] = ((uint32_t*)RO[fq][fv])[2];
((uint32_t*)(smem_O.base + (offset_O ^ 0x1) + 8 * (O_SMEM_STRIDE / PACK_SIZE_O)))[lane_id % 4] = ((uint32_t*)RO[fq][fv])[3];
}
}
}
// ! do we need to sync here?
__syncwarp();
// shared memory to global memory
DTypeOut *O_lane_ptr = O + batch_id * stride_bz_o + head_id * stride_h_o + (bx * CTA_Q + WARP_Q * get_warp_idx_q<num_warps_q, num_warps_k>() + lane_id / global_to_shared_line_lanes_O) * stride_seq_o + lane_id % global_to_shared_line_lanes_O * PACK_SIZE_O;
uint32_t offset_O = smem_O.get_permuted_offset(get_warp_idx_q<num_warps_q, num_warps_k>() * WARP_Q + lane_id / global_to_shared_line_lanes_O, lane_id % global_to_shared_line_lanes_O);
uint32_t O_load_idx_lane_base = bx * CTA_Q + CTA_Q / num_warps * warp_id + lane_id / global_to_shared_line_lanes_O;
#pragma unroll
for (uint32_t i = 0; i < O_smem_iters_col; i++)
{
#pragma unroll
for (uint32_t j = 0; j < O_smem_iters_row; j++)
{
if (O_load_idx_lane_base < qo_len)
{
smem_O.store_128b(offset_O, O_lane_ptr);
}
O_lane_ptr += (global_to_shared_line_lanes_O * PACK_SIZE_O);
offset_O = smem_O.advance_offset_by_column<global_to_shared_line_lanes_O>(offset_O);
}
offset_O = smem_O.advance_offset_by_row<global_to_shared_copy_lines_per_warp_O>(offset_O - (O_smem_iters_row * global_to_shared_line_lanes_O));
O_lane_ptr += ((global_to_shared_copy_lines_per_warp_O * stride_seq_o) - (O_smem_iters_row * global_to_shared_line_lanes_O * PACK_SIZE_O));
O_load_idx_lane_base += global_to_shared_copy_lines_per_warp_O;
}
if constexpr (return_lse)
{
uint32_t lse_idx = bx * CTA_Q + lane_id / 4 + 8 * (lane_id % 4) + WARP_Q * get_warp_idx_q<num_warps_q, num_warps_k>();
float *lse_lane_ptr = Lse + batch_id * (qo_len * num_qo_heads) + head_id * qo_len + lse_idx;
uint32_t fq = (lane_id % 4) / 2;
uint32_t k = (lane_id % 4) % 2;
if (lse_idx < qo_len && (lane_id % 4) < 2 * num_tiles_q)
{
lse_lane_ptr[0] = (math::ptx_log2(d[fq][k]) + m[fq][k]);
}
}
// }
#else
RUNTIME_ASSERT("SageAttention requires SM80 or newer");
#endif
}
#undef PACK_SIZE_QK
#undef PACK_SIZE_V
#undef PACK_SIZE_O
#undef MMA_QK_M
#undef MMA_QK_N
#undef MMA_QK_K
#undef MMA_SV_M
#undef MMA_SV_N
#undef MMA_SV_K
} // namespace sageattention
#endif
+718
View File
@@ -0,0 +1,718 @@
/*
* Copyright (c) 2024 by SageAttention team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SD_CORE_CUDA_SAGEATTENTION_ATTN_FP8_CUH
#define SD_CORE_CUDA_SAGEATTENTION_ATTN_FP8_CUH
#include "math.cuh"
#include "attn_utils.cuh"
namespace sageattention {
#define PACK_SIZE_QK 16 // as if it is int8
#define PACK_SIZE_V 16 // fp8
#define PACK_SIZE_O 8 // fp16
// treat as if int8 tensor core
#define MMA_QK_M 16
#define MMA_QK_N 16
#define MMA_QK_K 32
// fp8 tensor core
#define MMA_SV_M 16
#define MMA_SV_N 16
#define MMA_SV_K 32
template<uint32_t CTA_Q, uint32_t CTA_K, uint32_t WARP_Q, uint32_t WARP_K, uint32_t head_dim, DataType DTypeQK, QuantGranularity Q_GRAN, QuantGranularity K_GRAN,
typename DTypeSVAccum = float, bool use_inst_buffer = false, typename DTypeOut = half, ComputeUnit DenominatorAccumUnit, MaskMode mask_mode = MaskMode::kNone, bool return_lse = false, bool fuse_v_scale=false, bool fuse_v_mean=false, bool use_pv_fp16_accu=false>
__global__ void qk_int_sv_f8_attn_kernel(int8_t *__restrict__ Q, int8_t *__restrict__ K, int8_t *__restrict__ V, DTypeOut *__restrict__ O, float *__restrict__ Lse,
float *__restrict__ Q_scale, float *__restrict__ K_scale, float *__restrict__ V_scale, float *__restrict__ V_mean,
const uint32_t qo_len, const uint32_t kv_len, const uint32_t num_kv_groups,
const uint32_t stride_bz_q, const uint32_t stride_seq_q, const uint32_t stride_h_q,
const uint32_t stride_bz_k, const uint32_t stride_seq_k, const uint32_t stride_h_k,
const uint32_t stride_bz_v, const uint32_t stride_h_v, const uint32_t stride_d_v,
const uint32_t stride_bz_o, const uint32_t stride_seq_o, const uint32_t stride_h_o,
float sm_scale)
{
// Keep unsupported device passes from parsing architecture-specific intrinsics.
#if !defined(__CUDA_ARCH__) || __CUDA_ARCH__ >= 890
// compile time check
static_assert(DTypeQK == DataType::kInt8 || DTypeQK == DataType::kInt4, "DTypeQK must be int8 or int4");
static_assert(Q_GRAN == QuantGranularity::kPerBlock || Q_GRAN == QuantGranularity::kPerWarp || Q_GRAN == QuantGranularity::kPerThread, "Q_GRAN must be kPerBlock, kPerWarp or kPerThread");
static_assert(K_GRAN == QuantGranularity::kPerBlock || K_GRAN == QuantGranularity::kPerWarp || K_GRAN == QuantGranularity::kPerThread, "K_GRAN must be kPerBlock, kPerWarp or kPerThread");
static_assert(head_dim % 64 == 0, "head_dim must be a multiple of 64");
static_assert(std::is_same<DTypeSVAccum, float>::value, "DTypeSVAccum must be float, half is WIP");
static_assert(std::is_same<DTypeOut, half>::value || std::is_same<DTypeOut, nv_bfloat16>::value, "DTypeOut must be half or nv_bfloat16");
static_assert(CTA_K % 64 == 0);
static_assert(CTA_Q / CTA_K <= 2); // for efficient causal implementation
constexpr uint32_t num_warps_q = CTA_Q / WARP_Q;
constexpr uint32_t num_warps_k = CTA_K / WARP_K;
constexpr uint32_t num_warps = num_warps_q * num_warps_k;
constexpr uint32_t num_tiles_q = WARP_Q / MMA_QK_M;
constexpr uint32_t num_tiles_k = WARP_K / MMA_QK_N;
constexpr uint32_t num_tiles_qk_inner = (DTypeQK == DataType::kInt8) ? (head_dim / MMA_QK_K) : (head_dim / 2 / MMA_QK_K);
constexpr uint32_t num_tiles_v = head_dim / MMA_SV_N;
constexpr uint32_t QK_SMEM_STRIDE = (DTypeQK == DataType::kInt8) ? (head_dim) : (head_dim / 2);
constexpr uint32_t O_SMEM_STRIDE = head_dim;
// for fp16: head_dim
constexpr uint32_t V_SMEM_STRIDE = CTA_K;
extern __shared__ int8_t smem[];
const uint32_t lane_id = get_lane_id();
const uint32_t warp_id = get_warp_id();
// maximize L2 hit rate
const uint32_t batch_id = blockIdx.z;
const uint32_t bx = blockIdx.x;
const uint32_t num_qo_heads = gridDim.y;
const uint32_t head_id = blockIdx.y;
// transfer to base 2 instead of base e with better numerical efficiency
sm_scale *= math::log2e;
// RS holds the fragment of S
int32_t RS[num_tiles_q][num_tiles_k][8];
DTypeSVAccum RO[num_tiles_q][num_tiles_v][8];
float m[num_tiles_q][2]; // max
float d[num_tiles_q][2]; // denominator
uint32_t q_scale_idx, k_scale_idx;
if constexpr (Q_GRAN == QuantGranularity::kPerBlock)
{
const uint32_t num_block_q = gridDim.x;
q_scale_idx = batch_id * num_qo_heads * num_block_q + head_id * num_block_q + bx;
}
else if constexpr (Q_GRAN == QuantGranularity::kPerWarp)
{
const uint32_t num_warp_block_q = gridDim.x * num_warps_q;
q_scale_idx = batch_id * num_qo_heads * num_warp_block_q + head_id * num_warp_block_q + bx * num_warps_q + get_warp_idx_q<num_warps_q, num_warps_k>();
}
else if constexpr (Q_GRAN == QuantGranularity::kPerThread)
{
const uint32_t num_warp_block_q = gridDim.x * num_warps_q;
q_scale_idx = batch_id * num_qo_heads * (num_warp_block_q * 8) + head_id * (num_warp_block_q * 8) + bx * (num_warps_q * 8) + get_warp_idx_q<num_warps_q, num_warps_k>() * 8 + lane_id / 4;
}
if constexpr (K_GRAN == QuantGranularity::kPerBlock)
{
const uint32_t num_block_k = SAGE_DIV_CEIL(kv_len, CTA_K);
k_scale_idx = batch_id * (num_qo_heads / num_kv_groups) * num_block_k + (head_id / num_kv_groups) * num_block_k;
}
else if constexpr (K_GRAN == QuantGranularity::kPerWarp)
{
const uint32_t num_warp_block_k = SAGE_DIV_CEIL(kv_len, CTA_K) * (CTA_K / WARP_K);
k_scale_idx = batch_id * (num_qo_heads / num_kv_groups) * num_warp_block_k + (head_id / num_kv_groups) * num_warp_block_k + get_warp_idx_k<num_warps_q, num_warps_k>();
}
else if constexpr (K_GRAN == QuantGranularity::kPerThread)
{
const uint32_t num_warp_block_k = SAGE_DIV_CEIL(kv_len, CTA_K) * (CTA_K / WARP_K);
k_scale_idx = batch_id * (num_qo_heads / num_kv_groups) * (num_warp_block_k * 4) + (head_id / num_kv_groups) * (num_warp_block_k * 4) + get_warp_idx_k<num_warps_q, num_warps_k>() * 4 + lane_id % 4;
}
constexpr uint32_t k_scale_advance_offset = (K_GRAN == QuantGranularity::kPerBlock) ? 1 : (K_GRAN == QuantGranularity::kPerWarp) ? (CTA_K / WARP_K) : (CTA_K / WARP_K) * 4;
// initialize o, m, d
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
#pragma unroll
for (uint32_t k = 0; k < 8; k++)
{
RO[fq][fv][k] = 0.0f;
}
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
#pragma unroll
for (uint32_t k = 0; k < 4; k++)
{
((int32_t*)RO[fq][fv])[k] = 0;
}
}
}
}
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t k = 0; k < 2; k++)
{
m[fq][k] = -5000000.0f;
d[fq][k] = 1.0f;
}
}
constexpr uint32_t K_smem_idx_offset = CTA_Q;
constexpr uint32_t V_smem_idx_offset = CTA_Q + CTA_K;
constexpr SwizzleMode swizzle_mode_QK = (QK_SMEM_STRIDE == 32) ? SwizzleMode::k32B : (QK_SMEM_STRIDE == 64) ? SwizzleMode::k64B : SwizzleMode::k128B;
smem_t<swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK> smem_Q(smem);
smem_t<swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK> smem_K(smem + K_smem_idx_offset * QK_SMEM_STRIDE);
// for fp16: 32
constexpr SwizzleMode swizzle_mode_V = (V_SMEM_STRIDE == 64) ? SwizzleMode::k64B : SwizzleMode::k128B;
smem_t<swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V> smem_V(smem + V_smem_idx_offset * QK_SMEM_STRIDE);
constexpr SwizzleMode swizzle_mode_O = (O_SMEM_STRIDE == 32) ? SwizzleMode::k64B : SwizzleMode::k128B;
smem_t<swizzle_mode_O, O_SMEM_STRIDE / PACK_SIZE_O> smem_O(smem);
constexpr uint32_t global_to_shared_line_lanes_QK = (QK_SMEM_STRIDE == 32) ? 2 : (QK_SMEM_STRIDE == 64) ? 4 : 8;
constexpr uint32_t global_to_shared_copy_lines_per_warp_QK = (QK_SMEM_STRIDE == 32) ? 16 : (QK_SMEM_STRIDE == 64) ? 8 : 4;
// for fp16: 32
constexpr uint32_t global_to_shared_line_lanes_V = (V_SMEM_STRIDE == 64) ? 4 : 8;
// for fp16: 32
constexpr uint32_t global_to_shared_copy_lines_per_warp_V = (V_SMEM_STRIDE == 64) ? 8 : 4;
constexpr uint32_t global_to_shared_line_lanes_O = (O_SMEM_STRIDE == 32) ? 4 : 8;
constexpr uint32_t global_to_shared_copy_lines_per_warp_O = (O_SMEM_STRIDE == 32) ? 8 : 4;
constexpr uint32_t QK_smem_iters_row = QK_SMEM_STRIDE / (global_to_shared_line_lanes_QK * PACK_SIZE_QK);
constexpr uint32_t Q_smem_iters_col = CTA_Q / (num_warps * global_to_shared_copy_lines_per_warp_QK);
constexpr uint32_t K_smem_iters_col = CTA_K / (num_warps * global_to_shared_copy_lines_per_warp_QK);
constexpr uint32_t V_smem_iters_row = V_SMEM_STRIDE / (global_to_shared_line_lanes_V * PACK_SIZE_V);
// for fp16: CTA_K
constexpr uint32_t V_smem_iters_col = head_dim / (num_warps * global_to_shared_copy_lines_per_warp_V);
constexpr uint32_t O_smem_iters_row = O_SMEM_STRIDE / (global_to_shared_line_lanes_O * PACK_SIZE_O);
constexpr uint32_t O_smem_iters_col = CTA_Q / (num_warps * global_to_shared_copy_lines_per_warp_O);
int8_t *Q_lane_base_ptr = Q + batch_id * stride_bz_q + head_id * stride_h_q + (bx * CTA_Q + CTA_Q / num_warps * warp_id + lane_id / global_to_shared_line_lanes_QK) * stride_seq_q + (lane_id % global_to_shared_line_lanes_QK) * PACK_SIZE_QK;
int8_t *K_lane_base_ptr = K + batch_id * stride_bz_k + (head_id / num_kv_groups) * stride_h_k + (CTA_K / num_warps * warp_id + lane_id / global_to_shared_line_lanes_QK) * stride_seq_k + (lane_id % global_to_shared_line_lanes_QK) * PACK_SIZE_QK;
// for fp16: CTA_K / num_warps * warp_id * stride_seq_v + lane_id / global_to_shared_line_lanes_V * stride_seq_v
int8_t *V_lane_base_ptr = V + batch_id * stride_bz_v + (head_id / num_kv_groups) * stride_h_v + head_dim / num_warps * warp_id * stride_d_v + lane_id / global_to_shared_line_lanes_V * stride_d_v + (lane_id % global_to_shared_line_lanes_V) * PACK_SIZE_V;
uint32_t Q_smem_offset_load = smem_Q.get_permuted_offset(warp_id * global_to_shared_copy_lines_per_warp_QK * Q_smem_iters_col + lane_id / global_to_shared_line_lanes_QK, lane_id % global_to_shared_line_lanes_QK);
uint32_t K_smem_offset_load = smem_K.get_permuted_offset(warp_id * global_to_shared_copy_lines_per_warp_QK * K_smem_iters_col + lane_id / global_to_shared_line_lanes_QK, lane_id % global_to_shared_line_lanes_QK);
uint32_t V_smem_offset_load = smem_V.get_permuted_offset(warp_id * global_to_shared_copy_lines_per_warp_V * V_smem_iters_col + lane_id / global_to_shared_line_lanes_V, lane_id % global_to_shared_line_lanes_V);
uint32_t Q_smem_offset_mma = smem_Q.get_permuted_offset(get_warp_idx_q<num_warps_q, num_warps_k>() * WARP_Q + lane_id % 16, lane_id / 16);
uint32_t K_smem_offset_mma = smem_K.get_permuted_offset(get_warp_idx_k<num_warps_q, num_warps_k>() * WARP_K + lane_id % 8 + (lane_id / 16) * 8, (lane_id / 8) % 2);
// for fp 16:
// uint32_t V_smem_offset_mma = smem_V.get_permuted_offset(get_warp_idx_k<num_warps_q, num_warps_k>() * WARP_K + lane_id % 16, lane_id / 16);
uint32_t V_smem_offset_mma = smem_V.get_permuted_offset(lane_id % 8 + (lane_id / 16) * 8, get_warp_idx_k<num_warps_q, num_warps_k>() * WARP_K / PACK_SIZE_V + (lane_id / 8) % 2);
// for causal masking
uint32_t Q_idx_lane_base = bx * CTA_Q + get_warp_idx_q<num_warps_q, num_warps_k>() * WARP_Q + lane_id / 4;
uint32_t K_idx_lane_base = get_warp_idx_k<num_warps_q, num_warps_k>() * WARP_K + 2 * (lane_id % 4);
// for loading
uint32_t Q_load_idx_lane_base = bx * CTA_Q + CTA_Q / num_warps * warp_id + lane_id / global_to_shared_line_lanes_QK;
uint32_t K_load_idx_lane_base = CTA_K / num_warps * warp_id + lane_id / global_to_shared_line_lanes_QK;
const uint32_t num_iterations = SAGE_DIV_CEIL(
mask_mode == MaskMode::kCausal
? min(kv_len, (bx + 1) * CTA_Q)
: kv_len,
CTA_K);
// load Q with predicate
load_global_to_share<global_to_shared_line_lanes_QK, global_to_shared_copy_lines_per_warp_QK, QK_smem_iters_row, Q_smem_iters_col, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, CTA_Q>(
&Q_lane_base_ptr, Q_smem_offset_load, stride_seq_q, smem_Q, Q_load_idx_lane_base, qo_len);
cp_async::commit_group();
cp_async::wait_group<0>();
__syncthreads();
// for num_tiles_qk_inner = 1, we load all Qs in register
uint32_t RQ[num_tiles_q][4];
if constexpr (num_tiles_qk_inner == 1)
{
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
smem_Q.ldmatrix_m8n8x4(Q_smem_offset_mma, RQ[fq]);
Q_smem_offset_mma = smem_Q.advance_offset_by_row<16>(Q_smem_offset_mma);
}
}
// load K with predicate
load_global_to_share<global_to_shared_line_lanes_QK, global_to_shared_copy_lines_per_warp_QK, QK_smem_iters_row, K_smem_iters_col, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, CTA_K>(
&K_lane_base_ptr, K_smem_offset_load, stride_seq_k, smem_K, K_load_idx_lane_base, kv_len);
cp_async::commit_group();
float q_scale = Q_scale[q_scale_idx];
float original_sm_scale = sm_scale;
float dequant_scale = q_scale * K_scale[k_scale_idx + 0 * k_scale_advance_offset];
sm_scale = original_sm_scale * dequant_scale;
// load V
// ! we assume that V is padded. If not, there might be illegal memory access or nan issue.
// for fp16:
// load_global_to_share stride_seq_v
load_fp8_V_global_to_share<global_to_shared_line_lanes_V, global_to_shared_copy_lines_per_warp_V, V_smem_iters_row, V_smem_iters_col, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, CTA_K>(
&V_lane_base_ptr, V_smem_offset_load, stride_d_v, smem_V);
cp_async::commit_group();
K_load_idx_lane_base += CTA_K;
#pragma unroll
for (uint32_t iter = 1; iter < num_iterations - 1; iter++)
{
// ensure K is ready
cp_async::wait_group<1>();
__syncthreads();
// compute QK^T
if constexpr (num_tiles_qk_inner == 1)
{
compute_int_qk<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_qk_inner, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, DTypeQK>(
smem_K, RS, RQ, K_smem_offset_mma);
}
else
{
compute_int_qk<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_qk_inner, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, DTypeQK>(
smem_Q, smem_K, RS, Q_smem_offset_mma, K_smem_offset_mma);
}
float RS_f32[num_tiles_q][num_tiles_k][8];
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
#pragma unroll
for (uint32_t k = 0; k < 8; k++)
{
RS_f32[fq][fk][k] = __int2float_rz(RS[fq][fk][k]);
}
}
}
K_idx_lane_base += CTA_K;
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
update_mdo<num_tiles_q, num_tiles_k, num_tiles_v, false, true, false>(RS_f32, RO, m, d, sm_scale);
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
update_mdo<num_tiles_q, num_tiles_k, num_tiles_v, true, true, false>(RS_f32, RO, m, d, sm_scale);
}
if constexpr (DenominatorAccumUnit == ComputeUnit::kCudaCore)
{
accumulate_d<num_tiles_q, num_tiles_k, ComputeUnit::kCudaCore>(RS_f32, d);
}
uint32_t RS_f8[num_tiles_q][num_tiles_k / 2][4];
RS_32_to_8<num_tiles_q, num_tiles_k>(RS_f32, RS_f8);
if constexpr (DenominatorAccumUnit == ComputeUnit::kTensorCore)
{
accumulate_d_f8<num_tiles_q, num_tiles_k>(RS_f8, d);
}
__syncthreads();
// load K without predicate
load_global_to_share<global_to_shared_line_lanes_QK, global_to_shared_copy_lines_per_warp_QK, QK_smem_iters_row, K_smem_iters_col, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, CTA_K>(
&K_lane_base_ptr, K_smem_offset_load, stride_seq_k, smem_K);
cp_async::commit_group();
dequant_scale = q_scale * K_scale[k_scale_idx + iter * k_scale_advance_offset];
sm_scale = original_sm_scale * dequant_scale;
// ensure V is ready
cp_async::wait_group<1>();
__syncthreads();
// for fp16:
// compute_fp16_sv_permuted<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, 4>(
// smem_V, RS_f16, RO, d, V_smem_offset_mma);
if constexpr (!use_inst_buffer)
{
compute_fp8_sv<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V>(
smem_V, RS_f8, RO, d);
}
else
{
if constexpr (!use_pv_fp16_accu){
compute_fp8_sv_inst_buf<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V>(
smem_V, RS_f8, RO, d);
}
else{
compute_fp8_sv_inst_buf_fp16_accu<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V>(
smem_V, RS_f8, RO, d);
}
}
__syncthreads();
// load V
// for fp16:
// load_global_to_share stride_seq_v
load_fp8_V_global_to_share<global_to_shared_line_lanes_V, global_to_shared_copy_lines_per_warp_V, V_smem_iters_row, V_smem_iters_col, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, CTA_K>(
&V_lane_base_ptr, V_smem_offset_load, stride_d_v, smem_V);
cp_async::commit_group();
K_load_idx_lane_base += CTA_K;
}
// second last iter, apply causal mask
if (num_iterations > 1)
{
// ensure K is ready
cp_async::wait_group<1>();
__syncthreads();
// compute QK^T
if constexpr (num_tiles_qk_inner == 1)
{
compute_int_qk<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_qk_inner, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, DTypeQK>(
smem_K, RS, RQ, K_smem_offset_mma);
}
else
{
compute_int_qk<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_qk_inner, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, DTypeQK>(
smem_Q, smem_K, RS, Q_smem_offset_mma, K_smem_offset_mma);
}
float RS_f32[num_tiles_q][num_tiles_k][8];
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
#pragma unroll
for (uint32_t k = 0; k < 8; k++)
{
RS_f32[fq][fk][k] = __int2float_rz(RS[fq][fk][k]) * dequant_scale;
}
}
}
if constexpr (mask_mode == MaskMode::kCausal)
{
apply_causal_mask<num_tiles_q, num_tiles_k>(Q_idx_lane_base, K_idx_lane_base, RS_f32);
}
// apply_out_of_bound_mask<num_tiles_q, num_tiles_k>(K_idx_lane_base, RS_f32, kv_len);
K_idx_lane_base += CTA_K;
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
update_mdo<num_tiles_q, num_tiles_k, num_tiles_v, false, true, false>(RS_f32, RO, m, d, original_sm_scale);
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
update_mdo<num_tiles_q, num_tiles_k, num_tiles_v, true, true, false>(RS_f32, RO, m, d, original_sm_scale);
}
if constexpr (DenominatorAccumUnit == ComputeUnit::kCudaCore)
{
accumulate_d<num_tiles_q, num_tiles_k, ComputeUnit::kCudaCore>(RS_f32, d);
}
uint32_t RS_f8[num_tiles_q][num_tiles_k / 2][4];
RS_32_to_8<num_tiles_q, num_tiles_k>(RS_f32, RS_f8);
if constexpr (DenominatorAccumUnit == ComputeUnit::kTensorCore)
{
accumulate_d_f8<num_tiles_q, num_tiles_k>(RS_f8, d);
}
__syncthreads();
// load K with predicate
load_global_to_share<global_to_shared_line_lanes_QK, global_to_shared_copy_lines_per_warp_QK, QK_smem_iters_row, K_smem_iters_col, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, CTA_K>(
&K_lane_base_ptr, K_smem_offset_load, stride_seq_k, smem_K, K_load_idx_lane_base, kv_len);
cp_async::commit_group();
dequant_scale = q_scale * K_scale[k_scale_idx + (num_iterations - 1) * k_scale_advance_offset];
sm_scale = original_sm_scale * dequant_scale;
// ensure V is ready
cp_async::wait_group<1>();
__syncthreads();
// for fp16:
// compute_fp16_sv_permuted<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, 4>(
// smem_V, RS_f16, RO, d, V_smem_offset_mma);
if constexpr (!use_inst_buffer)
{
compute_fp8_sv<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V>(
smem_V, RS_f8, RO, d);
}
else
{
if constexpr (!use_pv_fp16_accu){
compute_fp8_sv_inst_buf<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V>(
smem_V, RS_f8, RO, d);
}
else{
compute_fp8_sv_inst_buf_fp16_accu<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V>(
smem_V, RS_f8, RO, d);
}
}
__syncthreads();
// load V
// for fp16:
// load_global_to_share stride_seq_v
load_fp8_V_global_to_share<global_to_shared_line_lanes_V, global_to_shared_copy_lines_per_warp_V, V_smem_iters_row, V_smem_iters_col, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, CTA_K>(
&V_lane_base_ptr, V_smem_offset_load, stride_d_v, smem_V);
cp_async::commit_group();
K_load_idx_lane_base += CTA_K;
}
// last iter, apply causal mask and out of bound mask
{
// ensure K is ready
cp_async::wait_group<1>();
__syncthreads();
// compute QK^T
if constexpr (num_tiles_qk_inner == 1)
{
compute_int_qk<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_qk_inner, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, DTypeQK>(
smem_K, RS, RQ, K_smem_offset_mma);
}
else
{
compute_int_qk<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_qk_inner, swizzle_mode_QK, QK_SMEM_STRIDE / PACK_SIZE_QK, DTypeQK>(
smem_Q, smem_K, RS, Q_smem_offset_mma, K_smem_offset_mma);
}
float RS_f32[num_tiles_q][num_tiles_k][8];
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
#pragma unroll
for (uint32_t k = 0; k < 8; k++)
{
RS_f32[fq][fk][k] = __int2float_rz(RS[fq][fk][k]) * dequant_scale;
}
}
}
if constexpr (mask_mode == MaskMode::kCausal)
{
apply_causal_mask<num_tiles_q, num_tiles_k>(Q_idx_lane_base, K_idx_lane_base, RS_f32);
}
apply_out_of_bound_mask<num_tiles_q, num_tiles_k>(K_idx_lane_base, RS_f32, kv_len);
K_idx_lane_base += CTA_K;
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
update_mdo<num_tiles_q, num_tiles_k, num_tiles_v, false, true, false>(RS_f32, RO, m, d, original_sm_scale);
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
update_mdo<num_tiles_q, num_tiles_k, num_tiles_v, true, true, false>(RS_f32, RO, m, d, original_sm_scale);
}
if constexpr (DenominatorAccumUnit == ComputeUnit::kCudaCore)
{
accumulate_d<num_tiles_q, num_tiles_k, ComputeUnit::kCudaCore>(RS_f32, d);
}
uint32_t RS_f8[num_tiles_q][num_tiles_k / 2][4];
RS_32_to_8<num_tiles_q, num_tiles_k>(RS_f32, RS_f8);
if constexpr (DenominatorAccumUnit == ComputeUnit::kTensorCore)
{
accumulate_d_f8<num_tiles_q, num_tiles_k>(RS_f8, d);
}
// ensure V is ready
cp_async::wait_group<0>();
__syncthreads();
// for fp16:
// compute_fp16_sv_permuted<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V, 4>(
// smem_V, RS_f16, RO, d, V_smem_offset_mma);
if constexpr (!use_inst_buffer)
{
compute_fp8_sv<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V>(
smem_V, RS_f8, RO, d);
}
else
{
if constexpr (!use_pv_fp16_accu){
compute_fp8_sv_inst_buf<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V>(
smem_V, RS_f8, RO, d);
}
else{
compute_fp8_sv_inst_buf_fp16_accu<num_warps_q, num_warps_k, num_tiles_q, num_tiles_k, num_tiles_v, swizzle_mode_V, V_SMEM_STRIDE / PACK_SIZE_V>(
smem_V, RS_f8, RO, d);
}
}
__syncthreads();
}
// TODO: thread block sync mdo state for num_warps_k > 0. Then only one thread block needs to do the final saving.
normalize_d<num_tiles_q, num_tiles_v, ComputeUnit::kCudaCore>(RO, m, d);
// ! here we just implement the case for fp32 acumulation
if constexpr (fuse_v_scale)
{
float v_scale[4];
float *V_scale_base_ptr = V_scale + batch_id * (num_qo_heads / num_kv_groups) * head_dim + (head_id / num_kv_groups) * head_dim + (lane_id % 4 ) * 2;
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
((float2*)v_scale)[0] = *((float2*)(V_scale_base_ptr + fv * 16));
((float2*)v_scale)[1] = *((float2*)(V_scale_base_ptr + fv * 16 + 8));
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
RO[fq][fv][0] *= v_scale[0];
RO[fq][fv][1] *= v_scale[1];
RO[fq][fv][2] *= v_scale[0];
RO[fq][fv][3] *= v_scale[1];
RO[fq][fv][4] *= v_scale[2];
RO[fq][fv][5] *= v_scale[3];
RO[fq][fv][6] *= v_scale[2];
RO[fq][fv][7] *= v_scale[3];
}
}
}
if constexpr (fuse_v_mean)
{
float v_mean[4];
float *V_mean_base_ptr = V_mean + batch_id * (num_qo_heads / num_kv_groups) * head_dim + (head_id / num_kv_groups) * head_dim + (lane_id % 4 ) * 2;
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
((float2*)v_mean)[0] = *((float2*)(V_mean_base_ptr + fv * 16));
((float2*)v_mean)[1] = *((float2*)(V_mean_base_ptr + fv * 16 + 8));
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
RO[fq][fv][0] += v_mean[0];
RO[fq][fv][1] += v_mean[1];
RO[fq][fv][2] += v_mean[0];
RO[fq][fv][3] += v_mean[1];
RO[fq][fv][4] += v_mean[2];
RO[fq][fv][5] += v_mean[3];
RO[fq][fv][6] += v_mean[2];
RO[fq][fv][7] += v_mean[3];
}
}
}
// save the result to shared memory
uint32_t smem_O_row_base = get_warp_idx_q<num_warps_q, num_warps_k>() * WARP_Q + lane_id / 4;
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
uint32_t offset_O = smem_O.get_permuted_offset(smem_O_row_base + fq * MMA_QK_M, fv * (MMA_SV_N / PACK_SIZE_O));
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
// convert RO to half
uint32_t RO_f16[4];
#pragma unroll
for (uint32_t k = 0; k < 4; k++)
{
if constexpr (std::is_same<DTypeOut, half>::value)
{
((half2*)RO_f16)[k] = __float22half2_rn(((float2*)RO[fq][fv])[k]);
}
else
{
((nv_bfloat162*)RO_f16)[k] = __float22bfloat162_rn(((float2*)RO[fq][fv])[k]);
}
}
((uint32_t*)(smem_O.base + offset_O))[lane_id % 4] = RO_f16[0];
((uint32_t*)(smem_O.base + offset_O + 8 * (O_SMEM_STRIDE / PACK_SIZE_O)))[lane_id % 4] = RO_f16[1];
offset_O = smem_O.get_permuted_offset(smem_O_row_base + fq * MMA_QK_M, fv * (MMA_SV_N / PACK_SIZE_O) + 1);
((uint32_t*)(smem_O.base + offset_O))[lane_id % 4] = RO_f16[2];
((uint32_t*)(smem_O.base + offset_O + 8 * (O_SMEM_STRIDE / PACK_SIZE_O)))[lane_id % 4] = RO_f16[3];
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
// TODO: not implement
}
}
}
// ! do we need to sync here?
__syncwarp();
// shared memory to global memory
DTypeOut *O_lane_ptr = O + batch_id * stride_bz_o + head_id * stride_h_o + (bx * CTA_Q + WARP_Q * get_warp_idx_q<num_warps_q, num_warps_k>() + lane_id / global_to_shared_line_lanes_O) * stride_seq_o + lane_id % global_to_shared_line_lanes_O * PACK_SIZE_O;
uint32_t offset_O = smem_O.get_permuted_offset(get_warp_idx_q<num_warps_q, num_warps_k>() * WARP_Q + lane_id / global_to_shared_line_lanes_O, lane_id % global_to_shared_line_lanes_O);
uint32_t O_load_idx_lane_base = bx * CTA_Q + CTA_Q / num_warps * warp_id + lane_id / global_to_shared_line_lanes_O;
#pragma unroll
for (uint32_t i = 0; i < O_smem_iters_col; i++)
{
#pragma unroll
for (uint32_t j = 0; j < O_smem_iters_row; j++)
{
if (O_load_idx_lane_base < qo_len)
{
smem_O.store_128b(offset_O, O_lane_ptr);
}
O_lane_ptr += (global_to_shared_line_lanes_O * PACK_SIZE_O);
offset_O = smem_O.advance_offset_by_column<global_to_shared_line_lanes_O>(offset_O);
}
offset_O = smem_O.advance_offset_by_row<global_to_shared_copy_lines_per_warp_O>(offset_O - (O_smem_iters_row * global_to_shared_line_lanes_O));
O_lane_ptr += ((global_to_shared_copy_lines_per_warp_O * stride_seq_o) - (O_smem_iters_row * global_to_shared_line_lanes_O * PACK_SIZE_O));
O_load_idx_lane_base += global_to_shared_copy_lines_per_warp_O;
}
if constexpr (return_lse)
{
// ! this only works for num_tiles_q = 2
uint32_t lse_idx = bx * CTA_Q + lane_id / 4 + 8 * (lane_id % 4) + WARP_Q * get_warp_idx_q<num_warps_q, num_warps_k>();
float *lse_lane_ptr = Lse + batch_id * (qo_len * num_qo_heads) + head_id * qo_len + lse_idx;
uint32_t fq = (lane_id % 4) / 2;
uint32_t k = (lane_id % 4) % 2;
if (lse_idx < qo_len)
{
lse_lane_ptr[0] = (math::ptx_log2(d[fq][k]) + m[fq][k]);
}
}
#else
RUNTIME_ASSERT("FP8 SageAttention requires SM89 or newer");
#endif
}
#undef PACK_SIZE_QK
#undef PACK_SIZE_V
#undef PACK_SIZE_O
#undef MMA_QK_M
#undef MMA_QK_N
#undef MMA_QK_K
#undef MMA_SV_M
#undef MMA_SV_N
#undef MMA_SV_K
} // namespace sageattention
#endif
+998
View File
@@ -0,0 +1,998 @@
/*
* Copyright (c) 2024 by SageAttention team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SD_CORE_CUDA_SAGEATTENTION_ATTN_UTILS_CUH
#define SD_CORE_CUDA_SAGEATTENTION_ATTN_UTILS_CUH
#include <cuda_fp16.h>
#include <cuda_pipeline_primitives.h>
#include "cp_async.cuh"
#include "mma.cuh"
#include "permuted_smem.cuh"
#include "numeric_conversion.cuh"
namespace sageattention {
#define WARP_SIZE 32
#define S_FP8_OFFSET 8.807f
#define S_FP8_OFFSET_EXP 6680.8477f
#define S_FP8_OFFSET_EXP_INV 0.0022326917f
#define SAGE_DIV_CEIL(M, N) (((M) + (N)-1) / (N))
enum class MaskMode {
kNone = 0,
kCausal = 1,
};
enum class DataType {
kHalf,
kInt8,
kInt4,
kE4M3,
kE5M2,
};
enum class QuantGranularity {
kPerTensor = 0,
kPerBlock = 1,
kPerWarp = 2,
kPerThread = 3,
};
enum class ComputeUnit {
kTensorCore,
kCudaCore,
};
__device__ __forceinline__ uint32_t get_warp_id()
{
return threadIdx.y;
}
__device__ __forceinline__ uint32_t get_lane_id()
{
return threadIdx.x;
}
template <uint32_t num_warps_q, uint32_t num_warps_k>
__device__ __forceinline__ uint32_t get_warp_idx_q()
{
return get_warp_id() / num_warps_k;
}
template <uint32_t num_warps_q, uint32_t num_warps_k>
__device__ __forceinline__ uint32_t get_warp_idx_k()
{
return get_warp_id() % num_warps_k;
}
template <uint32_t global_to_shared_line_lanes, uint32_t global_to_shared_copy_lines_per_warp_per_iter,
uint32_t smem_iters_row, uint32_t smem_iters_col, SwizzleMode swizzle_mode, uint32_t stride, uint32_t CTA, typename T>
__device__ __forceinline__ void load_global_to_share(T **lane_ptr, uint32_t &smem_offset,
const uint32_t &gmem_stride,
const smem_t<swizzle_mode, stride> &smem)
{
static_assert(global_to_shared_copy_lines_per_warp_per_iter * global_to_shared_line_lanes == WARP_SIZE);
static_assert(std::is_same<T, half>::value || std::is_same<T, int8_t>::value);
constexpr uint32_t pack_size = std::is_same<T, half>::value ? 8 : 16;
#pragma unroll
for (uint32_t i = 0; i < smem_iters_col; i++)
{
#pragma unroll
for (uint32_t j = 0; j < smem_iters_row; j++)
{
smem.load_128b_async(smem_offset, *lane_ptr);
*lane_ptr += (global_to_shared_line_lanes * pack_size);
smem_offset = smem.advance_offset_by_column<global_to_shared_line_lanes>(smem_offset);
}
smem_offset = smem.advance_offset_by_row<global_to_shared_copy_lines_per_warp_per_iter>(smem_offset - (smem_iters_row * global_to_shared_line_lanes));
*lane_ptr += ((global_to_shared_copy_lines_per_warp_per_iter * gmem_stride) - (smem_iters_row * global_to_shared_line_lanes * pack_size));
}
smem_offset -= (smem_iters_col * global_to_shared_copy_lines_per_warp_per_iter * stride);
*lane_ptr += (CTA - smem_iters_col * global_to_shared_copy_lines_per_warp_per_iter) * gmem_stride;
}
// with predicate
template <uint32_t global_to_shared_line_lanes, uint32_t global_to_shared_copy_lines_per_warp_per_iter,
uint32_t smem_iters_row, uint32_t smem_iters_col, SwizzleMode swizzle_mode, uint32_t stride, uint32_t CTA, typename T>
__device__ __forceinline__ void load_global_to_share(T **lane_ptr, uint32_t &smem_offset,
const uint32_t &gmem_stride,
const smem_t<swizzle_mode, stride> &smem, uint32_t base_idx, uint32_t max_len)
{
static_assert(global_to_shared_copy_lines_per_warp_per_iter * global_to_shared_line_lanes == WARP_SIZE);
static_assert(std::is_same<T, half>::value || std::is_same<T, int8_t>::value);
constexpr uint32_t pack_size = std::is_same<T, half>::value ? 8 : 16;
#pragma unroll
for (uint32_t i = 0; i < smem_iters_col; i++)
{
#pragma unroll
for (uint32_t j = 0; j < smem_iters_row; j++)
{
// Masked probabilities still propagate NaNs from stale V lanes through 0 * NaN.
smem.load_128b_async<std::is_same<T, half>::value ? cp_async::SharedMemFillMode::kFillZero : cp_async::SharedMemFillMode::kNoFill>(smem_offset, *lane_ptr, base_idx < max_len);
*lane_ptr += (global_to_shared_line_lanes * pack_size);
smem_offset = smem.advance_offset_by_column<global_to_shared_line_lanes>(smem_offset);
}
smem_offset = smem.advance_offset_by_row<global_to_shared_copy_lines_per_warp_per_iter>(smem_offset - (smem_iters_row * global_to_shared_line_lanes));
*lane_ptr += ((global_to_shared_copy_lines_per_warp_per_iter * gmem_stride) - (smem_iters_row * global_to_shared_line_lanes * pack_size));
base_idx += global_to_shared_copy_lines_per_warp_per_iter;
}
smem_offset -= (smem_iters_col * global_to_shared_copy_lines_per_warp_per_iter * stride);
*lane_ptr += (CTA - smem_iters_col * global_to_shared_copy_lines_per_warp_per_iter) * gmem_stride;
}
template <uint32_t global_to_shared_line_lanes, uint32_t global_to_shared_copy_lines_per_warp_per_iter,
uint32_t smem_iters_row, uint32_t smem_iters_col, SwizzleMode swizzle_mode, uint32_t stride, uint32_t CTA>
__device__ __forceinline__ void load_fp8_V_global_to_share(int8_t **lane_ptr, uint32_t &smem_offset,
const uint32_t &gmem_stride,
const smem_t<swizzle_mode, stride> &smem)
{
static_assert(global_to_shared_copy_lines_per_warp_per_iter * global_to_shared_line_lanes == WARP_SIZE);
constexpr uint32_t pack_size_fp8 = 16;
#pragma unroll
for (uint32_t i = 0; i < smem_iters_col; i++)
{
#pragma unroll
for (uint32_t j = 0; j < smem_iters_row; j++)
{
smem.load_128b_async(smem_offset, *lane_ptr);
*lane_ptr += (global_to_shared_line_lanes * pack_size_fp8);
smem_offset = smem.advance_offset_by_column<global_to_shared_line_lanes>(smem_offset);
}
smem_offset = smem.advance_offset_by_row<global_to_shared_copy_lines_per_warp_per_iter>(smem_offset - (smem_iters_row * global_to_shared_line_lanes));
*lane_ptr += ((global_to_shared_copy_lines_per_warp_per_iter * gmem_stride) - (smem_iters_row * global_to_shared_line_lanes * pack_size_fp8));
}
smem_offset -= (smem_iters_col * global_to_shared_copy_lines_per_warp_per_iter * stride);
// for QK: *lane_ptr += (CTA - smem_iters_col * global_to_shared_copy_lines_per_warp_per_iter) * gmem_stride;
*lane_ptr += CTA; // ! prevent underflow
*lane_ptr -= (smem_iters_col * global_to_shared_copy_lines_per_warp_per_iter) * gmem_stride;
}
template <uint32_t num_warps_q, uint32_t num_warps_k,
uint32_t num_tiles_q, uint32_t num_tiles_k, uint32_t num_tiles_qk_inner,
SwizzleMode swizzle_mode, uint32_t stride, DataType DTypeQK>
__device__ __forceinline__ void compute_int_qk(const smem_t<swizzle_mode, stride> &smem_Q, const smem_t<swizzle_mode, stride> &smem_K, int32_t RS[][num_tiles_k][8], uint32_t &offset_Q, uint32_t &offset_K)
{
static_assert(DTypeQK == DataType::kInt8 || DTypeQK == DataType::kInt4);
uint32_t RQ[num_tiles_q][4];
uint32_t RK[4];
// the first iteration, mma mode is kInit
#pragma unroll
for (uint32_t iter = 0; iter < 1; iter++)
{
// load RQ
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
smem_Q.ldmatrix_m8n8x4(offset_Q, RQ[fq]);
offset_Q = smem_Q.advance_offset_by_row<16>(offset_Q);
}
// ! using permutation invariance
offset_Q = smem_Q.advance_offset_by_column<2>(offset_Q - (num_tiles_q * 16 * stride), iter);
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
// load RK
smem_K.ldmatrix_m8n8x4(offset_K, RK);
offset_K = smem_K.advance_offset_by_row<16>(offset_K);
// mma
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
if constexpr (DTypeQK == DataType::kInt8)
{
mma::mma_sync_m16n16k32_row_col_s8s8s32<mma::MMAMode::kInit>(RS[fq][fk], RQ[fq], RK);
}
else if constexpr (DTypeQK == DataType::kInt4)
{
mma::mma_sync_m16n16k64_row_col_s4s4s32<mma::MMAMode::kInit>(RS[fq][fk], RQ[fq], RK);
}
}
}
offset_K = smem_K.advance_offset_by_column<2>(offset_K - (num_tiles_k * 16 * stride), iter);
}
// following iteration, mma mode is kInplace
#pragma unroll
for (uint32_t iter = 1; iter < num_tiles_qk_inner; iter++)
{
// load RQ
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
smem_Q.ldmatrix_m8n8x4(offset_Q, RQ[fq]);
offset_Q = smem_Q.advance_offset_by_row<16>(offset_Q);
}
offset_Q = smem_Q.advance_offset_by_column<2>(offset_Q - (num_tiles_q * 16 * stride), iter);
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
// load RK
smem_K.ldmatrix_m8n8x4(offset_K, RK);
offset_K = smem_K.advance_offset_by_row<16>(offset_K);
// mma
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
if constexpr (DTypeQK == DataType::kInt8)
{
mma::mma_sync_m16n16k32_row_col_s8s8s32<mma::MMAMode::kInplaceUpdate>(RS[fq][fk], RQ[fq], RK);
}
else if constexpr (DTypeQK == DataType::kInt4)
{
mma::mma_sync_m16n16k64_row_col_s4s4s32<mma::MMAMode::kInplaceUpdate>(RS[fq][fk], RQ[fq], RK);
}
}
}
offset_K = smem_K.advance_offset_by_column<2>(offset_K - (num_tiles_k * 16 * stride), iter);
}
offset_Q -= (2 * num_tiles_qk_inner);
offset_K -= (2 * num_tiles_qk_inner);
}
// for case when num_tiles_qk_inner = 1
template <uint32_t num_warps_q, uint32_t num_warps_k,
uint32_t num_tiles_q, uint32_t num_tiles_k, uint32_t num_tiles_qk_inner,
SwizzleMode swizzle_mode, uint32_t stride, DataType DTypeQK>
__device__ __forceinline__ void compute_int_qk(const smem_t<swizzle_mode, stride> &smem_K, int32_t RS[][num_tiles_k][8], uint32_t RQ[][4], uint32_t offset_K)
{
static_assert(DTypeQK == DataType::kInt8 || DTypeQK == DataType::kInt4);
static_assert(num_tiles_qk_inner == 1);
uint32_t RK[4];
// mma mode is kInit
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
// load RK
smem_K.ldmatrix_m8n8x4(offset_K, RK);
offset_K = smem_K.advance_offset_by_row<16>(offset_K);
// mma
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
if constexpr (DTypeQK == DataType::kInt8)
{
mma::mma_sync_m16n16k32_row_col_s8s8s32<mma::MMAMode::kInit>(RS[fq][fk], RQ[fq], RK);
}
else if constexpr (DTypeQK == DataType::kInt4)
{
mma::mma_sync_m16n16k64_row_col_s4s4s32<mma::MMAMode::kInit>(RS[fq][fk], RQ[fq], RK);
}
}
}
}
template <uint32_t num_tiles_q, uint32_t num_tiles_k, typename DTypeQKAccum>
__device__ __forceinline__ void apply_causal_mask(const uint32_t &Q_idx_lane_base, const uint32_t &K_idx_lane_base, DTypeQKAccum RS[][num_tiles_k][8])
{
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
#pragma unroll
for (uint32_t k = 0; k < 8; k++)
{
const uint32_t q_idx = Q_idx_lane_base + fq * 16 + 8 * ((k % 4) / 2);
const uint32_t kv_idx = K_idx_lane_base + fk * 16 + 8 * (k / 4) + k % 2;
const bool out_of_boundary = (kv_idx > q_idx);
if constexpr (std::is_same<DTypeQKAccum, float>::value)
{
RS[fq][fk][k] = (out_of_boundary ? -5000000.0f : RS[fq][fk][k]);
}
else if constexpr (std::is_same<DTypeQKAccum, half>::value)
{
RS[fq][fk][k] = (out_of_boundary ? __float2half_rn(-50000.0f) : RS[fq][fk][k]);
}
}
}
}
}
template <uint32_t num_tiles_q, uint32_t num_tiles_k, typename DTypeQKAccum>
__device__ __forceinline__ void apply_out_of_bound_mask(const uint32_t &K_idx_lane_base, DTypeQKAccum RS[][num_tiles_k][8], const uint32_t &kv_len)
{
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
#pragma unroll
for (uint32_t k = 0; k < 8; k++)
{
const uint32_t kv_idx = K_idx_lane_base + fk * 16 + 8 * (k / 4) + k % 2;
const bool out_of_boundary = (kv_idx >= kv_len);
if constexpr (std::is_same<DTypeQKAccum, float>::value)
{
RS[fq][fk][k] = (out_of_boundary ? -5000000.0f : RS[fq][fk][k]);
}
else if constexpr (std::is_same<DTypeQKAccum, half>::value)
{
RS[fq][fk][k] = (out_of_boundary ? __float2half_rn(-50000.0f) : RS[fq][fk][k]);
}
}
}
}
}
// for DTypeQKAccum float
template <uint32_t num_tiles_q, uint32_t num_tiles_k, uint32_t num_tiles_v, bool use_half_o_scale, bool exp_offset, bool fuse_scale=false, typename DTypeSVAccum>
__device__ __forceinline__ void update_mdo(float RS[][num_tiles_k][8], DTypeSVAccum RO[][num_tiles_v][8], float m[][2], float d[][2], const float &sm_scale)
{
static_assert(std::is_same<DTypeSVAccum, half>::value || (!use_half_o_scale));
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t k = 0; k < 2; k++)
{
// assign the smallest value possible
float m_prev = m[fq][k];
float m_temp = -5000000.0f;
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
float m_local = max(max(RS[fq][fk][k * 2 + 0], RS[fq][fk][k * 2 + 1]),
max(RS[fq][fk][k * 2 + 4], RS[fq][fk][k * 2 + 5]));
m_temp = max(m_temp, m_local);
}
if constexpr (!fuse_scale)
{
if constexpr (exp_offset)
{
m_temp = fmaf(m_temp, sm_scale, -S_FP8_OFFSET);
}
else
{
m_temp *= sm_scale;
}
}
else if constexpr (exp_offset)
{
m_temp += (-S_FP8_OFFSET);
}
// exchange element with the 4 threads in the row
m_temp = max(m_temp, __shfl_xor_sync(0xffffffff, m_temp, 0x1)); // 0 exchange with 1, 2 exchange with 3
m_temp = max(m_temp, __shfl_xor_sync(0xffffffff, m_temp, 0x2)); // 0 exchange with 2, 1 exchange with 3
m[fq][k] = max(m[fq][k], m_temp);
float o_scale = math::ptx_exp2(m_prev - m[fq][k]);
// update denominator
d[fq][k] *= o_scale;
half2 o_scale2;
if constexpr (use_half_o_scale)
{
o_scale2 = __floats2half2_rn(o_scale, o_scale);
}
// update RO
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
RO[fq][fv][k * 2 + 0] *= o_scale;
RO[fq][fv][k * 2 + 1] *= o_scale;
RO[fq][fv][k * 2 + 4] *= o_scale;
RO[fq][fv][k * 2 + 5] *= o_scale;
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
if constexpr (use_half_o_scale)
{
((half2*)RO[fq][fv])[k] = __hmul2(((half2*)RO[fq][fv])[k], o_scale2);
((half2*)RO[fq][fv])[k + 2] = __hmul2(((half2*)RO[fq][fv])[k + 2], o_scale2);
}
else
{
RO[fq][fv][k * 2 + 0] = __float2half_rn(__half2float(RO[fq][fv][k * 2 + 0]) * o_scale);
RO[fq][fv][k * 2 + 1] = __float2half_rn(__half2float(RO[fq][fv][k * 2 + 1]) * o_scale);
RO[fq][fv][k * 2 + 4] = __float2half_rn(__half2float(RO[fq][fv][k * 2 + 4]) * o_scale);
RO[fq][fv][k * 2 + 5] = __float2half_rn(__half2float(RO[fq][fv][k * 2 + 5]) * o_scale);
}
}
}
// raise RS to exponent
float negative_m = -m[fq][k];
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
if constexpr (fuse_scale)
{
RS[fq][fk][k * 2 + 0] = math::ptx_exp2(RS[fq][fk][k * 2 + 0] + negative_m);
RS[fq][fk][k * 2 + 1] = math::ptx_exp2(RS[fq][fk][k * 2 + 1] + negative_m);
RS[fq][fk][k * 2 + 4] = math::ptx_exp2(RS[fq][fk][k * 2 + 4] + negative_m);
RS[fq][fk][k * 2 + 5] = math::ptx_exp2(RS[fq][fk][k * 2 + 5] + negative_m);
}
else
{
RS[fq][fk][k * 2 + 0] = math::ptx_exp2(fmaf(RS[fq][fk][k * 2 + 0], sm_scale, negative_m));
RS[fq][fk][k * 2 + 1] = math::ptx_exp2(fmaf(RS[fq][fk][k * 2 + 1], sm_scale, negative_m));
RS[fq][fk][k * 2 + 4] = math::ptx_exp2(fmaf(RS[fq][fk][k * 2 + 4], sm_scale, negative_m));
RS[fq][fk][k * 2 + 5] = math::ptx_exp2(fmaf(RS[fq][fk][k * 2 + 5], sm_scale, negative_m));
}
}
}
}
}
template <uint32_t num_tiles_q, uint32_t num_tiles_k, typename T>
__device__ __forceinline__ void RS_32_to_16(T RS[][num_tiles_k][8], uint32_t RS_16[][num_tiles_k][4])
{
static_assert(sizeof(T) == 4);
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
((half2*)RS_16[fq][fk])[0] = __float22half2_rn(((float2*)RS[fq][fk])[0]);
((half2*)RS_16[fq][fk])[1] = __float22half2_rn(((float2*)RS[fq][fk])[1]);
((half2*)RS_16[fq][fk])[2] = __float22half2_rn(((float2*)RS[fq][fk])[2]);
((half2*)RS_16[fq][fk])[3] = __float22half2_rn(((float2*)RS[fq][fk])[3]);
}
}
}
template <uint32_t num_tiles_q, uint32_t num_tiles_k>
__device__ __forceinline__ void RS_32_to_8(float RS[][num_tiles_k][8], uint32_t RS_8[][num_tiles_k / 2][4])
{
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k / 2; fk++)
{
floatx4_to_e4m3x4(RS_8[fq][fk], RS[fq][fk * 2 + 0], RS[fq][fk * 2 + 0] + 4);
floatx4_to_e4m3x4(RS_8[fq][fk] + 1, RS[fq][fk * 2 + 0] + 2, RS[fq][fk * 2 + 0] + 6);
floatx4_to_e4m3x4(RS_8[fq][fk] + 2, RS[fq][fk * 2 + 1], RS[fq][fk * 2 + 1] + 4);
floatx4_to_e4m3x4(RS_8[fq][fk] + 3, RS[fq][fk * 2 + 1] + 2, RS[fq][fk * 2 + 1] + 6);
}
}
}
template <uint32_t num_tiles_q, uint32_t num_tiles_k>
__device__ __forceinline__ void RS_16_to_8(uint32_t RS[][num_tiles_k][4], uint32_t RS_8[][num_tiles_k / 2][4])
{
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k / 2; fk++)
{
halfx4_to_e4m3x4(RS_8[fq][fk], RS[fq][fk * 2 + 0], RS[fq][fk * 2 + 0] + 2);
halfx4_to_e4m3x4(RS_8[fq][fk] + 1, RS[fq][fk * 2 + 0] + 1, RS[fq][fk * 2 + 0] + 3);
halfx4_to_e4m3x4(RS_8[fq][fk] + 2, RS[fq][fk * 2 + 1], RS[fq][fk * 2 + 1] + 2);
halfx4_to_e4m3x4(RS_8[fq][fk] + 3, RS[fq][fk * 2 + 1] + 1, RS[fq][fk * 2 + 1] + 3);
}
}
}
template <uint32_t num_tiles_q, uint32_t num_tiles_k>
__device__ __forceinline__ void RS_8_to_16(uint32_t RS_8[][num_tiles_k / 2][4], uint32_t RS[][num_tiles_k][4])
{
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k / 2; fk++)
{
e4m3x4_to_halfx4(RS[fq][fk * 2 + 0], RS[fq][fk * 2 + 0] + 2, RS_8[fq][fk]);
e4m3x4_to_halfx4(RS[fq][fk * 2 + 0] + 1, RS[fq][fk * 2 + 0] + 3, RS_8[fq][fk] + 1);
e4m3x4_to_halfx4(RS[fq][fk * 2 + 1], RS[fq][fk * 2 + 1] + 2, RS_8[fq][fk] + 2);
e4m3x4_to_halfx4(RS[fq][fk * 2 + 1] + 1, RS[fq][fk * 2 + 1] + 3, RS_8[fq][fk] + 3);
}
}
}
template <uint32_t num_tiles_q, uint32_t num_tiles_k, ComputeUnit compute_unit = ComputeUnit::kTensorCore, typename T>
__device__ __forceinline__ void accumulate_d(T RS[][num_tiles_k][(compute_unit == ComputeUnit::kTensorCore)? 4 : 8], float d[][2])
{
// for compute unit cuda core, RS is float
// for compute unit tensor core, RS is packed half
static_assert((std::is_same<T, float>::value && compute_unit == ComputeUnit::kCudaCore) ||
(std::is_same<T, uint32_t>::value && compute_unit == ComputeUnit::kTensorCore));
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
if constexpr (compute_unit == ComputeUnit::kTensorCore)
{
// full accumulate with tensor core
mma::rowsum_f16f16f32(d[fq], (uint32_t*)(RS[fq][fk]));
}
else if constexpr (compute_unit == ComputeUnit::kCudaCore)
{
// partial accumulate with cuda core
d[fq][0] += RS[fq][fk][0] + RS[fq][fk][1] + RS[fq][fk][4] + RS[fq][fk][5];
d[fq][1] += RS[fq][fk][2] + RS[fq][fk][3] + RS[fq][fk][6] + RS[fq][fk][7];
}
}
}
}
template <uint32_t num_tiles_q, uint32_t num_tiles_k>
__device__ __forceinline__ void accumulate_d_f8(uint32_t RS[][num_tiles_k / 2][4], float d[][2])
{
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k / 2; fk++)
{
mma::rowsum_f8f8f32(d[fq], RS[fq][fk]);
}
}
}
template <uint32_t num_warps_q, uint32_t num_warps_k,
uint32_t num_tiles_q, uint32_t num_tiles_k, uint32_t num_tiles_v,
SwizzleMode swizzle_mode, uint32_t stride, typename DTypeSVAccum>
__device__ __forceinline__ void compute_fp16_sv(const smem_t<swizzle_mode, stride> &smem_V, uint32_t RS_f16[][num_tiles_k][4], DTypeSVAccum RO[][num_tiles_v][8], float d[][2])
{
uint32_t smem_V_row_base = get_warp_idx_k<num_warps_q, num_warps_k>() * (num_tiles_k * 16) + get_lane_id() % 16;
uint32_t smem_V_col_base = get_lane_id() / 16;
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
// load RV
uint32_t RV[4];
uint32_t offset_V = (smem_V).get_permuted_offset(smem_V_row_base + fk * 16, smem_V_col_base + fv * 2);
smem_V.ldmatrix_m8n8x4_trans(offset_V, RV);
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
mma::mma_sync_m16n16k16_row_col_f16f16f32(RO[fq][fv], RS_f16[fq][fk], RV);
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
mma::mma_sync_m16n16k16_row_col_f16f16f16((uint32_t*)RO[fq][fv], RS_f16[fq][fk], RV);
}
}
}
}
}
template <uint32_t num_warps_q, uint32_t num_warps_k,
uint32_t num_tiles_q, uint32_t num_tiles_k, uint32_t num_tiles_v,
SwizzleMode swizzle_mode, uint32_t stride, uint32_t RS_width=4, typename T, typename DTypeSVAccum>
__device__ __forceinline__ void compute_fp16_sv_permuted(const smem_t<swizzle_mode, stride> &smem_V, T RS_f16[][num_tiles_k][RS_width], DTypeSVAccum RO[][num_tiles_v][8], float d[][2], uint32_t &offset_V)
{
static_assert(sizeof(T) == 4);
// ! be sure you know what you are doing
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k; fk++)
{
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
// load RV
uint32_t RV[4];
smem_V.ldmatrix_m8n8x4_trans(offset_V, RV);
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
mma::mma_sync_m16n16k16_row_col_f16f16f32(RO[fq][fv], (uint32_t*)(RS_f16[fq][fk]), RV);
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
mma::mma_sync_m16n16k16_row_col_f16f16f16((uint32_t*)RO[fq][fv], (uint32_t*)(RS_f16[fq][fk]), RV);
}
}
offset_V = smem_V.advance_offset_by_column<2>(offset_V, fv);
}
offset_V = smem_V.advance_offset_by_row<16>(offset_V - (2 * num_tiles_v));
}
// make offset_V their original value
offset_V -= (16 * num_tiles_k * stride);
}
template <uint32_t num_warps_q, uint32_t num_warps_k,
uint32_t num_tiles_q, uint32_t num_tiles_k, uint32_t num_tiles_v,
SwizzleMode swizzle_mode, uint32_t stride, uint32_t RS_width=4, typename T, typename DTypeSVAccum>
__device__ __forceinline__ void compute_fp16_sv_permuted_inst_buf(const smem_t<swizzle_mode, stride> &smem_V, T RS_f16[][num_tiles_k][RS_width], DTypeSVAccum RO[][num_tiles_v][8], float d[][2], uint32_t &offset_V)
{
static_assert(sizeof(T) == 4);
static_assert(std::is_same<DTypeSVAccum, float>::value);
uint32_t RO_inst_buf[num_tiles_q][num_tiles_v][4];
// ! be sure you know what you are doing
#pragma unroll
for (uint32_t fk = 0; fk < 1; fk++)
{
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
// load RV
uint32_t RV[4];
smem_V.ldmatrix_m8n8x4_trans(offset_V, RV);
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
{
mma::mma_sync_m16n16k16_row_col_f16f16f16<mma::MMAMode::kInit>((uint32_t*)RO_inst_buf[fq][fv], (uint32_t*)(RS_f16[fq][fk]), RV);
}
}
offset_V = smem_V.advance_offset_by_column<2>(offset_V, fv);
}
offset_V = smem_V.advance_offset_by_row<16>(offset_V - (2 * num_tiles_v));
}
#pragma unroll
for (uint32_t fk = 1; fk < num_tiles_k; fk++)
{
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
// load RV
uint32_t RV[4];
smem_V.ldmatrix_m8n8x4_trans(offset_V, RV);
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
{
mma::mma_sync_m16n16k16_row_col_f16f16f16<mma::MMAMode::kInplaceUpdate>((uint32_t*)RO_inst_buf[fq][fv], (uint32_t*)(RS_f16[fq][fk]), RV);
}
}
offset_V = smem_V.advance_offset_by_column<2>(offset_V, fv);
}
offset_V = smem_V.advance_offset_by_row<16>(offset_V - (2 * num_tiles_v));
}
// accumulate into RO
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
RO[fq][fv][0] += __half2float(((half2*)RO_inst_buf[fq][fv])[0].x);
RO[fq][fv][1] += __half2float(((half2*)RO_inst_buf[fq][fv])[0].y);
RO[fq][fv][2] += __half2float(((half2*)RO_inst_buf[fq][fv])[1].x);
RO[fq][fv][3] += __half2float(((half2*)RO_inst_buf[fq][fv])[1].y);
RO[fq][fv][4] += __half2float(((half2*)RO_inst_buf[fq][fv])[2].x);
RO[fq][fv][5] += __half2float(((half2*)RO_inst_buf[fq][fv])[2].y);
RO[fq][fv][6] += __half2float(((half2*)RO_inst_buf[fq][fv])[3].x);
RO[fq][fv][7] += __half2float(((half2*)RO_inst_buf[fq][fv])[3].y);
}
}
// make offset_V their original value
offset_V -= (16 * num_tiles_k * stride);
}
template<uint32_t num_tiles_q, uint32_t num_tiles_v,
ComputeUnit compute_unit = ComputeUnit::kTensorCore, // compute unit for accumulate_d
typename DTypeQKAccum, typename DTypeSVAccum>
__device__ __forceinline__ void normalize_d(DTypeSVAccum RO[][num_tiles_v][8], DTypeQKAccum m[][2], float d[][2])
{
if constexpr (compute_unit == ComputeUnit::kCudaCore)
{
// accumulate_d performs partial accumulation with cuda core
// aggregate d
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t k = 0; k < 2; k++)
{
d[fq][k] += __shfl_xor_sync(0xffffffff, d[fq][k], 0x1); // sum 0 and 1, 2 and 3
d[fq][k] += __shfl_xor_sync(0xffffffff, d[fq][k], 0x2); // sum 0 and 2, 1 and 3
}
}
}
// divide O by d
float d_rcp[num_tiles_q][2];
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t k = 0; k < 2; k++)
{
// TODO: check m to prevent nan
d_rcp[fq][k] = math::ptx_rcp(d[fq][k]);
}
}
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
#pragma unroll
for (uint32_t k = 0; k < 8; k++)
{
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
RO[fq][fv][k] *= d_rcp[fq][(k % 4) / 2];
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
RO[fq][fv][k] = __float2half_rn(__half2float(RO[fq][fv][k]) * d_rcp[fq][(k % 4) / 2]);
}
}
}
}
}
template <uint32_t num_warps_q, uint32_t num_warps_k,
uint32_t num_tiles_q, uint32_t num_tiles_k, uint32_t num_tiles_v,
SwizzleMode swizzle_mode, uint32_t stride, typename DTypeSVAccum>
__device__ __forceinline__ void compute_fp8_sv(const smem_t<swizzle_mode, stride> &smem_V, uint32_t RS_f8[][num_tiles_k / 2][4], DTypeSVAccum RO[][num_tiles_v][8], float d[][2])
{
uint32_t smem_V_row_base = get_lane_id() % 8 + (get_lane_id() / 16) * 8;
// uint32_t smem_V_col_base = get_warp_idx_k<num_warps_q, num_warps_k>() * ((16 * num_tiles_k) / 16) + (get_lane_id() / 8) % 2;
uint32_t smem_V_col_base = (get_lane_id() / 8) % 2;
#pragma unroll
for (uint32_t fk = 0; fk < num_tiles_k / 2; fk++)
{
uint32_t offset_V = smem_V.get_permuted_offset(smem_V_row_base, smem_V_col_base + fk * 2);
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
// load RV
uint32_t RV[4];
// uint32_t offset_V = (smem_V).get_permuted_offset(smem_V_row_base + fv * 16, smem_V_col_base + fk * 2);
smem_V.ldmatrix_m8n8x4(offset_V, RV);
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
mma::mma_sync_m16n16k32_row_col_f8f8f32(RO[fq][fv], RS_f8[fq][fk], RV);
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
// ! Not Implemented
}
}
offset_V = smem_V.advance_offset_by_row<16>(offset_V);
}
}
}
template <uint32_t num_warps_q, uint32_t num_warps_k,
uint32_t num_tiles_q, uint32_t num_tiles_k, uint32_t num_tiles_v,
SwizzleMode swizzle_mode, uint32_t stride, typename DTypeSVAccum>
__device__ __forceinline__ void compute_fp8_sv_inst_buf(const smem_t<swizzle_mode, stride> &smem_V, uint32_t RS_f8[][num_tiles_k / 2][4], DTypeSVAccum RO[][num_tiles_v][8], float d[][2])
{
uint32_t smem_V_row_base = get_lane_id() % 8 + (get_lane_id() / 16) * 8;
// uint32_t smem_V_col_base = get_warp_idx_k<num_warps_q, num_warps_k>() * ((16 * num_tiles_k) / 16) + (get_lane_id() / 8) % 2;
uint32_t smem_V_col_base = (get_lane_id() / 8) % 2;
float RO_inst_buf[num_tiles_q][num_tiles_v][8];
#pragma unroll
for (uint32_t fk = 0; fk < 1; fk++)
{
uint32_t offset_V = smem_V.get_permuted_offset(smem_V_row_base, smem_V_col_base + fk * 2);
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
// load RV
uint32_t RV[4];
// uint32_t offset_V = (smem_V).get_permuted_offset(smem_V_row_base + fv * 16, smem_V_col_base + fk * 2);
smem_V.ldmatrix_m8n8x4(offset_V, RV);
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
mma::mma_sync_m16n16k32_row_col_f8f8f32<mma::MMAMode::kInit>(RO_inst_buf[fq][fv], RS_f8[fq][fk], RV);
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
// ! Not Implemented
}
}
offset_V = smem_V.advance_offset_by_row<16>(offset_V);
}
}
#pragma unroll
for (uint32_t fk = 1; fk < num_tiles_k / 2; fk++)
{
uint32_t offset_V = smem_V.get_permuted_offset(smem_V_row_base, smem_V_col_base + fk * 2);
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
// load RV
uint32_t RV[4];
// uint32_t offset_V = (smem_V).get_permuted_offset(smem_V_row_base + fv * 16, smem_V_col_base + fk * 2);
smem_V.ldmatrix_m8n8x4(offset_V, RV);
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
mma::mma_sync_m16n16k32_row_col_f8f8f32<mma::MMAMode::kInplaceUpdate>(RO_inst_buf[fq][fv], RS_f8[fq][fk], RV);
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
// ! Not Implemented
}
}
offset_V = smem_V.advance_offset_by_row<16>(offset_V);
}
}
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
RO[fq][fv][0] += RO_inst_buf[fq][fv][0];
RO[fq][fv][1] += RO_inst_buf[fq][fv][1];
RO[fq][fv][2] += RO_inst_buf[fq][fv][2];
RO[fq][fv][3] += RO_inst_buf[fq][fv][3];
RO[fq][fv][4] += RO_inst_buf[fq][fv][4];
RO[fq][fv][5] += RO_inst_buf[fq][fv][5];
RO[fq][fv][6] += RO_inst_buf[fq][fv][6];
RO[fq][fv][7] += RO_inst_buf[fq][fv][7];
}
}
}
template <uint32_t num_warps_q, uint32_t num_warps_k,
uint32_t num_tiles_q, uint32_t num_tiles_k, uint32_t num_tiles_v,
SwizzleMode swizzle_mode, uint32_t stride, typename DTypeSVAccum>
__device__ __forceinline__ void compute_fp8_sv_inst_buf_fp16_accu(const smem_t<swizzle_mode, stride> &smem_V, uint32_t RS_f8[][num_tiles_k / 2][4], DTypeSVAccum RO[][num_tiles_v][8], float d[][2])
{
uint32_t smem_V_row_base = get_lane_id() % 8 + (get_lane_id() / 16) * 8;
// uint32_t smem_V_col_base = get_warp_idx_k<num_warps_q, num_warps_k>() * ((16 * num_tiles_k) / 16) + (get_lane_id() / 8) % 2;
uint32_t smem_V_col_base = (get_lane_id() / 8) % 2;
uint32_t RO_int32[num_tiles_q][num_tiles_v][4];
#pragma unroll
for (uint32_t fk = 0; fk < 1; fk++)
{
uint32_t offset_V = smem_V.get_permuted_offset(smem_V_row_base, smem_V_col_base + fk * 2);
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
// load RV
uint32_t RV[4];
// uint32_t offset_V = (smem_V).get_permuted_offset(smem_V_row_base + fv * 16, smem_V_col_base + fk * 2);
smem_V.ldmatrix_m8n8x4(offset_V, RV);
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
//mma::mma_sync_m16n16k32_row_col_f8f8f32<mma::MMAMode::kInit>(RO_inst_buf[fq][fv], RS_f8[fq][fk], RV);
mma::mma_sync_m16n16k32_row_col_f8f8f16<mma::MMAMode::kInit>(RO_int32[fq][fv], RS_f8[fq][fk], RV);
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
// ! Not Implemented
}
}
offset_V = smem_V.advance_offset_by_row<16>(offset_V);
}
}
#pragma unroll
for (uint32_t fk = 1; fk < num_tiles_k / 2; fk++)
{
uint32_t offset_V = smem_V.get_permuted_offset(smem_V_row_base, smem_V_col_base + fk * 2);
#pragma unroll
for (uint32_t fv = 0; fv < num_tiles_v; fv++)
{
// load RV
uint32_t RV[4];
// uint32_t offset_V = (smem_V).get_permuted_offset(smem_V_row_base + fv * 16, smem_V_col_base + fk * 2);
smem_V.ldmatrix_m8n8x4(offset_V, RV);
#pragma unroll
for (uint32_t fq = 0; fq < num_tiles_q; fq++)
{
if constexpr (std::is_same<DTypeSVAccum, float>::value)
{
//mma::mma_sync_m16n16k32_row_col_f8f8f32<mma::MMAMode::kInplaceUpdate>(RO_inst_buf[fq][fv], RS_f8[fq][fk], RV);
mma::mma_sync_m16n16k32_row_col_f8f8f16<mma::MMAMode::kInplaceUpdate>(RO_int32[fq][fv], RS_f8[fq][fk], RV);
}
else if constexpr (std::is_same<DTypeSVAccum, half>::value)
{
// ! Not Implemented
}
}
offset_V = smem_V.advance_offset_by_row<16>(offset_V);
}
}
float RO_tmp_float[2];
#pragma unroll
for(int i = 0; i < num_tiles_q; i++){
#pragma unroll
for(int j = 0; j < num_tiles_v; j++){
#pragma unroll
for(int k = 0; k < 4; k++){
unpack_half2_from_uint32_to_float(RO_tmp_float, RO_int32[i][j][k]);
RO[i][j][k * 2 + 0] += RO_tmp_float[0];
RO[i][j][k * 2 + 1] += RO_tmp_float[1];
}
}
}
// #pragma unroll
// for (uint32_t fq = 0; fq < num_tiles_q; fq++)
// {
// #pragma unroll
// for (uint32_t fv = 0; fv < num_tiles_v; fv++)
// {
// RO[fq][fv][0] += RO_inst_buf[fq][fv][0];
// RO[fq][fv][1] += RO_inst_buf[fq][fv][1];
// RO[fq][fv][2] += RO_inst_buf[fq][fv][2];
// RO[fq][fv][3] += RO_inst_buf[fq][fv][3];
// RO[fq][fv][4] += RO_inst_buf[fq][fv][4];
// RO[fq][fv][5] += RO_inst_buf[fq][fv][5];
// RO[fq][fv][6] += RO_inst_buf[fq][fv][6];
// RO[fq][fv][7] += RO_inst_buf[fq][fv][7];
// }
// }
}
} // namespace sageattention
#endif
+148
View File
@@ -0,0 +1,148 @@
/*
* Copyright (c) 2024 by SageAttention team.
*
* This file is based on code from Flashinfer, https://github.com/flashinfer-ai/flashinfer/blob/v0.1.5/include/flashinfer/cp_async.cuh
* Copyright (c) 2023 by FlashInfer team.
* Small modifications made by SageAttention team, 2024 (e.g., renamed namespace).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SD_CORE_CUDA_SAGEATTENTION_CP_ASYNC_CUH
#define SD_CORE_CUDA_SAGEATTENTION_CP_ASYNC_CUH
#include <cuda_runtime.h>
namespace sageattention {
namespace cp_async {
enum class SharedMemFillMode {
kFillZero, // Fill zero to shared memory when predicate is false
kNoFill // Do not fill zero to shared memory when predicate is false
};
enum class PrefetchMode {
kNoPrefetch, // Do not fetch additional data from global memory to L2
kPrefetch // Fetch additional data from global memory to L2
};
#if (__CUDACC_VER_MAJOR__ >= 11)
#if (!defined(__CUDA_ARCH__) || (__CUDA_ARCH__ >= 800))
#define CP_ASYNC_ENABLED
#endif
#endif
/*!
* \brief Wrapper of PTX cp.async.commit_group instruction, commit all prior uncommitted
* cp.async instructions to a group
*/
__device__ __forceinline__ void commit_group() {
#ifdef CP_ASYNC_ENABLED
asm volatile("cp.async.commit_group;\n" ::);
#endif
}
/*!
* \brief Wrapper of PTX cp.async.wait_group instruction
* \tparam n Wait till most recent n groups are committed
*/
template <size_t n>
__device__ __forceinline__ void wait_group() {
#ifdef CP_ASYNC_ENABLED
asm volatile("cp.async.wait_group %0;\n" ::"n"(n));
#endif
}
/*!
* \brief Wrapper of PTX cp.async.cg.shared.global instruction, asynchronously copy data from
* global memory to shared memory
* \tparam prefetch_mode Whether to fetch additional data from global memory to L2
* \tparam T Data type
* \param smem_ptr Pointer to shared memory
* \param gmem_ptr Pointer to global memory
*/
template <PrefetchMode prefetch_mode, typename T>
__device__ __forceinline__ void load_128b(T* smem_ptr, const T* gmem_ptr) {
#ifdef CP_ASYNC_ENABLED
uint32_t smem_int_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(smem_ptr));
if constexpr (prefetch_mode == PrefetchMode::kPrefetch) {
asm volatile("cp.async.cg.shared.global.L2::128B [%0], [%1], %2, %3;\n" ::"r"(smem_int_ptr),
"l"(gmem_ptr), "n"(16), "r"(16));
} else {
asm volatile("cp.async.cg.shared.global [%0], [%1], %2, %3;\n" ::"r"(smem_int_ptr),
"l"(gmem_ptr), "n"(16), "r"(16));
}
#else
*((uint4*)smem_ptr) = *((uint4*)gmem_ptr);
#endif
}
/*!
* \brief Wrapper of PTX cp.async.cg.shared.global instruction, asynchronously copy data from
* global memory to shared memory with predicate.
* \tparam prefetch_mode Whether to fetch additional data from global memory to L2
* \tparam fill_mode Whether to fill zero to shared memory when predicate is false
* \tparam T Data type
* \param smem_ptr Pointer to shared memory
* \param gmem_ptr Pointer to global memory
* \param predicate Predicate value
* \note fill zero is slower than not fill zero
*/
template <PrefetchMode prefetch_mode, SharedMemFillMode fill_mode, typename T>
__device__ __forceinline__ void pred_load_128b(T* smem_ptr, const T* gmem_ptr, bool predicate) {
#ifdef CP_ASYNC_ENABLED
uint32_t smem_int_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(smem_ptr));
if constexpr (fill_mode == SharedMemFillMode::kFillZero) {
int src_in_bytes = predicate ? 16 : 0;
if constexpr (prefetch_mode == PrefetchMode::kPrefetch) {
asm volatile("cp.async.cg.shared.global.L2::128B [%0], [%1], %2, %3;\n" ::"r"(smem_int_ptr),
"l"(gmem_ptr), "n"(16), "r"(src_in_bytes));
} else {
asm volatile("cp.async.cg.shared.global [%0], [%1], %2, %3;\n" ::"r"(smem_int_ptr),
"l"(gmem_ptr), "n"(16), "r"(src_in_bytes));
}
} else {
if constexpr (prefetch_mode == PrefetchMode::kPrefetch) {
asm volatile(
"{\n"
" .reg .pred p;\n"
" setp.ne.b32 p, %0, 0;\n"
" @p cp.async.cg.shared.global.L2::128B [%1], [%2], %3;\n"
"}\n" ::"r"((int)predicate),
"r"(smem_int_ptr), "l"(gmem_ptr), "n"(16));
} else {
asm volatile(
"{\n"
" .reg .pred p;\n"
" setp.ne.b32 p, %0, 0;\n"
" @p cp.async.cg.shared.global [%1], [%2], %3;\n"
"}\n" ::"r"((int)predicate),
"r"(smem_int_ptr), "l"(gmem_ptr), "n"(16));
}
}
#else
if (predicate) {
*((uint4*)smem_ptr) = *((uint4*)gmem_ptr);
} else {
if constexpr (fill_mode == SharedMemFillMode::kFillZero) {
*((uint4*)smem_ptr) = make_uint4(0, 0, 0, 0);
}
}
#endif
}
} // namespace cp_async
} // namespace sageattention
#endif
+162
View File
@@ -0,0 +1,162 @@
/*
* Copyright (c) 2024 by SageAttention team.
*
* This file is based on code from Flashinfer, https://github.com/flashinfer-ai/flashinfer/blob/v0.1.5/include/flashinfer/math.cuh
* Copyright (c) 2023 by FlashInfer team.
* Small modifications made by SageAttention team, 2024 (e.g., renamed namespace).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SD_CORE_CUDA_SAGEATTENTION_MATH_CUH
#define SD_CORE_CUDA_SAGEATTENTION_MATH_CUH
#include <cuda_fp16.h>
#include <cuda_runtime.h>
namespace sageattention {
#ifndef USHORT_TYPE
#define USHORT_TYPE
typedef unsigned short ushort;
#endif
namespace math {
// log2(e)
constexpr float log2e = 1.44269504088896340736f;
constexpr float log2e_recp = 1.0f / log2e;
__forceinline__ __device__ half2 uint32_as_half2(uint32_t x) { return *(half2*)&x; }
__forceinline__ __device__ uint32_t half2_as_uint32(half2 x) { return *(uint32_t*)&x; }
/*!
* \brief Wrapper of PTX ex2.approx instruction, which computes 2^x
* \param x input
*/
__forceinline__ __device__ float ptx_exp2(float x) {
float y;
asm volatile("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
return y;
}
/*!
* \brief Wrapper of PTX lg2.approx instruction, which computes log2(x)
* \param x input
*/
__forceinline__ __device__ float ptx_log2(float x) {
float y;
asm volatile("lg2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
return y;
}
/*!
* \brief Wrapper of PTX ex2.approx.f16x2 instruction, which computes 2^x
* \param x input
*/
__forceinline__ __device__ half2 ptx_exp2(half2 x) {
uint32_t y_u32;
uint32_t x_u32 = half2_as_uint32(x);
asm volatile("ex2.approx.f16x2 %0, %1;" : "=r"(y_u32) : "r"(x_u32));
return uint32_as_half2(y_u32);
}
/*!
* \brief Wrapper of PTX ex2.approx.f16 instruction, which computes 2^x
* \param x input
*/
__forceinline__ __device__ half ptx_exp2(half x) {
ushort y_u16;
asm volatile("ex2.approx.f16 %0, %1;" : "=h"(y_u16) : "h"(__half_as_ushort(x)));
return __ushort_as_half(y_u16);
}
/*!
* \brief Wrapper of PTX rcp.approx instruction, which computes 1/x
* \param x input
*/
__forceinline__ __device__ float ptx_rcp(float x) {
float y;
asm volatile("rcp.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
return y;
}
/*!
* \brief Wrapper of PTX shfl.sync.bfly instruction, which performs a butterfly shuffle
* between threads in a warp.
* \param x The value in the source lane
* \param lane_mask The mask to perform thread index xor with: y[i] <- x[i ^ delta]
*/
__forceinline__ __device__ float shfl_xor_sync(float x, int lane_mask) {
float y;
asm volatile("shfl.sync.bfly.b32 %0, %1, %2, 0x1f, 0xffffffff;"
: "=f"(y)
: "f"(x), "r"(lane_mask));
return y;
}
/*!
* \brief Wrapper of PTX shfl.sync.bfly instruction on half2, which performs a butterfly
* shuffle between threads in a warp.
* \param x The value in the source lane
* \param lane_mask The mask to perform thread index xor with: y[i] <- x[i ^ lane_mask]
*/
__forceinline__ __device__ half2 shfl_xor_sync(half2 x, int lane_mask) {
return __shfl_xor_sync(0xffffffff, x, lane_mask);
}
/*!
* \brief Wrapper of PTX rsqrt approximation instruction, which computes 1/sqrt(x)
* \param x input
*/
__forceinline__ __device__ float rsqrt(float x) {
float y;
asm volatile("rsqrt.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
return y;
}
/*!
* \brief Wrapper of PTX tanh.approx.f32 instruction, which computes tanh(x)
* \param x input
*/
__forceinline__ __device__ float tanh(float x) {
float y;
asm volatile("tanh.approx.f32 %0, %1;" : "=f"(y) : "f"(x));
return y;
}
/*!
* \brief Wrapper of PTX tanh.approx.f16x2 instruction, which computes tanh(x)
* \param x input
*/
__forceinline__ __device__ half2 tanh(half2 x) {
uint32_t y_u32;
uint32_t x_u32 = half2_as_uint32(x);
asm volatile("tanh.approx.f16x2 %0, %1;" : "=r"(y_u32) : "r"(x_u32));
return uint32_as_half2(y_u32);
}
/*!
* \brief Wrapper of PTX tanh.approx.f16 instruction, which computes tanh(x)
* \param x input
*/
__forceinline__ __device__ half tanh(half x) {
ushort y_u16;
asm volatile("tanh.approx.f16 %0, %1;" : "=h"(y_u16) : "h"(__half_as_ushort(x)));
return __ushort_as_half(y_u16);
}
} // namespace math
} // namespace sageattention
#endif
+729
View File
@@ -0,0 +1,729 @@
/*
* Adapted from Flashinfer, https://github.com/flashinfer-ai/flashinfer/blob/v0.1.5/include/flashinfer/mma.cuh
* Copyright (c) 2023 by FlashInfer team.
*
* Modifications copyright (c) 2024 by SageAttention team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SD_CORE_CUDA_SAGEATTENTION_MMA_CUH
#define SD_CORE_CUDA_SAGEATTENTION_MMA_CUH
#include <cuda_bf16.h>
#include <cuda_fp16.h>
#include <cuda_runtime.h>
#include <type_traits>
namespace sageattention {
namespace mma{
#if (__CUDACC_VER_MAJOR__ >= 11)
#if (!defined(__CUDA_ARCH__) || (__CUDA_ARCH__ >= 800))
#define MMA_F16F16F32_M16N8K16_ENABLED
#define MMA_F16F16F16_M16N8K16_ENABLED
#define MMA_S8S8S32_M16N8K32_ENABLED
#define MMA_S4S4S32_M16N8K64_ENABLED
#endif
#if (!defined(__CUDA_ARCH__) || (__CUDA_ARCH__ >= 750))
#define MMA_F16F16F32_M16N8K8_ENABLED
#define MMA_F16F16F16_M16N8K8_ENABLED
#define LDMATRIX_M8N8X2_ENABLED
#define LDMATRIX_M8N8X4_ENABLED
#endif
#endif
#if (__CUDACC_VER_MAJOR__ * 10000 + __CUDACC_VER_MINOR__ * 100 >= 120400)
#if (!defined(__CUDA_ARCH__) || (__CUDA_ARCH__ >= 890))
#define MMA_F8F8F32_M16N8K16_ENABLED
#endif
#endif
#if (__CUDACC_VER_MAJOR__ * 10000 + __CUDACC_VER_MINOR__ * 100 >= 120800)
#if (!defined(__CUDA_ARCH__) || (__CUDA_ARCH__ >= 890))
#define MMA_F8F8F16_M16N8K16_ENABLED
#endif
#endif
#if defined(__CUDA_ARCH__)
#define RUNTIME_ASSERT(x) __brkpt()
#else
#define RUNTIME_ASSERT(x) assert(0 && x)
#endif
enum class MMAMode {
kInit = 0U,
kInplaceUpdate = 1U,
};
/*!
* \brief Wrapper of PTX ldmatrix m8n8.x2 instruction, loads data from shared memory
* to fragment
* \tparam T data type of the fragment
* \param R pointer to the fragment
* \param smem_ptr pointer to the shared memory
*/
template <typename T>
__device__ __forceinline__ void ldmatrix_m8n8x2(uint32_t* R, T* smem_ptr) {
#ifdef LDMATRIX_M8N8X2_ENABLED
uint32_t smem_int_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(smem_ptr));
asm volatile("ldmatrix.sync.aligned.m8n8.x2.shared.b16 {%0, %1}, [%2];\n"
: "=r"(R[0]), "=r"(R[1])
: "r"(smem_int_ptr));
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for ldmatrix instruction");
#endif
}
/*!
* \brief Wrapper of PTX ldmatrix m8n8.x4 instruction, loads data from shared memory
* to fragment
* \tparam T data type of the fragment
* \param R pointer to the fragment
* \param smem_ptr pointer to the shared memory
*/
template <typename T>
__device__ __forceinline__ void ldmatrix_m8n8x4(uint32_t* R, T* smem_ptr) {
#ifdef LDMATRIX_M8N8X4_ENABLED
uint32_t smem_int_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(smem_ptr));
asm volatile("ldmatrix.sync.aligned.m8n8.x4.shared.b16 {%0, %1, %2, %3}, [%4];\n"
: "=r"(R[0]), "=r"(R[1]), "=r"(R[2]), "=r"(R[3])
: "r"(smem_int_ptr));
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for ldmatrix instruction");
#endif
}
/*!
* \brief Wrapper of PTX ldmatrix m8n8.x4 transposed instruction, loads data from
* shared memory to fragment and transposes the fragment
* \tparam T data type of the fragment
* \param R pointer to the fragment
* \param smem_ptr pointer to the shared memory
*/
template <typename T>
__device__ __forceinline__ void ldmatrix_m8n8x4_trans(uint32_t* R, T* smem_ptr) {
#ifdef LDMATRIX_M8N8X4_ENABLED
uint32_t smem_int_ptr = static_cast<uint32_t>(__cvta_generic_to_shared(smem_ptr));
asm volatile("ldmatrix.sync.aligned.trans.m8n8.x4.shared.b16 {%0, %1, %2, %3}, [%4];\n"
: "=r"(R[0]), "=r"(R[1]), "=r"(R[2]), "=r"(R[3])
: "r"(smem_int_ptr));
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for ldmatrix instruction");
#endif
}
/*!
* \brief Wrapper of the mma m16n8k16 instruction for row major and column major f16 matrix
* multiplication, accumulated in f32.
* \tparam mma_mode The mode of mma instruction, either kInit or kInplaceUpdate
* \param C pointer to the accumulator
* \param A pointer to the fragment of matrix A
* \param B pointer to the fragment of matrix B
*/
template <MMAMode mma_mode = MMAMode::kInplaceUpdate>
__device__ __forceinline__ void mma_sync_m16n8k16_row_col_f16f16f32(float* C, uint32_t* A,
uint32_t* B) {
#ifdef MMA_F16F16F32_M16N8K16_ENABLED
// ! only support half dtype now
if constexpr (mma_mode == MMAMode::kInplaceUpdate)
{
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=f"(C[0]), "=f"(C[1]), "=f"(C[2]), "=f"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "f"(C[0]), "f"(C[1]),
"f"(C[2]), "f"(C[3]));
}
else if constexpr (mma_mode == MMAMode::kInit)
{
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=f"(C[0]), "=f"(C[1]), "=f"(C[2]), "=f"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "f"(0.f), "f"(0.f),
"f"(0.f), "f"(0.f));
}
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for mma instruction");
#endif
}
/*!
* \brief Wrapper of the mma m16n16k16 instruction for row major and column major f16 matrix
* multiplication, accumulated in f32.
* \tparam mma_mode The mode of mma instruction, either kInit or kInplaceUpdate
* \param C pointer to the accumulator
* \param A pointer to the fragment of matrix A
* \param B pointer to the fragment of matrix B
*/
template <MMAMode mma_mode = MMAMode::kInplaceUpdate>
__device__ __forceinline__ void mma_sync_m16n16k16_row_col_f16f16f32(float* C, uint32_t* A,
uint32_t* B) {
#ifdef MMA_F16F16F32_M16N8K16_ENABLED
// ! only support half dtype now
if constexpr (mma_mode == MMAMode::kInplaceUpdate)
{
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=f"(C[0]), "=f"(C[1]), "=f"(C[2]), "=f"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "f"(C[0]), "f"(C[1]),
"f"(C[2]), "f"(C[3]));
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=f"(C[4]), "=f"(C[5]), "=f"(C[6]), "=f"(C[7])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[2]), "r"(B[3]), "f"(C[4]), "f"(C[5]),
"f"(C[6]), "f"(C[7]));
}
else if constexpr (mma_mode == MMAMode::kInit)
{
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=f"(C[0]), "=f"(C[1]), "=f"(C[2]), "=f"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "f"(0.f), "f"(0.f),
"f"(0.f), "f"(0.f));
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=f"(C[4]), "=f"(C[5]), "=f"(C[6]), "=f"(C[7])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[2]), "r"(B[3]), "f"(0.f), "f"(0.f),
"f"(0.f), "f"(0.f));
}
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for mma instruction");
#endif
}
/*!
* \brief Wrapper of the mma m16n8k16 instruction for row major and column major f16 matrix
* multiplication, accumulated in f16.
* \tparam mma_mode The mode of mma instruction, either kInit or kInplaceUpdate
* \param C pointer to the accumulator
* \param A pointer to the fragment of matrix A
* \param B pointer to the fragment of matrix B
*/
template <MMAMode mma_mode = MMAMode::kInplaceUpdate>
__device__ __forceinline__ void mma_sync_m16n8k16_row_col_f16f16f16(uint32_t* C, uint32_t* A,
uint32_t* B) {
#ifdef MMA_F16F16F16_M16N8K16_ENABLED
if constexpr (mma_mode == MMAMode::kInplaceUpdate)
{
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16 "
"{%0, %1},"
"{%2, %3, %4, %5},"
"{%6, %7},"
"{%8, %9};\n"
: "=r"(C[0]), "=r"(C[1])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(C[0]), "r"(C[1]));
}
else if constexpr (mma_mode == MMAMode::kInit)
{
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16 "
"{%0, %1},"
"{%2, %3, %4, %5},"
"{%6, %7},"
"{%8, %9};\n"
: "=r"(C[0]), "=r"(C[1])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(0), "r"(0));
}
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for mma instruction");
#endif
}
/*!
* \brief Wrapper of the mma m16n16k16 instruction for row major and column major f16 matrix
* multiplication, accumulated in f16.
* \tparam mma_mode The mode of mma instruction, either kInit or kInplaceUpdate
* \param C pointer to the accumulator
* \param A pointer to the fragment of matrix A
* \param B pointer to the fragment of matrix B
*/
template <MMAMode mma_mode = MMAMode::kInplaceUpdate>
__device__ __forceinline__ void mma_sync_m16n16k16_row_col_f16f16f16(uint32_t* C, uint32_t* A,
uint32_t* B) {
#ifdef MMA_F16F16F16_M16N8K16_ENABLED
if constexpr (mma_mode == MMAMode::kInplaceUpdate)
{
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16 "
"{%0, %1},"
"{%2, %3, %4, %5},"
"{%6, %7},"
"{%8, %9};\n"
: "=r"(C[0]), "=r"(C[1])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(C[0]), "r"(C[1]));
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16 "
"{%0, %1},"
"{%2, %3, %4, %5},"
"{%6, %7},"
"{%8, %9};\n"
: "=r"(C[2]), "=r"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[2]), "r"(B[3]), "r"(C[2]), "r"(C[3]));
}
else if constexpr (mma_mode == MMAMode::kInit)
{
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16 "
"{%0, %1},"
"{%2, %3, %4, %5},"
"{%6, %7},"
"{%8, %9};\n"
: "=r"(C[0]), "=r"(C[1])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(0), "r"(0));
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16 "
"{%0, %1},"
"{%2, %3, %4, %5},"
"{%6, %7},"
"{%8, %9};\n"
: "=r"(C[2]), "=r"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[2]), "r"(B[3]), "r"(0), "r"(0));
}
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for mma instruction");
#endif
}
/*!
* \brief Wrapper of the mma m16n8k32 instruction for row major and column major int8 matrix
* multiplication, accumulated in int32.
* \tparam mma_mode The mode of mma instruction, either kInit or kInplaceUpdate
* \param C pointer to the accumulator
* \param A pointer to the fragment of matrix A
* \param B pointer to the fragment of matrix B
*/
template <MMAMode mma_mode = MMAMode::kInplaceUpdate>
__device__ __forceinline__ void mma_sync_m16n8k32_row_col_s8s8s32(int32_t* C, uint32_t* A,
uint32_t* B) {
#ifdef MMA_S8S8S32_M16N8K32_ENABLED
if constexpr (mma_mode == MMAMode::kInplaceUpdate)
{
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.s32.s8.s8.s32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=r"(C[0]), "=r"(C[1]), "=r"(C[2]), "=r"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(C[0]), "r"(C[1]),
"r"(C[2]), "r"(C[3]));
}
else if constexpr (mma_mode == MMAMode::kInit)
{
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.s32.s8.s8.s32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=r"(C[0]), "=r"(C[1]), "=r"(C[2]), "=r"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(0), "r"(0),
"r"(0), "r"(0));
}
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for mma instruction");
#endif
}
/*!
* \brief Wrapper of the mma m16n16k32 instruction for row major and column major int8 matrix
* multiplication, accumulated in int32.
* \tparam mma_mode The mode of mma instruction, either kInit or kInplaceUpdate
* \param C pointer to the accumulator
* \param A pointer to the fragment of matrix A
* \param B pointer to the fragment of matrix B
*/
template <MMAMode mma_mode = MMAMode::kInplaceUpdate>
__device__ __forceinline__ void mma_sync_m16n16k32_row_col_s8s8s32(int32_t* C, uint32_t* A,
uint32_t* B) {
#ifdef MMA_S8S8S32_M16N8K32_ENABLED
if constexpr (mma_mode == MMAMode::kInplaceUpdate)
{
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.s32.s8.s8.s32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=r"(C[0]), "=r"(C[1]), "=r"(C[2]), "=r"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(C[0]), "r"(C[1]),
"r"(C[2]), "r"(C[3]));
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.s32.s8.s8.s32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=r"(C[4]), "=r"(C[5]), "=r"(C[6]), "=r"(C[7])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[2]), "r"(B[3]), "r"(C[4]), "r"(C[5]),
"r"(C[6]), "r"(C[7]));
}
else if constexpr (mma_mode == MMAMode::kInit)
{
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.s32.s8.s8.s32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=r"(C[0]), "=r"(C[1]), "=r"(C[2]), "=r"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(0), "r"(0),
"r"(0), "r"(0));
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.s32.s8.s8.s32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=r"(C[4]), "=r"(C[5]), "=r"(C[6]), "=r"(C[7])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[2]), "r"(B[3]), "r"(0), "r"(0),
"r"(0), "r"(0));
}
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for mma instruction");
#endif
}
/*!
* \brief Wrapper of the mma m16n8k32 instruction for row major and column major int4 matrix
* multiplication, accumulated in int32.
* \tparam mma_mode The mode of mma instruction, either kInit or kInplaceUpdate
* \param C pointer to the accumulator
* \param A pointer to the fragment of matrix A
* \param B pointer to the fragment of matrix B
*/
template <MMAMode mma_mode = MMAMode::kInplaceUpdate>
__device__ __forceinline__ void mma_sync_m16n8k64_row_col_s4s4s32(int32_t* C, uint32_t* A,
uint32_t* B) {
#ifdef MMA_S4S4S32_M16N8K64_ENABLED
if constexpr (mma_mode == MMAMode::kInplaceUpdate)
{
asm volatile(
"mma.sync.aligned.m16n8k64.row.col.s32.s4.s4.s32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=r"(C[0]), "=r"(C[1]), "=r"(C[2]), "=r"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(C[0]), "r"(C[1]),
"r"(C[2]), "r"(C[3]));
}
else if constexpr (mma_mode == MMAMode::kInit)
{
asm volatile(
"mma.sync.aligned.m16n8k64.row.col.s32.s4.s4.s32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=r"(C[0]), "=r"(C[1]), "=r"(C[2]), "=r"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(0), "r"(0),
"r"(0), "r"(0));
}
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for mma instruction");
#endif
}
/*!
* \brief Wrapper of the mma m16n16k64 instruction for row major and column major int4 matrix
* multiplication, accumulated in int32.
* \tparam mma_mode The mode of mma instruction, either kInit or kInplaceUpdate
* \param C pointer to the accumulator
* \param A pointer to the fragment of matrix A
* \param B pointer to the fragment of matrix B
*/
template <MMAMode mma_mode = MMAMode::kInplaceUpdate>
__device__ __forceinline__ void mma_sync_m16n16k64_row_col_s4s4s32(int32_t* C, uint32_t* A,
uint32_t* B) {
#ifdef MMA_S4S4S32_M16N8K64_ENABLED
if constexpr (mma_mode == MMAMode::kInplaceUpdate)
{
asm volatile(
"mma.sync.aligned.m16n8k64.row.col.s32.s4.s4.s32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=r"(C[0]), "=r"(C[1]), "=r"(C[2]), "=r"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(C[0]), "r"(C[1]),
"r"(C[2]), "r"(C[3]));
asm volatile(
"mma.sync.aligned.m16n8k64.row.col.s32.s4.s4.s32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=r"(C[4]), "=r"(C[5]), "=r"(C[6]), "=r"(C[7])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[2]), "r"(B[3]), "r"(C[4]), "r"(C[5]),
"r"(C[6]), "r"(C[7]));
}
else if constexpr (mma_mode == MMAMode::kInit)
{
asm volatile(
"mma.sync.aligned.m16n8k64.row.col.s32.s4.s4.s32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=r"(C[0]), "=r"(C[1]), "=r"(C[2]), "=r"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(0), "r"(0),
"r"(0), "r"(0));
asm volatile(
"mma.sync.aligned.m16n8k64.row.col.s32.s4.s4.s32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=r"(C[4]), "=r"(C[5]), "=r"(C[6]), "=r"(C[7])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[2]), "r"(B[3]), "r"(0), "r"(0),
"r"(0), "r"(0));
}
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for mma instruction");
#endif
}
/*!
* \brief Wrapper of the mma m16n8k32 instruction for row major and column major fp8 e4m3 matrix
* multiplication, accumulated in fp32.
* \tparam mma_mode The mode of mma instruction, either kInit or kInplaceUpdate
* \param C pointer to the accumulator
* \param A pointer to the fragment of matrix A
* \param B pointer to the fragment of matrix B
*/
template <MMAMode mma_mode = MMAMode::kInplaceUpdate>
__device__ __forceinline__ void mma_sync_m16n8k32_row_col_f8f8f32(float* C, uint32_t* A,
uint32_t* B) {
#ifdef MMA_F8F8F32_M16N8K16_ENABLED
if constexpr (mma_mode == MMAMode::kInplaceUpdate)
{
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.f32.e4m3.e4m3.f32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=f"(C[0]), "=f"(C[1]), "=f"(C[2]), "=f"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "f"(C[0]), "f"(C[1]),
"f"(C[2]), "f"(C[3]));
}
else if constexpr (mma_mode == MMAMode::kInit)
{
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.f32.e4m3.e4m3.f32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=f"(C[0]), "=f"(C[1]), "=f"(C[2]), "=f"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "f"(0.f), "f"(0.f),
"f"(0.f), "f"(0.f));
}
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for mma instruction");
#endif
}
/*!
* \brief Wrapper of the mma m16n16k32 instruction for row major and column major fp8 matrix
* multiplication, accumulated in fp16.
* \tparam mma_mode The mode of mma instruction, either kInit or kInplaceUpdate
* \param C pointer to the accumulator
* \param A pointer to the fragment of matrix A
* \param B pointer to the fragment of matrix B
*/
template <MMAMode mma_mode = MMAMode::kInplaceUpdate>
__device__ __forceinline__ void mma_sync_m16n16k32_row_col_f8f8f16(uint32_t* C_uint32, uint32_t* A,
uint32_t* B) {
//uint32_t* C_uint32 = reinterpret_cast<uint32_t*>(C);
#ifdef MMA_F8F8F16_M16N8K16_ENABLED
if constexpr (mma_mode == MMAMode::kInplaceUpdate)
{
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.f16.e4m3.e4m3.f16 "
"{%0, %1},"
"{%2, %3, %4, %5},"
"{%6, %7},"
"{%8, %9};\n"
: "=r"(C_uint32[0]), "=r"(C_uint32[1])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(C_uint32[0]), "r"(C_uint32[1]));
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.f16.e4m3.e4m3.f16 "
"{%0, %1},"
"{%2, %3, %4, %5},"
"{%6, %7},"
"{%8, %9};\n"
: "=r"(C_uint32[2]), "=r"(C_uint32[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[2]), "r"(B[3]), "r"(C_uint32[2]), "r"(C_uint32[3]));
}
else if constexpr (mma_mode == MMAMode::kInit)
{
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.f16.e4m3.e4m3.f16 "
"{%0, %1},"
"{%2, %3, %4, %5},"
"{%6, %7},"
"{%8, %9};\n"
: "=r"(C_uint32[0]), "=r"(C_uint32[1])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "r"(0), "r"(0));
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.f16.e4m3.e4m3.f16 "
"{%0, %1},"
"{%2, %3, %4, %5},"
"{%6, %7},"
"{%8, %9};\n"
: "=r"(C_uint32[2]), "=r"(C_uint32[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[2]), "r"(B[3]), "r"(0), "r"(0));
}
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for mma instruction");
#endif
}
/*!
* \brief Wrapper of the mma m16n16k32 instruction for row major and column major fp8 matrix
* multiplication, accumulated in fp32.
* \tparam mma_mode The mode of mma instruction, either kInit or kInplaceUpdate
* \param C pointer to the accumulator
* \param A pointer to the fragment of matrix A
* \param B pointer to the fragment of matrix B
*/
template <MMAMode mma_mode = MMAMode::kInplaceUpdate>
__device__ __forceinline__ void mma_sync_m16n16k32_row_col_f8f8f32(float* C, uint32_t* A,
uint32_t* B) {
#ifdef MMA_F8F8F32_M16N8K16_ENABLED
if constexpr (mma_mode == MMAMode::kInplaceUpdate)
{
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.f32.e4m3.e4m3.f32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=f"(C[0]), "=f"(C[1]), "=f"(C[2]), "=f"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "f"(C[0]), "f"(C[1]),
"f"(C[2]), "f"(C[3]));
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.f32.e4m3.e4m3.f32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=f"(C[4]), "=f"(C[5]), "=f"(C[6]), "=f"(C[7])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[2]), "r"(B[3]), "f"(C[4]), "f"(C[5]),
"f"(C[6]), "f"(C[7]));
}
else if constexpr (mma_mode == MMAMode::kInit)
{
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.f32.e4m3.e4m3.f32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=f"(C[0]), "=f"(C[1]), "=f"(C[2]), "=f"(C[3])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]), "f"(0.f), "f"(0.f),
"f"(0.f), "f"(0.f));
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.f32.e4m3.e4m3.f32 "
"{%0, %1, %2, %3},"
"{%4, %5, %6, %7},"
"{%8, %9},"
"{%10, %11, %12, %13};\n"
: "=f"(C[4]), "=f"(C[5]), "=f"(C[6]), "=f"(C[7])
: "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[2]), "r"(B[3]), "f"(0.f), "f"(0.f),
"f"(0.f), "f"(0.f));
}
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for mma instruction");
#endif
}
/*!
* \brief Use mma instructions to compute rowsum.
*/
__device__ __forceinline__ void rowsum_f16f16f32(float* d, uint32_t* s) {
#ifdef MMA_F16F16F32_M16N8K16_ENABLED
asm volatile(
"{\n"
"mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 "
"{%0, _, %1, _},"
"{%2, %3, %4, %5},"
"{%6, %7},"
"{%8, 0., %9, 0.};\n"
"}\n"
: "=f"(d[0]), "=f"(d[1])
: "r"(s[0]), "r"(s[1]), "r"(s[2]), "r"(s[3]), "r"(1006648320), // 1006648320 packs two 1.0f in half precision
"r"(1006648320), "f"(d[0]), "f"(d[1]));
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for mma instruction");
#endif
}
/*!
* \brief Use mma instructions to compute rowsum.
*/
__device__ __forceinline__ void rowsum_f8f8f32(float* d, uint32_t* s) {
#ifdef MMA_F8F8F32_M16N8K16_ENABLED
asm volatile(
"mma.sync.aligned.m16n8k32.row.col.f32.e4m3.e4m3.f32 "
"{%0, _, %1, _},"
"{%2, %3, %4, %5},"
"{%6, %7},"
"{%8, 0., %9, 0.};\n"
: "=f"(d[0]), "=f"(d[1])
: "r"(s[0]), "r"(s[1]), "r"(s[2]), "r"(s[3]), "r"(943208504), "r"(943208504), // 943208504 packs four 1.0f in e4m3
"f"(d[0]), "f"(d[1]));
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for mma instruction");
#endif
}
} // namespace mma
} // namespace sageattention
#endif
@@ -0,0 +1,155 @@
/*
* Copyright (c) 2024 by SageAttention team.
*
* Inspired by CUTLASS, https://github.com/NVIDIA/cutlass/blob/main/include/cutlass/numeric_conversion.h
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SD_CORE_CUDA_SAGEATTENTION_NUMERIC_CONVERSION_CUH
#define SD_CORE_CUDA_SAGEATTENTION_NUMERIC_CONVERSION_CUH
#include <cuda_bf16.h>
#include <cuda_fp16.h>
#include <cuda_fp8.h>
#include <cuda_runtime.h>
namespace sageattention {
#if (__CUDACC_VER_MAJOR__ * 10000 + __CUDACC_VER_MINOR__ * 100 >= 120400)
#if (!defined(__CUDA_ARCH__) || (__CUDA_ARCH__ >= 890))
#define FP8_CAST_ENABLED
#endif
#endif
#if defined(__CUDA_ARCH__)
#define RUNTIME_ASSERT(x) __brkpt()
#else
#define RUNTIME_ASSERT(x) assert(0 && x)
#endif
__device__ __forceinline__ void unpack_half2_from_uint32_to_float(float* dest, uint32_t source) {
uint16_t h0 = source & 0xFFFF;
uint16_t h1 = (source >> 16) & 0xFFFF;
asm("cvt.f32.f16 %0, %1;" : "=f"(dest[0]) : "h"(h0));
asm("cvt.f32.f16 %0, %1;" : "=f"(dest[1]) : "h"(h1));
}
__device__ __forceinline__ void floatx4_to_e4m3x4(uint32_t *dest, float *source0, float *source1)
{
#ifdef FP8_CAST_ENABLED
asm volatile( \
"{\n" \
".reg .b16 lo;\n" \
".reg .b16 hi;\n" \
"cvt.rn.satfinite.e4m3x2.f32 lo, %2, %1;\n" \
"cvt.rn.satfinite.e4m3x2.f32 hi, %4, %3;\n" \
"mov.b32 %0, {lo, hi};\n" \
"}" \
: "=r"(dest[0]) : "f"(source0[0]), "f"(source0[1]), "f"(source1[0]), "f"(source1[1]));
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for FP8 CAST instruction");
#endif
}
__device__ __forceinline__ void floatx4_to_e5m2x4(uint32_t *dest, float *source0, float *source1)
{
#ifdef FP8_CAST_ENABLED
asm volatile( \
"{\n" \
".reg .b16 lo;\n" \
".reg .b16 hi;\n" \
"cvt.rn.satfinite.e5m2x2.f32 lo, %2, %1;\n" \
"cvt.rn.satfinite.e5m2x2.f32 hi, %4, %3;\n" \
"mov.b32 %0, {lo, hi};\n" \
"}" \
: "=r"(dest[0]) : "f"(source0[0]), "f"(source1[1]), "f"(source1[0]), "f"(source1[1]));
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for FP8 CAST instruction");
#endif
}
__device__ __forceinline__ void halfx4_to_e4m3x4(uint32_t *dest, uint32_t *source0, uint32_t *source1)
{
#ifdef FP8_CAST_ENABLED
asm volatile( \
"{\n" \
".reg .b16 lo;\n" \
".reg .b16 hi;\n" \
"cvt.rn.satfinite.e4m3x2.f16x2 lo, %1;\n" \
"cvt.rn.satfinite.e4m3x2.f16x2 hi, %2;\n" \
"mov.b32 %0, {lo, hi};\n" \
"}" \
: "=r"(dest[0]) : "r"(source0[0]), "r"(source1[0]));
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for FP8 CAST instruction");
#endif
}
__device__ __forceinline__ void halfx4_to_e5m2x4(uint32_t *dest, uint32_t *source0, uint32_t *source1)
{
#ifdef FP8_CAST_ENABLED
asm volatile( \
"{\n" \
".reg .b16 lo;\n" \
".reg .b16 hi;\n" \
"cvt.rn.satfinite.e5m2x2.f16x2 lo, %1;\n" \
"cvt.rn.satfinite.e5m2x2.f16x2 hi, %2;\n" \
"mov.b32 %0, {lo, hi};\n" \
"}" \
: "=r"(dest[0]) : "r"(source0[0]), "r"(source1[0]));
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for FP8 CAST instruction");
#endif
}
__device__ __forceinline__ void e4m3x4_to_halfx4(uint32_t *dest0, uint32_t *dest1, uint32_t *source)
{
#ifdef FP8_CAST_ENABLED
asm volatile( \
"{\n" \
".reg .b16 lo, hi;\n" \
"mov.b32 {lo, hi}, %2;\n" \
"cvt.rn.f16x2.e4m3x2 %0, lo;\n" \
"cvt.rn.f16x2.e4m3x2 %1, hi;\n" \
"}\n" : "=r"(dest0[0]), "=r"(dest1[0]) : "r"(source[0]));
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for FP8 CAST instruction");
#endif
}
__device__ __forceinline__ void e5m2x4_to_halfx4(uint32_t *dest0, uint32_t *dest1, uint32_t *source)
{
#ifdef FP8_CAST_ENABLED
asm volatile( \
"{\n" \
".reg .b16 lo, hi;\n" \
"mov.b32 {lo, hi}, %2;\n" \
"cvt.rn.f16x2.e5m2x2 %0, lo;\n" \
"cvt.rn.f16x2.e5m2x2 %1, hi;\n" \
"}\n" : "=r"(dest0[0]), "=r"(dest1[0]) : "r"(source[0]));
#else
RUNTIME_ASSERT("Unsupported CUDA architecture for FP8 CAST instruction");
#endif
}
__device__ __forceinline__ int8_t float_to_int8_rn(float x)
{
uint32_t dst;
asm volatile("cvt.rni.sat.s8.f32 %0, %1;" : "=r"(dst) : "f"(x));
return reinterpret_cast<const int8_t&>(dst);
}
} // namespace sageattention
#endif
@@ -0,0 +1,202 @@
/*
* Adapted from Flashinfer, https://github.com/flashinfer-ai/flashinfer/blob/v0.1.5/include/flashinfer/permuted_smem.cuh
* Copyright (c) 2023 by FlashInfer team.
*
* Modifications copyright (c) 2024 by SageAttention team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SD_CORE_CUDA_SAGEATTENTION_PERMUTED_SMEM_CUH
#define SD_CORE_CUDA_SAGEATTENTION_PERMUTED_SMEM_CUH
#include <cuda_bf16.h>
#include <cuda_fp16.h>
#include <cuda_runtime.h>
#include "cp_async.cuh"
#include "mma.cuh"
namespace sageattention {
enum class SwizzleMode {
k32B, // for k32B mode, a line of shared memory must have 32B (16 half value)
k64B, // for k64B mode, a line of shared memory must have 64B (32 half value)
k128B, // 128B already spans all banks in shared memory. a line of shared memory can have multiple 128B.
};
// Use 128bit as the granularity to fetch/store data per thread to maximize memory bandwidth
using b128_t = uint4;
/*!
* \brief A stateless shared memory wrapper that uses templates to avoid runtime conditionals. It makes sure
* that access to consecutive rows idx in the same column idx will make full use of the shared memory bank through
* permutation in the granularity of 128bit.
*
* This struct treats all offsets to be the number of `b128_t` elements. It is designed to be stateless,
* meaning it does not maintain any information about the current pointer position. The offset returnd by
* the struct can be used to access the shared memory through the provided interface.
*
* The struct guarantees that the read to permuted offset (i, j) will be the value stored in permuted offset (i, j).
* We assume that shared memory operation operates on at least two consecutive 128-bit values in a row within a warp.
* Under this assumption, we do not permute for k32B mode.
*/
template <SwizzleMode swizzle_mode, uint32_t stride>
struct smem_t {
// The base pointer.
b128_t* base;
// How many b128_t value a row contains
// uint32_t stride;
__device__ __forceinline__ smem_t() : base(nullptr) {}
template <typename T>
__device__ __forceinline__ smem_t(T* base) : base((b128_t*)base) {
if constexpr (swizzle_mode == SwizzleMode::k128B) {
static_assert(stride % 8 == 0, "Stride must be multiple of 8 for 128B swizzle mode");
} else if constexpr (swizzle_mode == SwizzleMode::k64B) {
static_assert(stride == 4, "Stride must be 4 for 64B swizzle mode");
} else if constexpr (swizzle_mode == SwizzleMode::k32B) {
static_assert(stride == 2, "Stride must be 2 for 32B swizzle mode");
} else {
static_assert(swizzle_mode != swizzle_mode, "Unsupported swizzle mode");
}
}
/*!
* \brief Set the base pointer.
*/
template <typename T>
__device__ __forceinline__ void set_base(T* new_base) {
base = (b128_t*)new_base;
}
/*!
* \brief Compute the element offset given coordinates in a permuted shared memory.
* \param i The row index.
* \param j The column index.
*/
static __device__ __forceinline__ uint32_t get_permuted_offset(const uint32_t &i, const uint32_t &j) {
if constexpr (swizzle_mode == SwizzleMode::k128B) {
return i * stride + (j ^ (i % 8));
} else if constexpr (swizzle_mode == SwizzleMode::k64B) {
return i * stride + (j ^ ((i / 2) % 4));
} else if constexpr (swizzle_mode == SwizzleMode::k32B) {
return i * stride + j;
}
}
/*!
* \tparam step_size The step size to advance the offset in the permuted shared memory.
* \param offset The current offset.
*/
template <uint32_t step_size>
static __device__ __forceinline__ uint32_t advance_offset_by_column(const uint32_t &offset) {
if constexpr (swizzle_mode == SwizzleMode::k128B) {
static_assert(step_size % 8 == 0,
"Unsupported step size");
return offset + step_size;
} else if constexpr (swizzle_mode == SwizzleMode::k64B) {
static_assert(step_size == 4, "Unsupported step size");
return offset + step_size;
} else if constexpr (swizzle_mode == SwizzleMode::k32B) {
static_assert(step_size == 2, "Unsupported step size");
return offset + step_size;
}
}
// ! use with care
template <uint32_t step_size>
static __device__ __forceinline__ uint32_t advance_offset_by_column(const uint32_t &offset, const uint32_t &step_idx) {
if constexpr (swizzle_mode == SwizzleMode::k128B) {
static_assert(step_size == 2 || step_size == 4 || step_size % 8 == 0,
"Unsupported step size");
if constexpr (step_size == 2) {
return (offset ^ (0x2 + (0x4 * (step_idx % 2 == 1)))) + (step_idx % 4 == 3) * 8;
} else if constexpr (step_size == 4) {
return (offset ^ 0x4) + (step_idx % 2 == 1) * 8;
} else {
// step_size % 8 == 0
return offset + step_size;
}
} else if constexpr (swizzle_mode == SwizzleMode::k64B) {
static_assert(step_size == 2 || step_size == 4, "Unsupported step size");
if constexpr (step_size == 2) {
return (offset ^ 0x2) + (step_idx % 2 == 1) * 4;
} else {
return offset + step_size;
}
} else if constexpr (swizzle_mode == SwizzleMode::k32B) {
return offset + step_size;
}
}
template <uint32_t step_size>
static __device__ __forceinline__ uint32_t advance_offset_by_row(const uint32_t &offset) {
if constexpr (swizzle_mode == SwizzleMode::k128B) {
static_assert(step_size == 4 || step_size % 8 == 0, "Unsupported step size");
if constexpr (step_size == 4) {
return (offset ^ 0x4) + step_size * stride;
} else {
// step_size % 8 == 0
return offset + step_size * stride;
}
} else if constexpr (swizzle_mode == SwizzleMode::k64B) {
static_assert(step_size == 4 || step_size % 8 == 0, "Unsupported step size");
if constexpr (step_size == 4) {
return (offset ^ 0x2) + step_size * stride;
} else {
// step_size % 8 == 0
return offset + step_size * stride;
}
} else if constexpr (swizzle_mode == SwizzleMode::k32B) {
return offset + step_size * stride;
}
}
__device__ __forceinline__ void ldmatrix_m8n8x2(const uint32_t &offset, uint32_t* R) const {
b128_t* smem_ptr = base + offset;
mma::ldmatrix_m8n8x2(R, smem_ptr);
}
__device__ __forceinline__ void ldmatrix_m8n8x4(const uint32_t &offset, uint32_t* R) const {
b128_t* smem_ptr = base + offset;
mma::ldmatrix_m8n8x4(R, smem_ptr);
}
__device__ __forceinline__ void ldmatrix_m8n8x4_trans(const uint32_t &offset, uint32_t* R) const {
b128_t* smem_ptr = base + offset;
mma::ldmatrix_m8n8x4_trans(R, smem_ptr);
}
template <cp_async::SharedMemFillMode fill_mode, typename T>
__device__ __forceinline__ void load_128b_async(const uint32_t &offset, const T* gptr, bool predicate) const {
b128_t* smem_ptr = base + offset;
cp_async::pred_load_128b<cp_async::PrefetchMode::kPrefetch, fill_mode>(
smem_ptr, reinterpret_cast<const b128_t*>(gptr), predicate);
}
template <typename T>
__device__ __forceinline__ void load_128b_async(const uint32_t &offset, const T* gptr) const {
b128_t* smem_ptr = base + offset;
cp_async::load_128b<cp_async::PrefetchMode::kPrefetch>(smem_ptr, reinterpret_cast<const b128_t*>(gptr));
}
template <typename T>
__device__ __forceinline__ void store_128b(const uint32_t &offset, T* gptr) const {
*reinterpret_cast<b128_t*>(gptr) = *(base + offset);
}
};
} // namespace sageattention
#endif
+54
View File
@@ -5,6 +5,7 @@
#include "ggml-ext.h" #include "ggml-ext.h"
#include <algorithm> #include <algorithm>
#include <cmath>
#include <cstring> #include <cstring>
#include <mutex> #include <mutex>
#include <unordered_set> #include <unordered_set>
@@ -223,6 +224,59 @@ ggml_tensor* ggml_quantize_i8_convrot(ggml_context* ctx, ggml_tensor* x, int gro
return ggml_ext_op_create(ctx, sd_ggml_convrot_op(), &x, 1, &attr); return ggml_ext_op_create(ctx, sd_ggml_convrot_op(), &x, 1, &attr);
} }
static bool sage_attn_infer(ggml_tensor* const* inputs, int count, const void* attributes, ggml_ext_tensor_desc* out) {
if (count != 3 || !attributes)
return false;
SDSageAttentionParams params;
std::memcpy(&params, attributes, sizeof(params));
if (!std::isfinite(params.scale) || params.scale <= 0.f || params.mode < GGML_SAGE_ATTN_AUTO || params.mode > GGML_SAGE_ATTN_2_PLUS_PLUS)
return false;
for (int i = 0; i < count; ++i) {
const auto* t = inputs[i];
if (!t || !ggml_is_contiguous(t) || t->type != (i == 2 ? GGML_TYPE_F16 : GGML_TYPE_F32))
return false;
int64_t elements = 1;
for (int d = 0; d < GGML_MAX_DIMS; ++d) {
if (t->ne[d] <= 0 || t->ne[d] > INT64_MAX / int64_t(sizeof(float)) / elements)
return false;
elements *= t->ne[d];
}
}
const auto* q = inputs[0];
const auto* k = inputs[1];
if (q->ne[0] != k->ne[0] || !ggml_are_same_shape(k, inputs[2]) || q->ne[2] % k->ne[2] != 0 || q->ne[3] != k->ne[3])
return false;
*out = {GGML_TYPE_F32, {q->ne[0], q->ne[2], q->ne[1], q->ne[3]}};
return true;
}
ggml_ext_op_t sd_ggml_sage_attn_op() {
static const auto op = [] {
const ggml_ext_op_desc desc = {GGML_EXT_ABI_VERSION, "sd.sage_attn.v1", sizeof(SDSageAttentionParams), sage_attn_infer};
auto result = ggml_ext_register_op(&desc);
GGML_ASSERT(result);
return result;
}();
return op;
}
bool sd_ggml_sage_attn_describe(const ggml_tensor* node, SDSageAttentionParams* params) {
if (!node || ggml_ext_op_get(node) != sd_ggml_sage_attn_op() || node->src[3] || !ggml_is_contiguous(node))
return false;
ggml_ext_tensor_desc output;
const auto* attributes = ggml_ext_op_attributes(node);
if (!sage_attn_infer(node->src, 3, attributes, &output) || node->type != output.type || std::memcmp(node->ne, output.ne, sizeof(output.ne)))
return false;
std::memcpy(params, attributes, sizeof(*params));
return true;
}
ggml_tensor* ggml_sage_attn(ggml_context* ctx, ggml_tensor* q, ggml_tensor* k, ggml_tensor* v, float scale, ggml_sage_attn_mode mode) {
ggml_tensor* inputs[] = {q, k, v};
const SDSageAttentionParams params = {scale, mode};
return ggml_ext_op_create(ctx, sd_ggml_sage_attn_op(), inputs, 3, &params);
}
void sd_ggml_register_extensions(ggml_backend_t backend) { void sd_ggml_register_extensions(ggml_backend_t backend) {
if (!backend) { if (!backend) {
return; return;
+17
View File
@@ -34,6 +34,23 @@ bool sd_ggml_i8_describe(const ggml_tensor* node, SDI8Operation* operation);
bool sd_ggml_convrot_describe(const ggml_tensor* node, int* group_size); bool sd_ggml_convrot_describe(const ggml_tensor* node, int* group_size);
ggml_tensor* ggml_mul_mat_i8_tensorwise(ggml_context* ctx, ggml_tensor* weight, ggml_tensor* input, ggml_tensor* weight_scale, ggml_tensor* bias, int group_size, ggml_tensor* input_scale = nullptr); ggml_tensor* ggml_mul_mat_i8_tensorwise(ggml_context* ctx, ggml_tensor* weight, ggml_tensor* input, ggml_tensor* weight_scale, ggml_tensor* bias, int group_size, ggml_tensor* input_scale = nullptr);
ggml_tensor* ggml_quantize_i8_convrot(ggml_context* ctx, ggml_tensor* input, int group_size); ggml_tensor* ggml_quantize_i8_convrot(ggml_context* ctx, ggml_tensor* input, int group_size);
enum ggml_sage_attn_mode {
GGML_SAGE_ATTN_AUTO = 0,
GGML_SAGE_ATTN_2,
GGML_SAGE_ATTN_2_PLUS_PLUS,
};
struct SDSageAttentionParams {
float scale;
ggml_sage_attn_mode mode;
};
ggml_ext_op_t sd_ggml_sage_attn_op();
bool sd_ggml_sage_attn_describe(const ggml_tensor* node, SDSageAttentionParams* params);
// Q/K: contiguous F32 [D, tokens, heads, batch], V: contiguous F16 in the same layout.
// Output: F32 [D, query heads, query tokens, batch]. CUDA-only, unmasked inference.
ggml_tensor* ggml_sage_attn(ggml_context* ctx, ggml_tensor* q, ggml_tensor* k, ggml_tensor* v, float scale, ggml_sage_attn_mode mode);
#endif #endif
#endif #endif