aclnnRotaryPositionEmbeddingV2

📄 View Source Code

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: Performs single-path rotary position encoding computation. Compared with aclnnRotaryPositionEmbedding, this API adds the input parameter rotate, which provides performance gains by inputting a rotary encoding matrix in recommendation scenarios. Select the appropriate API based on your actual situation.

  • Recommended Scenarios for rotate

    • interleave mode, and B * N * S > 28800.

    • half mode is recommended only in the following scenario: when the input matrix x needs to be split into multiple parts along the last dimension, and each part requires a call to the aclnnRotaryPositionEmbedding API for rotary position encoding computation, you can construct a rotary encoding matrix to achieve performance gains with a single call. For example, when the layout of x is BSND and needs to be split into three parts:

      x is split into three parts, x=[x1∣x2∣x3](dim=4)∈RB×S×N×D,x1∈RB×S×N×D1,x2∈RB×S×N×D2,x3∈RB×S×N×D3x = [x1|x2|x3]_{(dim=4)} ∈ R^{B×S×N×D}, x1 ∈ R^{B×S×N×D1},x2 ∈ R^{B×S×N×D2},x3 ∈ R^{B×S×N×D3}, where D=D1+D2+D3D = D1 + D2 + D3. You can then construct a rotate matrix to complete the rotary position encoding computation of x with a single call to the aclnnRotaryPositionEmbeddingV2 API. The rotate matrix is constructed as follows: rotate=diag(rotate1,rotate2,rotate3)=(rotate1000rotate2000rotate3)rotate = diag(rotate1, rotate2, rotate3) = \begin{pmatrix}rotate1&0&0\\0&rotate2&0\\0&0&rotate3\\\end{pmatrix} where rotate1, rotate2, and rotate3 are the rotary encoding matrices for x1, x2, and x3 respectively. For the construction of a single rotary matrix, see the example.

  • Formula:

    • Atlas A3 training products/Atlas A3 inference products ,Atlas A2 training products/Atlas A2 inference products

      • Without the rotate parameter (recommended for half mode 1D):

        (1) half mode (mode equals 0):

        x1=x[...,:x.shape[−1]//2]x1 = x[..., : x.shape[-1] // 2] x2=x[...,x.shape[−1]//2:]x2 = x[..., x.shape[-1] // 2 :] x_rotate=torch.cat((−x2,x1),dim=−1)x\_rotate = torch.cat((-x2, x1), dim=-1) y=x∗cos+x_rotate∗siny = x * cos + x\_rotate * sin

        (2) interleave mode (mode equals 1):

        x1=x[...,::2].view(−1,1)x1 = x[..., ::2].view(-1, 1) x2=x[...,1::2].view(−1,1)x2 = x[..., 1::2].view(-1, 1) x_rotate=torch.cat((−x2,x1),dim=−1).view(x.shape[0],x.shape[1],x.shape[2],x.shape[3])x\_rotate = torch.cat((-x2, x1), dim=-1).view(x.shape[0], x.shape[1], x.shape[2], x.shape[3]) y=x∗cos+x_rotate∗siny = x * cos + x\_rotate * sin
      • When the rotate parameter is passed (the rotate matrix is generated by the developer; see the generation section in the Example for reference):

        x_rotate=x@rotatex\_rotate = x @ rotate y=x∗cos+x_rotate∗siny = x * cos + x\_rotate * sin

Function Prototype

Each operator uses a two-phase API. You must first call the "aclnnRotaryPositionEmbeddingV2GetWorkspaceSize" API to obtain the input parameters and calculate the required workspace size based on the process, and then call the "aclnnRotaryPositionEmbeddingV2" API to perform the computation.

aclnnStatus aclnnRotaryPositionEmbeddingV2GetWorkspaceSize(
    const aclTensor* x,
    const aclTensor* cos,
    const aclTensor* sin,
    int64_t mode,
    const aclTensor* rotate,
    aclTensor* out,
    uint64_t* workspaceSize,
    aclOpExecutor** executor);
aclnnStatus aclnnRotaryPositionEmbeddingV2(
    void          *workspace,
    uint64_t       workspaceSize,
    aclOpExecutor *executor,
    aclrtStream    stream)

aclnnRotaryPositionEmbeddingV2GetWorkspaceSize

  • Parameters:

    Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-Contiguous Tensor
    x Input Tensor on which rotary position encoding is to be performed, i.e., x in the formula. - BFLOAT16, FLOAT16, FLOAT32 ND 3 or 4 √
    cos Input Position encoding tensor, i.e., cos in the formula. Same as the data type of x. BFLOAT16, FLOAT16, FLOAT32 ND 3 or 4 √
    sin Input Position encoding tensor, i.e., sin in the formula. Must have the same data type as x. BFLOAT16, FLOAT16, FLOAT32 ND 3 or 4 √
    mode Input Rotation mode. - INT64 - - -
    rotate Input Rotation matrix. The data type is the same as that of x. BFLOAT16, FLOAT16, FLOAT32 ND 2 -
    out Output Rotary position encoding computation result, i.e., y in the formula. Must have the same data type as x. BFLOAT16, FLOAT16, FLOAT32 ND 4 x
    workspaceSize Output Returns the workspace size to be requested on the Device side. - - - - -
    executor Output Returns the op executor, which contains the operator computation flow. - - - - -
    • Constraints on the mode parameter:

      • Atlas A3 training products/Atlas A3 inference products, Atlas A2 training products/Atlas A2 inference products: 0=half, 1=interleave. The mode parameter constraints for the V2 API are the same as those for the V1 API. Developers can select an appropriate rotate generation method in the auxiliary matrix rotate generation in the Example based on the mode.

      • Ascend 950PR/Ascend 950DT: 2=quarter, 3=interleave-half.

    • The rotate parameter currently supports BFLOAT16, FLOAT16, and FLOAT32 types.

  • 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 Code Error Code Description
    ACLNN_ERR_PARAM_NULLPTR 161001 The input x, cos, sin, or out is a null pointer.
    ACLNN_ERR_PARAM_INVALID 161002 The data types and formats of the input x, cos, sin, and out are not within the supported range.
    ACLNN_ERR_INNER_TILING_ERROR 561103 The shapes of the input x, cos, sin, and out do not match.
    The input mode parameter is not within the range of 0, 1, 2, or 3.

aclnnRotaryPositionEmbeddingV2

  • 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 from the first-phase API aclnnRotaryPositionEmbeddingV2GetWorkspaceSize.
    executor Input Operator executor, which 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

  • Atlas A3 training products/Atlas A3 inference products ,Atlas A2 training products/Atlas A2 inference products

    The input tensor x supports BNSD, BSND, and SBND layouts. The D dimension of input tensors x, cos, sin and output tensor y must be the same size, satisfying D < 896 and must be a multiple of 2. The shapes of input tensor x and output tensor y must be identical. The shapes of input tensors cos and sin must be identical.

    • Half mode:

      • B, N < 1000;

      • When x is BNSD, cos and sin support 11SD, B1SD, and BNSD.

        • When (D/2) % (32/inputDtypeSize) == 0, B * N <= S * 8 must be satisfied.

        • When (D/2) % (32/inputDtypeSize) != 0, B * N * 2 <= (S + coreNum -1) / coreNum or D >= 80 must be satisfied.

      • When x is BSND, cos and sin support 1S1D, BS1D, and BSND.

      • When x is SBND, cos and sin support S11D, SB1D, and SBND.

    • Interleave mode:

      • B * N < 1000

      • When x is in BNSD format, cos and sin support 11SD.

      • When x is in BSND format, cos and sin support 1S1D.

      • When x is in SBND format, cos and sin support S11D.

Example

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

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

#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 shape_size = 1;
    for (auto i : shape) {
        shape_size *= i;
    }
    return shape_size;
}

