aclnnFlashAttentionVarLenScore

Applicable Products

ProductSupported
Ascend 950PR/Ascend 950DT√
Atlas A3 training products/Atlas A3 inference products√
Atlas A2 training products/Atlas A2 inference products√
Atlas 200I/500 A2 inference products×
Atlas inference products×
Atlas training products×

Function

  • Description: In training scenarios, this API uses the FlashAttention algorithm to implement self-attention computation. The difference from the aclnnFlashAttentionScore API is that this API supports variable-length S computation, meaning that multiple sequences of unequal lengths can be passed in at once. When using this API, query, key, and value are passed in TND format, where T stands for total number, representing the sum of the lengths of all sequences. The cumulative length of each sequence is passed in sequence via the actualSeqQLenOptional and actualSeqKvLenOptional inputs to distinguish different sequences. The attention result is computed separately for each sequence.

  • Formula:

    The forward computation formula of attention is as follows:

    attention_out=Dropout(Softmax(Mask(scale∗(pse+query∗keyT),atten_mask)),keep_prob)∗valueattention\_out=Dropout(Softmax(Mask(scale*(pse+query*key^T),atten\_mask)),keep\_prob)*value

Function Prototype

Each operator uses a two-phase API. You must first call the "aclnnFlashAttentionVarLenScoreGetWorkspaceSize" API to obtain the workspace size required for computation and the executor that encapsulates the operator's computation process, and then call the "aclnnFlashAttentionVarLenScore" API to perform the computation.

aclnnStatus aclnnFlashAttentionVarLenScoreGetWorkspaceSize(
  const aclTensor   *query,
  const aclTensor   *key,
  const aclTensor   *value,
  const aclTensor   *realShiftOptional,
  const aclTensor   *dropMaskOptional,
  const aclTensor   *paddingMaskOptional,
  const aclTensor   *attenMaskOptional,
  const aclIntArray *prefixOptional,
  const aclIntArray *actualSeqQLenOptional,
  const aclIntArray *actualSeqKvLenOptional,
  double             scaleValue,
  double             keepProb,
  int64_t            preTokens,
  int64_t            nextTokens,
  int64_t            headNum,
  char              *inputLayout,
  int64_t            innerPrecise,
  int64_t            sparseMode,
  const aclTensor   *softmaxMaxOut,
  const aclTensor   *softmaxSumOut,
  const aclTensor   *softmaxOutOut,
  const aclTensor   *attentionOutOut,
  uint64_t          *workspaceSize,
  aclOpExecutor    **executor)
aclnnStatus aclnnFlashAttentionVarLenScore(
  void             *workspace,
  uint64_t          workspaceSize,
  aclOpExecutor    *executor,
  const aclrtStream stream)

