aclnnSparseFlashAttention
Applicable Products
| Product | Supported |
|---|---|
| √ | |
| √ |
Function
Description: sparse_flash_attention (SFA) is an efficient attention computation module designed for large sequence length inference scenarios. This module significantly reduces the computation workload by "computing only the critical parts." However, it introduces a large amount of discrete memory access, which increases data transfer time and thereby affects overall performance.
Formula:
Where are the Key and Value of higher importance obtained based on a certain selection algorithm (such as lightning_indexer), generally having sparse or block-sparse characteristics, and is the dimension of each head of .
Function Prototype
Each operator is divided into a two-phase API. The "aclnnSparseFlashAttentionGetWorkspaceSize" API must be called first to obtain the workspace size required for computation and the executor that contains the operator computation process, and then the "aclnnSparseFlashAttention" API is called to perform the computation.
aclnnStatus aclnnSparseFlashAttentionGetWorkspaceSize(
const aclTensor *query,
const aclTensor *key,
const aclTensor *value,
const aclTensor *sparseIndices,
const aclTensor *blockTable,
const aclTensor *actualSeqLengthsQuery,
const aclTensor *actualSeqLengthsKv,
const aclTensor *queryRope,
const aclTensor *keyRope,
double scaleValue,
int64_t sparseBlockSize,
char *layoutQuery,
char *layoutKv,
int64_t sparseMode,
int64_t preTokens,
int64_t nextTokens,
int64_t attentionMode,
bool returnSoftmaxLse,
const aclTensor *attentionOutOut,
const aclTensor *softmaxMaxOut,
const aclTensor *softmaxSumOut,
uint64_t *workspaceSize,
aclOpExecutor **executor)aclnnStatus aclnnSparseFlashAttention(
void *workspace,
uint64_t workspaceSize,
aclOpExecutor *executor,
const aclrtStream stream)aclnnSparseFlashAttentionGetWorkspaceSize
- Parameters:
- The dimension meanings of query, key, and value parameters: B (Batch Size) indicates the batch size of input samples, S (Sequence Length) indicates the sequence length of input samples, H (Head Size) indicates the size of the hidden layer, N (Head Num) indicates the number of heads, D (Head Dim) indicates the minimum unit size of the hidden layer, and D = H/N is satisfied. T indicates the cumulative sum of sequence lengths of all batch input samples.
- Q_S and S1 indicate S in the query shape, KV_S and S2 indicate S in the key shape, Q_N and N1 indicate num_query_heads, KV_N and N2 indicate num_key_value_heads, T1 indicates T in the query shape, and T2 indicates the cumulative sum of sequence lengths of input samples in the key shape.
| Parameter | Input/Output | Description | Instruction | Data Type | Data Format | Dimension (Shape) | Non-contiguous Tensor |
|---|---|---|---|---|---|---|---|
| query (aclTensor) | Input | The Query input of the attention structure. | Empty tensors are not supported. | FLOAT16, BFLOAT16 | ND |
|
x |
| key (aclTensor) | Input | Key input of the attention structure. |
|
FLOAT16, BFLOAT16 | ND |
|
x |
| value (aclTensor) | Input | Value input of the attention structure. | Empty tensors are not supported. | FLOAT16, BFLOAT16 | ND |
|
x |
| sparseIndices (aclTensor) | Input | Index for discrete retrieval of kvCache. |
|
INT32 | ND |
|
x |
| blockTable (aclTensor) | Input | Indicates the block mapping table used for kvCache storage in PageAttention. |
|
INT32 | ND | The shape supports (B, S2/block_size). | x |
| actualSeqLengthsQuery (aclTensor) | Input | Indicates the number of valid tokens of the query in different batches. |
|
INT32 | ND | (B,) | x |
| actualSeqLengthsKv (aclTensor) | Input | Indicates the number of valid tokens in key and value for different batches. |
|
INT32 | ND | (B,) | x |
| queryRope (aclTensor) | Input | Indicates the rope information of the query in the MLA structure. | An empty tensor is not supported. | FLOAT16, BFLOAT16 | ND |
|
x |
| keyRope (aclTensor) | Input | Indicates the RoPE information of the key in the MLA structure. | Empty tensors are not supported. | FLOAT16, BFLOAT16 | ND |
|
x |
| scaleValue (double) | Input | Represents the scaling factor. | - | FLOAT16 | - | - | - |
| sparseBlockSize (int64_t) | Input | Indicates the block size in the sparse stage. |
|
INT64 | - | - | - |
| layoutQuery (char) | Input | Indicates the data layout format of the input query. |
|
STRING | - | - | - |
| layoutKv (char) | Input | Indicates the data layout format of the input key. |
|
STRING | - | - | - |
| sparseMode (int64_t) | Input | Indicates the sparse mode. |
|
INT64 | - | - | - |
| preTokens (int64_t) | Input | Used for sparse computation. Indicates the number of previous tokens that the attention needs to compute correlation with. | Only the default value 2^63-1 is supported. | INT64 | - | - | - |
| nextTokens (int64_t) | Input | Used for sparse computation. Indicates the number of subsequent tokens that the attention needs to compute association with. | Only the default value 2^63-1 is supported. | INT64 | - | - | - |
| attentionMode (int64_t) | Input | - | Only 2 is supported, which indicates the MLA-absorb mode. | INT64 | - | - | - |
| returnSoftmaxLse (bool) | Input | Indicates whether to return softmax_max and softmax_sum. |
|
BOOL | - | - | - |
| attentionOut (aclTensor) | Output | Output in the formula. | Empty tensors are not supported. | FLOAT16, BFLOAT16 | ND |
|
x |
| softmaxMaxOut (aclTensor) | Output | The softmax_max obtained by taking the max of the result of query multiplied by key in the attention algorithm. | Empty tensors are not supported. | FLOAT | ND |
|
x |
| softmaxSumOut (aclTensor) | Output | The result of query multiplied by key in the attention algorithm, minus softmax_max, then exponentiated, and finally summed to obtain softmax_sum. | Empty tensors are not supported. | FLOAT | ND |
|
x |
| workspaceSize (uint64_t*) | Output | Returns the workspace size to be applied for on the Device side. | - | - | - | - | - |
| executor (aclOpExecutor) | Output | Returns the op executor, which contains the operator computation flow. | - | - | - | - | - |
Return value
aclnnStatus: return code. For details, see aclnn Return Code.
The first-phase API performs input parameter validation and returns an error in the following scenarios:
Return Value Return Code Description ACLNN_ERR_PARAM_NULLPTR 161001 If the input parameter is a mandatory input, output, or mandatory attribute and is a null pointer, 161001 is returned. ACLNN_ERR_PARAM_INVALID 161002 The data type or data format of query, key, value, sparseIndices, blockTable, actualSeqLengthsQuery, actualSeqLengthsKv, queryRope, keyRope, scaleValue, sparseBlockSize, layoutQuery, layoutKv, sparseMode, attentionMode, returnSoftmaxLse, attentionOut, softmaxMaxOut, or softmaxSumOut is not within the supported range.
aclnnSparseFlashAttention
Parameters:
Parameter Input/Output Description workspace Input Workspace memory address applied for on the Device side. workspaceSize Input Workspace size applied for on the Device side, obtained from the first-phase API aclnnSparseFlashAttentionGetWorkspaceSize. executor Input Operator executor that contains the operator computation flow. stream Input Specifies the stream for task execution. Return value
aclnnStatus: return code. For details, see aclnn Return Code.
Constraints
This API supports use in inference scenarios.
This API supports graph mode.
N1 supports 1/2/4/8/16/32/64/128.
block_size is the number of tokens in a block. The value of block_size must be a multiple of 16, with a maximum of 1024.
The D in the query parameter and the D in key and value must be equal to 512, and the Dr in the query_rope parameter and the Dr in key_rope must be equal to 64.
The data types of the query, key, and value parameters must be consistent.
sparse_block_size must be divisible by block_size.
Example
The following provides example code for reference only. For details about compilation and running, see Compile and Run Samples.
/**
* Copyright (c) 2024 Huawei Technologies Co., Ltd.
* This file is a part of the CANN Open Software.
* Licensed under CANN Open Software License Agreement Version 1.0 (the "License").
* Please refer to the License for details. You may not use this file except in compliance with the License.
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
* See LICENSE in the root of the software repository for the full text of the License.
*/
/*!
* \file test_incre_flash_attention_v4.cpp
* \brief
*/
#include <iostream>
#include <vector>
#include <cmath>
#include <cstring>
#include "securec.h"
#include "acl/acl.h"
#include "aclnnop/aclnn_sparse_flash_attention.h"
using namespace std;
namespace {
#define CHECK_RET(cond) ((cond) ? true :(false))
#define LOG_PRINT(message, ...) \
do { \
(void)printf(message, ##__VA_ARGS__); \
} while (0)
int64_t GetShapeSize(const std::vector<int64_t>& shape) {
int64_t shapeSize = 1;
for (auto i : shape) {
shapeSize *= i;
}
return shapeSize;
}
int Init(int32_t deviceId, aclrtStream* stream) {
auto ret = aclInit(nullptr);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
LOG_PRINT("aclInit failed. ERROR: %d\n", ret);
return ret;
}
ret = aclrtSetDevice(deviceId);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret);
return ret;
}
ret = aclrtCreateStream(stream);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret);
return ret;
}
return 0;
}
template <typename T>
int CreateAclTensor(const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr,
aclDataType dataType, aclTensor** tensor) {
auto size = GetShapeSize(shape) * sizeof(T);
auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret);
return ret;
}
ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret);
return ret;
}
std::vector<int64_t> strides(shape.size(), 1);
for (int64_t i = shape.size() - 2; i >= 0; i--) {
strides[i] = shape[i + 1] * strides[i + 1];
}
*tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND,
shape.data(), shape.size(), *deviceAddr);
return 0;
}
struct TensorResources {
void* queryDeviceAddr = nullptr;
void* keyDeviceAddr = nullptr;
void* valueDeviceAddr = nullptr;
void* sparseIndicesDeviceAddr = nullptr;
void* attentionOutDeviceAddr = nullptr;
void* softmaxMaxDeviceAddr = nullptr;
void* softmaxSumDeviceAddr = nullptr;
void* queryRopeDeviceAddr = nullptr;
void* keyRopeDeviceAddr = nullptr;
aclTensor* queryTensor = nullptr;
aclTensor* keyTensor = nullptr;
aclTensor* valueTensor = nullptr;
aclTensor* sparseIndicesTensor = nullptr;
aclTensor* attentionOutTensor = nullptr;
aclTensor* softmaxMaxTensor = nullptr;
aclTensor* softmaxSumTensor = nullptr;
aclTensor* queryRopeTensor = nullptr;
aclTensor* keyRopeTensor = nullptr;
};
int InitializeTensors(TensorResources& resources) {
std::vector<int64_t> queryShape = {1, 2, 1, 512};
std::vector<int64_t> keyShape = {1, 2, 1, 512};
std::vector<int64_t> valueShape = {1, 2, 1, 512};
std::vector<int64_t> sparseIndicesShape = {1, 2, 1, 2};
std::vector<int64_t> attentionOutShape = {1, 2, 1, 512};
std::vector<int64_t> softmaxMaxShape = {1, 2, 1, 16};
std::vector<int64_t> softmaxSumShape = {1, 2, 1, 16};
std::vector<int64_t> queryRopeShape = {1, 2, 1, 64};
std::vector<int64_t> keyRopeShape = {1, 2, 1, 64};
int64_t queryShapeSize = GetShapeSize(queryShape);
int64_t keyShapeSize = GetShapeSize(keyShape);
int64_t valueShapeSize = GetShapeSize(valueShape);
int64_t sparseIndicesShapeSize = GetShapeSize(sparseIndicesShape);
int64_t attentionOutShapeSize = GetShapeSize(attentionOutShape);
int64_t softmaxMaxShapeSize = GetShapeSize(softmaxMaxShape);
int64_t softmaxSumShapeSize = GetShapeSize(softmaxSumShape);
int64_t queryRopeShapeSize = GetShapeSize(queryRopeShape);
int64_t keyRopeShapeSize = GetShapeSize(keyRopeShape);
std::vector<float> queryHostData(queryShapeSize, 1);
std::vector<float> keyHostData(keyShapeSize, 1);
std::vector<float> valueHostData(valueShapeSize, 1);
std::vector<int32_t> sparseIndicesHostData(sparseIndicesShapeSize, 1);
std::vector<float> attentionOutHostData(attentionOutShapeSize, 1);
std::vector<float> softmaxMaxHostData(softmaxMaxShapeSize, 1);
std::vector<float> softmaxSumHostData(softmaxSumShapeSize, 1);
std::vector<float> queryRopeHostData(queryRopeShapeSize, 1);
std::vector<float> keyRopeHostData(keyRopeShapeSize, 1);
// Create query aclTensor.
int ret = CreateAclTensor(queryHostData, queryShape, &resources.queryDeviceAddr,
aclDataType::ACL_FLOAT16, &resources.queryTensor);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
return ret;
}
// Create key aclTensor.
ret = CreateAclTensor(keyHostData, keyShape, &resources.keyDeviceAddr,
aclDataType::ACL_FLOAT16, &resources.keyTensor);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
return ret;
}
// Create value aclTensor.
ret = CreateAclTensor(valueHostData, valueShape, &resources.valueDeviceAddr,
aclDataType::ACL_FLOAT16, &resources.valueTensor);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
return ret;
}
// Create sparseIndices aclTensor.
ret = CreateAclTensor(sparseIndicesHostData, sparseIndicesShape, &resources.sparseIndicesDeviceAddr,
aclDataType::ACL_INT32, &resources.sparseIndicesTensor);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
return ret;
}
// Create queryRope aclTensor.
ret = CreateAclTensor(queryRopeHostData, queryRopeShape, &resources.queryRopeDeviceAddr,
aclDataType::ACL_FLOAT16, &resources.queryRopeTensor);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
return ret;
}
// Create keyRope aclTensor.
ret = CreateAclTensor(keyRopeHostData, keyRopeShape, &resources.keyRopeDeviceAddr,
aclDataType::ACL_FLOAT16, &resources.keyRopeTensor);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
return ret;
}
// Create attention_out aclTensor.
ret = CreateAclTensor(attentionOutHostData, attentionOutShape, &resources.attentionOutDeviceAddr,
aclDataType::ACL_FLOAT16, &resources.attentionOutTensor);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
return ret;
}
// Create softmax_max aclTensor.
ret = CreateAclTensor(softmaxMaxHostData, softmaxMaxShape, &resources.softmaxMaxDeviceAddr,
aclDataType::ACL_FLOAT, &resources.softmaxMaxTensor);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
return ret;
}
// Create softmax_sum aclTensor.
ret = CreateAclTensor(softmaxSumHostData, softmaxSumShape, &resources.softmaxSumDeviceAddr,
aclDataType::ACL_FLOAT, &resources.softmaxSumTensor);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
return ret;
}
return ACL_SUCCESS;
}
int ExecuteSparseFlashAttention(TensorResources& resources, aclrtStream stream,
void** workspaceAddr, uint64_t* workspaceSize) {
int64_t d = 2;
double scaleValue = 1 / sqrt(d);
int64_t sparseBlockSize = 64;
constexpr const char layerOutStr[] = "BSND";
constexpr size_t layerOutLen = sizeof(layerOutStr);
char layoutQuery[layerOutLen];
char layoutKv[layerOutLen];
errno_t memcpyRet = memcpy_s(layoutQuery, sizeof(layoutQuery), layerOutStr, layerOutLen);
if (memcpyRet != 0) {
LOG_PRINT("memcpy_s layoutQuery failed. ERROR: %d\n", memcpyRet);
return -1;
}
memcpyRet = memcpy_s(layoutKv, sizeof(layoutKv), layerOutStr, layerOutLen);
if (memcpyRet != 0) {
LOG_PRINT("memcpy_s layoutKv failed. ERROR: %d\n", memcpyRet);
return -1;
}
int64_t sparseMode = 3;
int64_t preTokens = 9223372036854775807;
int64_t nextTokens = 9223372036854775807;
int64_t attentionMode = 2;
bool returnSoftmaxLse = false;
aclOpExecutor* executor;
int ret = aclnnSparseFlashAttentionGetWorkspaceSize(resources.queryTensor, resources.keyTensor, resources.valueTensor, resources.sparseIndicesTensor, nullptr, nullptr, nullptr, resources.queryRopeTensor, resources.keyRopeTensor,
scaleValue, sparseBlockSize, layoutQuery, layoutKv, sparseMode, preTokens,
nextTokens, attentionMode, returnSoftmaxLse, resources.attentionOutTensor, resources.softmaxMaxTensor, resources.softmaxSumTensor, workspaceSize, &executor);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
LOG_PRINT("aclnnSparseFlashAttentionGetWorkspaceSize failed. ERROR: %d\n", ret);
return ret;
}
if (*workspaceSize > 0ULL) {
ret = aclrtMalloc(workspaceAddr, *workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret);
return ret;
}
}
ret = aclnnSparseFlashAttention(*workspaceAddr, *workspaceSize, executor, stream);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
LOG_PRINT("aclnnSparseFlashAttention failed. ERROR: %d\n", ret);
return ret;
}
return ACL_SUCCESS;
}
int PrintOutResult(std::vector<int64_t> &shape, void** deviceAddr) {
auto size = GetShapeSize(shape);
std::vector<aclFloat16> resultData(size, 0);
auto ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]),
*deviceAddr, size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret);
return ret;
}
for (int64_t i = 0; i < size; i++) {
LOG_PRINT("mean result[%ld] is: %f\n", i, aclFloat16ToFloat(resultData[i]));
}
return ACL_SUCCESS;
}
void CleanupResources(TensorResources& resources, void* workspaceAddr,
aclrtStream stream, int32_t deviceId) {
if (resources.queryTensor) {
aclDestroyTensor(resources.queryTensor);
}
if (resources.keyTensor) {
aclDestroyTensor(resources.keyTensor);
}
if (resources.valueTensor) {
aclDestroyTensor(resources.valueTensor);
}
if (resources.sparseIndicesTensor) {
aclDestroyTensor(resources.sparseIndicesTensor);
}
if (resources.attentionOutTensor) {
aclDestroyTensor(resources.attentionOutTensor);
}
if (resources.softmaxMaxTensor) {
aclDestroyTensor(resources.softmaxMaxTensor);
}
if (resources.softmaxSumTensor) {
aclDestroyTensor(resources.softmaxSumTensor);
}
if (resources.queryRopeTensor) {
aclDestroyTensor(resources.queryRopeTensor);
}
if (resources.keyRopeTensor) {
aclDestroyTensor(resources.keyRopeTensor);
}
if (resources.queryDeviceAddr) {
aclrtFree(resources.queryDeviceAddr);
}
if (resources.keyDeviceAddr) {
aclrtFree(resources.keyDeviceAddr);
}
if (resources.valueDeviceAddr) {
aclrtFree(resources.valueDeviceAddr);
}
if (resources.sparseIndicesDeviceAddr) {
aclrtFree(resources.sparseIndicesDeviceAddr);
}
if (resources.attentionOutDeviceAddr) {
aclrtFree(resources.attentionOutDeviceAddr);
}
if (resources.softmaxMaxDeviceAddr) {
aclrtFree(resources.softmaxMaxDeviceAddr);
}
if (resources.softmaxSumDeviceAddr) {
aclrtFree(resources.softmaxSumDeviceAddr);
}
if (resources.queryRopeDeviceAddr) {
aclrtFree(resources.queryRopeDeviceAddr);
}
if (resources.keyRopeDeviceAddr) {
aclrtFree(resources.keyRopeDeviceAddr);
}
if (workspaceAddr) {
aclrtFree(workspaceAddr);
}
if (stream) {
aclrtDestroyStream(stream);
}
aclrtResetDevice(deviceId);
aclFinalize();
}
} // namespace
int main() {
int32_t deviceId = 0;
aclrtStream stream = nullptr;
TensorResources resources = {};
void* workspaceAddr = nullptr;
uint64_t workspaceSize = 0;
std::vector<int64_t> attentionOutShape = {1, 2, 1, 16};
std::vector<int64_t> softmaxMaxShape = {1, 2, 1, 16};
std::vector<int64_t> softmaxSumShape = {1, 2, 1, 16};
int ret = ACL_SUCCESS;
// 1. Initialize device and stream
ret = Init(deviceId, &stream);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
LOG_PRINT("Init acl failed. ERROR: %d\n", ret);
return ret;
}
// 2. Initialize tensors
ret = InitializeTensors(resources);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
CleanupResources(resources, workspaceAddr, stream, deviceId);
return ret;
}
// 3. Execute the operation
ret = ExecuteSparseFlashAttention(resources, stream, &workspaceAddr, &workspaceSize);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
CleanupResources(resources, workspaceAddr, stream, deviceId);
return ret;
}
// 4. Synchronize stream
ret = aclrtSynchronizeStream(stream);
if (!CHECK_RET(ret == ACL_SUCCESS)) {
LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret);
CleanupResources(resources, workspaceAddr, stream, deviceId);
return ret;
}
// 5. Process results
printf("-----------attentionOut output-----------\n");
PrintOutResult(attentionOutShape, &resources.attentionOutDeviceAddr);
printf("-----------softmaxMax output-----------\n");
PrintOutResult(softmaxMaxShape, &resources.softmaxMaxDeviceAddr);
printf("-----------softmaxSum output-----------\n");
PrintOutResult(softmaxSumShape, &resources.softmaxSumDeviceAddr);
// 6. Cleanup resources
CleanupResources(resources, workspaceAddr, stream, deviceId);
return 0;
}