int Init(int32_t deviceId, aclrtStream* stream) {
    // Boilerplate for resource initialization.
    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 = 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 aclCreateTensor 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;
}

std::vector<float> get_interleave_matrix(int64_t n) {
    std::vector<float> matrix(n * n, 0.0f);
    for (int64_t i = 0; i < n; i += 2) {
        if (i + 1 >= n) {
            break;
        }
        matrix[i * n + (i + 1)] = 1.0f;
        matrix[(i + 1) * n + i] = 1.0f;
    }
    return matrix;
}

int main() {
    // 1. Boilerplate: device/stream initialization. See the ACL API manual.
    // Fill in the deviceId based on the actual device.
    int32_t deviceId = 0;
    aclrtStream stream;
    auto ret = Init(deviceId, &stream);
    // Handle the check based on your needs.
    CHECK_RET(ret == 0, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);
    // 2. Construct the input and output based on the API definition.
    std::vector<int64_t> xShape = {1, 1, 1, 128};
    std::vector<int64_t> cosShape = {1, 1, 1, 128};
    std::vector<int64_t> sinShape = {1, 1, 1, 128};
    std::vector<int64_t> outShape = {1, 1, 1, 128};
    std::vector<int64_t> rotateShape = {128, 128};
    int64_t mode = 1;

    void* xDeviceAddr = nullptr;
    void* cosDeviceAddr = nullptr;
    void* sinDeviceAddr = nullptr;
    void* rotateDeviceAddr = nullptr;
    void* outDeviceAddr = nullptr;
    aclTensor* x = nullptr;
    aclTensor* cos = nullptr;
    aclTensor* sin = nullptr;
    aclTensor* rotate = nullptr;
    aclTensor* out = nullptr;

    std::vector<float> xHostData = {74, 54, 84, 125, 23, 78, 37, 72, 27, 98, 34, 107, 29, 23, 54, 60, 70, 49,
                                    119, 54, 29, 54, 41, 99, 27, 62, 5, 46, 108, 39, 24, 123, 33, 82, 6, 40, 88,
                                    24, 6, 116, 38, 119, 110, 5, 30, 79, 87, 18, 29, 100, 90, 24, 21, 93, 63, 68,
                                    34, 112, 119, 48, 74, 43, 85, 64, 14, 49, 128, 59, 18, 37, 123, 76, 14, 63, 10,
                                    39, 107, 124, 79, 16, 17, 76, 80, 47, 90, 41, 58, 82, 75, 80, 69, 37, 74, 36, 54,
                                    26, 32, 54, 13, 100, 105, 15, 13, 69, 122, 26, 94, 59, 29, 14, 60, 8, 24, 17, 45,
                                    33, 107, 122, 63, 111, 75, 128, 68, 31, 105, 6, 82, 99};
    std::vector<float> cosHostData = {41, 37, 17, 25, 49, 25, 22, 24, 110, 120, 107, 3, 82, 66, 75, 86, 85, 115, 110, 56, 52,
                                      39, 86, 23, 36, 71, 20, 73, 113, 25, 114, 56, 125, 80, 95, 82, 31, 63, 99, 62, 23, 55, 30,
                                      99, 42, 121, 15, 24, 97, 87, 81, 67, 43, 21, 13, 9, 33, 29, 117, 10, 114, 61, 98, 15, 78,
                                      108, 48, 97, 1, 3, 78, 109, 57, 46, 47, 56, 50, 66, 81, 77, 17, 128, 68, 121, 47, 91, 114,
                                      125, 51, 108, 31, 15, 47, 78, 109, 115, 113, 26, 53, 97, 1, 111, 103, 58, 106, 68, 11,
                                      104, 22, 79, 61, 127, 86, 39, 33, 123, 102, 39, 64, 41, 119, 120, 61, 29, 94, 68, 36, 12};
    std::vector<float> sinHostData = {46, 56, 56, 101, 66, 10, 96, 16, 86, 57, 102, 66, 12, 105, 76, 58, 90, 6, 79, 128, 126,
                                      82, 41, 3, 45, 7, 66, 4, 46, 22, 31, 26, 37, 63, 97, 84, 91, 90, 47, 77, 90, 34, 41, 83,
                                      91, 108, 120, 13, 90, 32, 85, 37, 119, 31, 51, 82, 122, 125, 7, 116, 121, 108, 38, 56,
                                      100, 20, 97, 119, 10, 4, 53, 13, 46, 82, 103, 119, 124, 80, 23, 67, 78, 56, 119, 122, 40,
                                      58, 128, 27, 30, 52, 71, 42, 123, 69, 4, 5, 116, 97, 38, 107, 8, 4, 65, 120, 40, 22, 60,
                                      44, 48, 66, 68, 125, 4, 93, 112, 112, 113, 90, 94, 23, 104, 39, 85, 84, 64, 128, 96, 119};
    std::vector<float> rotateHostData = get_interleave_matrix(128);
    std::vector<float> outHostData(128, 0);

    // Create the x aclTensor.
    ret = CreateAclTensor(xHostData, xShape, &xDeviceAddr, aclDataType::ACL_FLOAT, &x);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create the cos aclTensor.
    ret = CreateAclTensor(cosHostData, cosShape, &cosDeviceAddr, aclDataType::ACL_FLOAT, &cos);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create the sin aclTensor.
    ret = CreateAclTensor(sinHostData, sinShape, &sinDeviceAddr, aclDataType::ACL_FLOAT, &sin);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create the rotate aclTensor.
    ret = CreateAclTensor(rotateHostData, rotateShape, &rotateDeviceAddr, aclDataType::ACL_FLOAT, &rotate);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create the out aclTensor.
    ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT, &out);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    // 3. Call the CANN operator library API. Replace with the specific API.
    uint64_t workspaceSize = 0;
    aclOpExecutor* executor;
    // Call the first-phase API of aclnnRotaryPositionEmbeddingV2.
    ret = aclnnRotaryPositionEmbeddingGetWorkspaceSizeV2(x, cos, sin, mode, rotate, out, &workspaceSize, &executor);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnRotaryPositionEmbeddingGetWorkspaceSizeV2 failed. ERROR: %d\n", ret); return ret);
    // Allocate device memory based on the workspaceSize computed 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 aclnnRotaryPositionEmbeddingV2.
    ret = aclnnRotaryPositionEmbeddingV2(workspaceAddr, workspaceSize, executor, stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnRotaryPositionEmbeddingV2 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 values and copy the results from the device side to the host side. Modify based on the specific API definition.
    auto size = GetShapeSize(outShape);
    std::vector<float> resultData(size, 0);
    ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(float),
                      ACL_MEMCPY_DEVICE_TO_HOST);
    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("result[%ld] is: %f\n", i, resultData[i]);
    }

    // 6. Release aclTensor and aclScalar. Modify based on the specific API definition.
    aclDestroyTensor(x);
    aclDestroyTensor(cos);
    aclDestroyTensor(sin);
    aclDestroyTensor(rotate);
    aclDestroyTensor(out);

    // 7. Release device resources.
    aclrtFree(xDeviceAddr);
    aclrtFree(cosDeviceAddr);
    aclrtFree(sinDeviceAddr);
    aclrtFree(rotateDeviceAddr);
    aclrtFree(outDeviceAddr);
    if (workspaceSize > 0) {
      aclrtFree(workspaceAddr);
    }
    aclrtDestroyStream(stream);
    aclrtResetDevice(deviceId);
    aclFinalize();

    return 0;
}
  • Example of generating the auxiliary matrix rotate:
import torch
import torch_npu

def get_interleave_matrix(n):
    matrix = torch.zeros(n, n, dtype=torch.bfloat16)
    for i in range(0, n, 2):
        matrix[i + 0, i + 1] = 1
        matrix[i + 1, i + 0] = -1
    return matrix

def get_half_matrix(n):
    matrix = torch.zeros(n, n, dtype=torch.bfloat16)
    half = n // 2
    matrix[:half, half:] = torch.eye(half)
    matrix[half:, :half] = -torch.eye(half)
    return matrix

def compose_2matrix(A, B):
    total_rows = A.size(0) + B.size(0)
    total_cols = A.size(1) + B.size(1)
    result = torch.zeros(total_rows, total_cols, dtype=torch.bfloat16)
    result[:A.size(0), :A.size(1)] = A
    b_row_start = A.size(0)
    b_col_start = A.size(1)
    result[b_row_start:b_row_start + B.size(0),
           b_col_start:b_col_start + B.size(1)] = B
    return result

def main():
    # interleave
    inter_mat_128 = get_interleave_matrix(128)
    inter_mat_64 = get_interleave_matrix(64)
    # interleave 2D
    inter_mat_128_64 = compose_2matrix(inter_mat_128, inter_mat_64)

    x = torch.rand(2, 2, 5, 128).npu()
    r1 = torch.rand(1, 2, 1, 128).npu()
    r2 = torch.rand(1, 2, 1, 128).npu()
    out = torch_npu.npu_rotary_mul(x, r1, r2, "interleave", inter_mat_128.npu())