aclnnFlashAttentionVarLenScoreGetWorkspaceSize

  • Parameters

    Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-contiguous Tensor
    query Input Query in the formula. Its data type is the same as that of key/value. FLOAT16, BFLOAT16, FLOAT32 ND [TND] √
    key Input Represents key in the formula. The data type is consistent with that of query and value. FLOAT16, BFLOAT16, FLOAT32 ND [TND] √
    value Input value in the formula. The data type is consistent with the data type of query/key. FLOAT16, BFLOAT16, FLOAT32 ND [TND] √
    realShiftOptional optional input pse in the formula. The data type is the same as that of query. FLOAT16, BFLOAT16, FLOAT32 ND [B,N,1024,Skv], [1,N,1024,Skv] √
    dropMaskOptional Optional input Dropout in the formula. - UINT8 ND 0, 1 √
    paddingMaskOptional Optional input Reserved parameter, not used yet. - - - - -
    attenMaskOptional Optional input atten_mask in the formula. A value of 1 indicates that the bit does not participate in the computation, and a value of 0 indicates that the bit participates in the computation. BOOL, UINT8 ND [B,N,Sq,Skv], [B,1,Sq,Skv], [1,1,Sq,Skv], [Sq,Skv] √
    prefixOptional Optional input Represents the N value of each batch in the prefix sparse computation scenario. - INT64 ND 0, 1 -
    actualSeqQLenOptional Optional input Describes the sequence length of the query corresponding to each batch. - INT64 ND 0, 1 -
    actualSeqKvLenOptional Optional input Describes the sequence length of key/value corresponding to each batch. - INT64 ND 0, 1 -
    scaleValue Optional input scale in the formula, representing the scaling factor. - DOUBLE - - -
    keepProb Optional input Represents the proportion of 1s in dropMaskOptional. Value range: (0, 1]. DOUBLE - - -
    preTokens Optional input Used for sparse computation, indicating the left boundary of the sliding window. - INT64 - - -
    nextTokens Optional input Indicates the right boundary of the sliding window for sparse computation. - INT64 - - -
    headNum Input Represents the number of heads on a single card, i.e., the length of the N axis of the input query. - INT64 - - -
    inputLayout Input Specifies the data layout format of the input query, key, and value. Supports TND. String - - -
    innerPrecise Optional input Improves precision. Configure it to the default value 0. INT64 - - -
    sparseMode Optional input Indicates the sparse mode. Supports configuration values of 0, 1, 2, 3, 4, 6, 7, and 8. INT64 - - -
    softmaxMaxOut Output Max intermediate result of the Softmax computation, used for backward computation. - FLOAT ND [N,T,8] √
    softmaxSumOut Output Intermediate sum result of the Softmax computation, used for backward computation. - FLOAT ND [N,T,8] √
    softmaxOutOut Output Reserved parameter, not used currently. - - - - -
    attentionOutOut Output Final output of the formula. The data type and shape type are consistent with those of query. FLOAT16, BFLOAT16, FLOAT32 ND [TND] √
    workspaceSize Output Returns the workspace size to be applied on the device side. - - - - -
    executor Output Returns the operator 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 Error Code Description
    ACLNN_ERR_PARAM_NULLPTR 161001 The input parameter is a mandatory input, output, or mandatory attribute, and is a null pointer.
    ACLNN_ERR_PARAM_INVALID 161002 The data type of query, key, value, realShiftOptional, dropMaskOptional, paddingMaskOptional, attenMaskOptional, softmaxMaxOut, softmaxSumOut, softmaxOutOut, or attentionOutOut is not within the supported range.
    The data format of query, key, value, realShiftOptional, dropMaskOptional, paddingMaskOptional, attenMaskOptional, softmaxMaxOut, softmaxSumOut, softmaxOutOut, or attentionOutOut is not within the supported range.

aclnnFlashAttentionVarLenScore

  • Parameters

    Parameter Input/Output Description
    workspace Input Address of the workspace memory applied on the Device side.
    workspaceSize Input Size of the workspace applied on the Device side, obtained through the first-phase API aclnnFlashAttentionVarLenScoreGetWorkspaceSize.
    executor Input Operator executor, which contains the operator computation process.
    stream Input Specifies the stream for task execution.
  • Return Value

    aclnnStatus: return code. For details, see aclnn Return Code.

Constraints

  • Deterministic computation:

    • aclnnFlashAttentionVarLenScore defaults to a deterministic implementation.
  • When this API is used with PyTorch, ensure that the versions of CANN-related packages match those of PyTorch-related packages.

  • The constraints on the input query, key, and value are as follows:

    • B: The batchsize must be equal.

    • D: Head-Dim must satisfy (qD == kD && kD >= vD).

    • The input layout must be consistent.

  • The data types of the input query, key, value, and realShiftOptional must be consistent.

  • The data format of query, key, and value supports only TND, where T is the tightly packed data of the B and S axes (SeqLenQ and SeqLenKV of each batch). B (Batch) indicates the input sample batch size, S (Seq-Length) indicates the input sample sequence length, H (Head-Size) indicates the hidden layer size, N (Head-Num) indicates the number of heads, and D (Head-Dim) indicates the minimum unit size of the hidden layer, satisfying D = H/N.

  • The N of query and the N of key/value can be unequal, but they must be in a proportional relationship, that is, Nq/Nkv must be a non-zero integer, with Nq in the value range of 1 to 256. When Nq/Nkv > 1, it is GQA (grouped-query attention); when Nkv = 1, it is MQA (multi-query attention). Unless otherwise specified in this document, N refers to Nq.

  • Constraints on data shapes, where:

    • T: value range is 1 to 1M.

    • N: value range is 1 to 256.

    • D: value range is 1 to 768.

  • realShiftOptional: If Sq is greater than 1024, Sq and Skv of each batch have the same length, and the scenario is a lower triangular mask with sparseMode 0, 2, or 3, you can enable alibi positional encoding compression. In this case, only the last 1024 rows of the original pse need to be input for memory optimization, that is, alibi_compress = ori_pse[:, :, -1024:, :], as detailed below:

    • When the parameter differs for each batch, the shape is BNHSkv (H=1024).

    • When the parameter is the same for each batch, the shape is 1NHSkv (H=1024).

    • If this parameter is not used, nullptr can be passed in.

  • innerPrecise: Currently, 0 and 1 are reserved configuration values, and 2 enables invalid row computation. Its function is to avoid precision loss caused by entire rows being masked during computation, but this configuration leads to performance degradation. If the operator can determine that an invalid row scenario exists, it automatically enables invalid row computation, for example, when sparseMode is 3 and Sq > Skv.

  • The constraints of sparseMode are as follows:

    • When all attenMaskOptional shapes are smaller than 2048 and identical, it is recommended to use the default mode to reduce memory usage.

    • When configured as 1, 2, 3, or 6, the user-configured preTokens and nextTokens do not take effect.

    • When configured as 0, 4, 7, or 8, ensure that attenMaskOptional is consistent with the ranges of preTokens and nextTokens.

    • If the user does not specify a value, it is recommended to pass 0.

    • For details about different sparse modes, see sparse mode description.

    • When sparseMode is set to 1, 2, 3, 4, 6, 7, or 8, the correct corresponding attenMaskOptional must be passed in; otherwise, the computation result will be incorrect. When attenMaskOptional is None, the sparseMode, preTokens, and nextTokens parameters do not take effect, and full computation is used by default.

    • When configured as 3, invalid row computation is not supported, and Sq ≤ Skv must be satisfied for each batch.

    • When configured as 7, the optional input realShiftOptional is not supported.

    • When configured as 8, the optional input realShiftOptional is supported when q and kv of each sequence have equal lengths, and global PSE generation is performed. Outer split along the q direction is supported, and q and kv of each sequence must have equal lengths before the outer split.

  • In some scenarios, if the computational load is too large, the operator execution may time out (aicore error, with errorStr: timeout or trap error). In this case, axis splitting is recommended. Note: The computational load here is affected by parameters such as B, S, N, and D. Larger values result in a larger computational load.

  • In the prefixOptional sparse computation scenario (sparseMode = 6), when Sq > Skv, the value range of prefix N is [0, Skv]; when Sq <= Skv, the value range of prefix N is [Skv-Sq, Skv].

  • In the band scenario, preTokens and nextTokens must have an intersection.

  • The attenMaskOptional input does not support padding, meaning that attenMaskOptional cannot contain any row that is all ones.

  • The actualSeqQLenOptional input supports an S length of 0 for a certain batch. In this case, the optional input realShiftOptional is not supported. For example, if the actual S lengths are [2,2,0,2,2], the input actualSeqQLenOptional is [2,4,4,6,8]. The value range of the length of actualSeqQLenOptional is 1 to 2K. When the prefixOptional input is present, its maximum supported length is 1K.

Example

The following provides example code for reference only. For details about compilation and running, see Compile and Run Samples.

#include <iostream>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_flash_attention_score.h"

#define CHECK_RET(cond, return_expr) \
  do {                               \
    if (!(cond)) {                   \
      return_expr;                   \
    }                                \
  } while (0)

#define LOG_PRINT(message, ...)     \
  do {                              \
    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;
}

void PrintOutResult(std::vector<int64_t> &shape, void** deviceAddr) {
  auto size = GetShapeSize(shape);
  std::vector<float> resultData(size, 0);
  auto ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]),
                         *deviceAddr, size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return);
  for (int64_t i = 0; i < size; i++) {
    LOG_PRINT("mean result[%ld] is: %f\n", i, resultData[i]);
  }
}

int Init(int32_t deviceId, aclrtContext* context, aclrtStream* stream) {
  // Boilerplate: initialize resources.
  auto ret = aclInit(nullptr);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
  ret = aclrtSetDevice(deviceId);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret);
  ret = aclrtCreateContext(context, deviceId);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateContext failed. ERROR: %d\n", ret); return ret);
  ret = aclrtSetCurrentContext(*context);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetCurrentContext failed. ERROR: %d\n", ret); return ret);
  ret = aclrtCreateStream(stream);
  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);
  // Call aclrtMalloc to apply for device-side memory.
  auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret);
  // Call aclrtMemcpy to copy host-side data to device-side memory.
  ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret);

  // Calculate the strides of a contiguous tensor.
  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];
  }

  // Call the aclCreateTensor API to create an aclTensor.
  *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND,
                            shape.data(), shape.size(), *deviceAddr);
  return 0;
}

int main() {
  // 1. (boilerplate) device/stream initialization. See the ACL API manual.
  // Fill in the deviceId based on your actual device.
  int32_t deviceId = 0;
  aclrtContext context;
  aclrtStream stream;
  auto ret = Init(deviceId, &context, &stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);

  // 2. Construct inputs and outputs. Custom construction is required based on the API.
  std::vector<int64_t> qShape = {256, 1, 128};
  std::vector<int64_t> kShape = {256, 1, 128};
  std::vector<int64_t> vShape = {256, 1, 128};
  std::vector<int64_t> attenmaskShape = {256, 256};

  std::vector<int64_t> attentionOutShape = {256, 1, 128};
  std::vector<int64_t> softmaxMaxShape = {256, 1, 8};
  std::vector<int64_t> softmaxSumShape = {256, 1, 8};

  void* qDeviceAddr = nullptr;
  void* kDeviceAddr = nullptr;
  void* vDeviceAddr = nullptr;
  void* attenmaskDeviceAddr = nullptr;
  void* attentionOutDeviceAddr = nullptr;
  void* softmaxMaxDeviceAddr = nullptr;
  void* softmaxSumDeviceAddr = nullptr;

  aclTensor* q = nullptr;
  aclTensor* k = nullptr;
  aclTensor* v = nullptr;
  aclTensor* pse = nullptr;
  aclTensor* dropMask = nullptr;
  aclTensor* padding = nullptr;
  aclTensor* attenmask = nullptr;
  aclTensor* attentionOut = nullptr;
  aclTensor* softmaxMax = nullptr;
  aclTensor* softmaxSum = nullptr;
  aclTensor* softmaxOut = nullptr;

  std::vector<float> qHostData(32768, 1);
  std::vector<float> kHostData(32768, 1);
  std::vector<float> vHostData(32768, 1);
  std::vector<uint8_t> attenmaskHostData(65536, 0);
  std::vector<float> attentionOutHostData(32768, 0);
  std::vector<float> softmaxMaxHostData(2048, 3.0);
  std::vector<float> softmaxSumHostData(2048, 3.0);

  ret = CreateAclTensor(qHostData, qShape, &qDeviceAddr, aclDataType::ACL_FLOAT, &q);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(kHostData, kShape, &kDeviceAddr, aclDataType::ACL_FLOAT, &k);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(vHostData, vShape, &vDeviceAddr, aclDataType::ACL_FLOAT, &v);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(attenmaskHostData, attenmaskShape, &attenmaskDeviceAddr, aclDataType::ACL_UINT8, &attenmask);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(attentionOutHostData, attentionOutShape, &attentionOutDeviceAddr, aclDataType::ACL_FLOAT, &attentionOut);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(softmaxMaxHostData, softmaxMaxShape, &softmaxMaxDeviceAddr, aclDataType::ACL_FLOAT, &softmaxMax);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(softmaxSumHostData, softmaxSumShape, &softmaxSumDeviceAddr, aclDataType::ACL_FLOAT, &softmaxSum);
  CHECK_RET(ret == ACL_SUCCESS, return ret);

  std::vector<int64_t> prefixOp = {0};
  aclIntArray *prefix = aclCreateIntArray(prefixOp.data(), 1);
  std::vector<int64_t>  acSeqQLenOp = {256};
  std::vector<int64_t>  acSeqKvLenOp = {256};
  aclIntArray* acSeqQLen = aclCreateIntArray(acSeqQLenOp.data(), acSeqQLenOp.size());
  aclIntArray* acSeqKvLen = aclCreateIntArray(acSeqKvLenOp.data(), acSeqKvLenOp.size());
  double scaleValue = 0.088388;
  double keepProb = 1;
  int64_t preTokens = 65536;
  int64_t nextTokens = 65536;
  int64_t headNum = 1;
  int64_t innerPrecise = 0;
  int64_t sparseMode = 0;

  char layOut[5] = {'T', 'N', 'D', 0};

  // 3. Call the CANN operator library API. Replace with the specific API name.
  uint64_t workspaceSize = 0;
  aclOpExecutor* executor;

  // Call the first-phase API of aclnnFlashAttentionVarLenScore.
  ret = aclnnFlashAttentionVarLenScoreGetWorkspaceSize(
            q, k, v, pse, dropMask, padding, attenmask, prefix, acSeqQLen, acSeqKvLen,
            scaleValue, keepProb, preTokens, nextTokens, headNum, layOut, innerPrecise,
            sparseMode, softmaxMax, softmaxSum, softmaxOut, attentionOut, &workspaceSize, &executor);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnFlashAttentionVarLenScoreGetWorkspaceSize failed. ERROR: %d\n", ret);
            return ret);

  // Apply for device memory based on the workspaceSize calculated by the first-phase API.
  void* workspaceAddr = nullptr;
  if (workspaceSize > 0) {
    ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret);
  }

  // Call the second-phase API of aclnnFlashAttentionVarLenScore.
  ret = aclnnFlashAttentionVarLenScore(workspaceAddr, workspaceSize, executor, stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnFlashAttentionVarLenScore failed. ERROR: %d\n", ret); return ret);

  // 4. (Boilerplate) Synchronously wait for task execution to complete.
  ret = aclrtSynchronizeStream(stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);

  // 5. Obtain the output value and copy the result from the device memory to the host memory. Modify this step based on the specific API definition.
  PrintOutResult(attentionOutShape, &attentionOutDeviceAddr);
  PrintOutResult(softmaxMaxShape, &softmaxMaxDeviceAddr);
  PrintOutResult(softmaxSumShape, &softmaxSumDeviceAddr);

  // 6. Release aclTensor and aclScalar. Modify based on the specific API definition.
  aclDestroyTensor(q);
  aclDestroyTensor(k);
  aclDestroyTensor(v);
  aclDestroyTensor(attenmask);
  aclDestroyTensor(attentionOut);
  aclDestroyTensor(softmaxMax);
  aclDestroyTensor(softmaxSum);

  // 7. Release device resources.
  aclrtFree(qDeviceAddr);
  aclrtFree(kDeviceAddr);
  aclrtFree(vDeviceAddr);
  aclrtFree(attenmaskDeviceAddr);
  aclrtFree(attentionOutDeviceAddr);
  aclrtFree(softmaxMaxDeviceAddr);
  aclrtFree(softmaxSumDeviceAddr);
  if (workspaceSize > 0) {
    aclrtFree(workspaceAddr);
  }
  aclrtDestroyStream(stream);
  aclrtDestroyContext(context);
  aclrtResetDevice(deviceId);
  aclFinalize();

  return 0;
}