Skip to content

asc_copy_gm2l1_dn2nz

产品支持情况

  • Ascend 950PR/Ascend 950DT:支持
  • Atlas A3 训练系列产品/Atlas A3 推理系列产品:不支持
  • Atlas A2 训练系列产品/Atlas A2 推理系列产品:不支持
  • Atlas 200I/500 A2 推理产品:不支持
  • Atlas 推理系列产品AI Core:不支持
  • Atlas 推理系列产品Vector Core:不支持
  • Atlas 训练系列产品:不支持

功能说明

头文件路径为:"c_api/cube_datamove/cube_datamove.h"

将数据从Global Memory搬运到L1 Buffer,并在搬运过程中进行DN到Nz格式的转换,使其满足后续矩阵计算对分形列连续性的要求。

进行DN2Nz格式搬运前,需先通过asc_set_gm2l1_nz_para接口配置DN2Nz搬运的目的Nz矩阵步长与DN矩阵搬运个数。

本接口仅在AIC上执行有效。

图1所示,DN到Nz的格式转换也等价于ND到Zn的格式转换,如果要进行ND到Zn的格式转换可采用此指令。

图1 DN2Nz与ND2Zn示意图

函数原型

模板原型(占位符形式)

C++
__aicore__ inline void asc_copy_gm2l1_dn2nz(__cbuf__ <dtype>* dst,
                                            __gm__ <dtype>* src,
                                            uint64_t loop1_src_stride,
                                            uint8_t l2_cache_ctl,
                                            uint16_t n_value,
                                            uint32_t d_value,
                                            uint64_t loop4_src_stride,
                                            bool smallc0_en)
__aicore__ inline void asc_copy_gm2l1_dn2nz_sync(__cbuf__ <dtype>* dst,
                                                 __gm__ <dtype>* src,
                                                 uint64_t loop1_src_stride,
                                                 uint8_t l2_cache_ctl,
                                                 uint16_t n_value,
                                                 uint32_t d_value,
                                                 uint64_t loop4_src_stride,
                                                 bool smallc0_en)

dtype支持的数据类型

dtype支持的数据类型为int8_tuint8_thifloat8_tfp8_e5m2_tfp8_e4m3fn_tint16_tuint16_thalfbfloat16_tint32_tuint32_tfloat

典型示例

C++
// 示例:half类型的DN到Nz格式搬运。
__aicore__ inline void asc_copy_gm2l1_dn2nz(__cbuf__ half* dst,
                                            __gm__ half* src,
                                            uint64_t loop1_src_stride,
                                            uint8_t l2_cache_ctl,
                                            uint16_t n_value,
                                            uint32_t d_value,
                                            uint64_t loop4_src_stride,
                                            bool smallc0_en)

参数说明

表1 参数说明

参数名输入/输出描述
dst输出目的操作数,存储位置为L1 Buffer。目的地址需32字节对齐。
src输入源操作数,存储位置为GM。起始地址需要按照1字节对齐。
loop1_src_stride输入源操作数同一DN矩阵的相邻行起始地址间的偏移,单位字节。取值范围:[0, ]。
l2_cache_ctl输入配置输入的GM数据在L2 Cache中的管理策略。取值需为已定义的策略(0/1/2/4之一),其他值触发L2 Cache策略异常。取值说明见表2
n_value输入源矩阵中DN矩阵的列数,单位为元素。取值范围:[1, 65535]。
d_value输入源矩阵中DN矩阵的行数,单位为元素。取值范围:[1, ]。
loop4_src_stride输入源矩阵相邻DN矩阵起始地址间的偏移,单位为字节。取值范围:[0, ]。
  • 源矩阵DN矩阵个数为1时,此参数无意义,设置为0即可。
  • 源矩阵DN矩阵个数大于1时,当loop4_src_stride = 0时,表示重复搬出源矩阵的第一个DN矩阵。
smallc0_en输入SmallC0模式开关,仅当时可开启,否则导致搬运异常。
  • false:不开启SmallC0模式,按标准Nz格式的C0(32字节)排布搬运;
  • true:开启SmallC0模式,搬运过程中搬运的数据量不满足字节对齐时,硬件在目的矩阵中自动补0至字节对齐。

表2 l2_cache_ctl取值说明

取值模式含义
0Normal模式启用L2 Cache,并将分配的Cache Line标记为高替换优先级。
1Last模式• 启用L2 Cache,并将分配的Cache Line标记为低替换优先级。
Last模式暂不支持。
2Persistent模式• 启用L2 Cache。已存入L2 Cache中的数据可能被替换,若需确保特定GM的数据始终保留在L2 Cache中,可采用驻留模式。
• 被标记为驻留模式的Cache Line只能被其他同样标记为驻留模式的Cache Line替换。
Persistent模式暂不支持。
4Disable模式不启用L2 Cache,每次都直接从GM中读取,并保持已有Cache Line的状态不变。

返回值说明

流水类型

PIPE_MTE2

约束说明

通用约束

  • 本接口非AIC调用直接返回。
  • dst的起始地址要求32字节对齐(L1 Buffer对齐要求),否则会导致搬运异常。
  • src的起始地址要求1字节对齐(Global Memory对齐要求),否则会导致搬运异常。
  • 如果本指令与其他指令存在目的地址重叠,需要插入同步指令(asc_sync_notifyasc_sync_wait),保证多个指令串行化,防止出现异常数据。
  • L1 Buffer容量上限:L1 Buffer总容量512KB,dst偏移量与搬运大小之和不可越界,否则触发异常。

DN2Nz约束

  • 调用本接口前,需要先调用asc_set_gm2l1_nz_para接口配置DN2Nz搬运的目的Nz矩阵步长与DN矩阵搬运个数。
  • loop1_src_striden_valued_valueloop4_src_stride取值需确保在取值范围内,超出取值范围的值会被截断,导致搬运结果不符合预期。
  • smallc0_en设置为true时,,否则导致搬运异常。

调用示例

将代码保存为examples.asc后,可通过bisheng命令编译运行,其中--npu-arch参数需根据实际产品型号指定对应的NPU架构,具体产品与NPU架构的映射关系请参考__NPU_ARCH__

以Ascend 950PR/Ascend 950DT产品(对应NPU架构为dav-3510)为例,编译运行命令如下:

Bash
bisheng examples.asc -o main --npu-arch=dav-3510 && ./main

样例单核占用64KB L1 Buffer、32KB L0A Buffer、32KB L0B Buffer和64KB L0C Buffer。样例依次复用EVENT_ID0完成MTE2到MTE1、MTE1到M、M到FIX的同步。基础API同类场景可参考data_copy_gm2l1样例中的场景3。

C++
#include <cstdint>
#include <iostream>
#include <vector>
#include "c_api/asc_simd.h"
#include "acl/acl.h"

namespace {
constexpr uint32_t M = 128;
constexpr uint32_t K = 128;
constexpr uint32_t N = 128;
constexpr uint32_t ELEMENTS = M * N;
constexpr uint32_t CUBE_M = 16;
constexpr uint32_t CUBE_K = 16;
constexpr uint16_t HALF_ONE = 0x3c00;
constexpr uint16_t HALF_NEG_ONE = 0xbc00;

__global__ __cube__ void AscCopyGm2l1Dn2nzKernel(
    __gm__ uint16_t* a, __gm__ uint16_t* b, __gm__ float* output)
{
    asc_init();
    __cbuf__ half a_l1[M * K], b_l1[N * K];
    __ca__ half a_l0[M * K];
    __cb__ half b_l0[K * N];
    __cc__ float c_l0[ELEMENTS];

    // 输入A、B分别按[K, M]和[N, K]的DN格式存放,搬入后转换为Nz格式。
    asc_set_gm2l1_nz_para(1, 1, 128, 0);
    asc_copy_gm2l1_dn2nz(reinterpret_cast<__cbuf__ half*>(a_l1), reinterpret_cast<__gm__ half*>(a),
        M * sizeof(half), 0, M, K, 0, false);
    asc_set_gm2l1_nz_para(1, 1, 128, 0);
    asc_copy_gm2l1_dn2nz(reinterpret_cast<__cbuf__ half*>(b_l1), reinterpret_cast<__gm__ half*>(b),
        K * sizeof(half), 0, K, N, 0, false);
    asc_sync_notify(PIPE_MTE2, PIPE_MTE1, EVENT_ID0);
    asc_sync_wait(PIPE_MTE2, PIPE_MTE1, EVENT_ID0);

    // 将Nz矩阵分别搬入L0A Buffer和L0B Buffer,供矩阵乘计算使用。
    asc_copy_l12l0a(a_l0, a_l1, 0, 0, M / CUBE_M, K / CUBE_K,
        M / CUBE_M, M / CUBE_M);
    asc_copy_l12l0b_transpose(b_l0, b_l1, 0, 0, K / CUBE_M, N / CUBE_K,
        K / CUBE_M, N / CUBE_M);
    asc_sync_notify(PIPE_MTE1, PIPE_M, EVENT_ID0);
    asc_sync_wait(PIPE_MTE1, PIPE_M, EVENT_ID0);

    // 计算128 x 128矩阵乘,并将结果从L0C Buffer搬回Global Memory。
    asc_mmad(c_l0, a_l0, b_l0, M, K, N, 0, false, false, true);
    asc_sync_notify(PIPE_M, PIPE_FIX, EVENT_ID0);
    asc_sync_wait(PIPE_M, PIPE_FIX, EVENT_ID0);
    asc_set_l0c_copy_nz_para(1, 0, 0);
    asc_copy_l0c2gm(output, c_l0, N, M, N, M, 0, 0, 0,
        static_cast<uint64_t>(QuantMode_t::NoQuant), 0, false, true,
        static_cast<uint64_t>(QuantMode_post::NoConv), 0, false, 0, false, false, false, false);
    asc_sync_pipe(PIPE_FIX);
}

uint16_t ToHalfBits(int32_t value)
{
    return value > 0 ? HALF_ONE : (value < 0 ? HALF_NEG_ONE : 0);
}

template <typename T>
void PrintRow(const char* label, const std::vector<T>& data)
{
    std::cout << label << ':';
    for (uint32_t i = 0; i < 8; ++i) {
        std::cout << ' ' << +data[i];
    }
    std::cout << " ..." << std::endl;
}
} // namespace

int main()
{
    // 在GM中按DN格式存放A和B,即分别存放A、B的转置矩阵。
    std::vector<uint16_t> a(K * M), b(N * K);
    std::vector<float> output(ELEMENTS), golden(ELEMENTS);
    for (uint32_t row = 0; row < M; ++row) {
        for (uint32_t k = 0; k < K; ++k) {
            const int32_t value = static_cast<int32_t>((row + 2 * k) % 3) - 1;
            a[k * M + row] = ToHalfBits(value);
        }
    }
    for (uint32_t k = 0; k < K; ++k) {
        for (uint32_t col = 0; col < N; ++col) {
            const int32_t value = static_cast<int32_t>((2 * k + col) % 3) - 1;
            b[col * K + k] = ToHalfBits(value);
        }
    }
    for (uint32_t row = 0; row < M; ++row) {
        for (uint32_t col = 0; col < N; ++col) {
            int32_t sum = 0;
            for (uint32_t k = 0; k < K; ++k) {
                const int32_t a_value = static_cast<int32_t>((row + 2 * k) % 3) - 1;
                const int32_t b_value = static_cast<int32_t>((2 * k + col) % 3) - 1;
                sum += a_value * b_value;
            }
            golden[row * N + col] = static_cast<float>(sum);
        }
    }

    aclInit(nullptr);
    aclrtSetDevice(0);
    uint16_t *a_device = nullptr, *b_device = nullptr;
    float* output_device = nullptr;
    aclrtMalloc(reinterpret_cast<void**>(&a_device), a.size() * sizeof(uint16_t), ACL_MEM_MALLOC_HUGE_FIRST);
    aclrtMalloc(reinterpret_cast<void**>(&b_device), b.size() * sizeof(uint16_t), ACL_MEM_MALLOC_HUGE_FIRST);
    aclrtMalloc(reinterpret_cast<void**>(&output_device), output.size() * sizeof(float), ACL_MEM_MALLOC_HUGE_FIRST);
    aclrtMemcpy(a_device, a.size() * sizeof(uint16_t), a.data(), a.size() * sizeof(uint16_t),
        ACL_MEMCPY_HOST_TO_DEVICE);
    aclrtMemcpy(b_device, b.size() * sizeof(uint16_t), b.data(), b.size() * sizeof(uint16_t),
        ACL_MEMCPY_HOST_TO_DEVICE);
    AscCopyGm2l1Dn2nzKernel<<<1, 0>>>(a_device, b_device, output_device);
    aclrtSynchronizeDevice();
    aclrtMemcpy(output.data(), output.size() * sizeof(float), output_device,
        output.size() * sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST);

    PrintRow("Output row 0", output);
    PrintRow("Golden row 0", golden);
    const bool passed = output == golden;
    std::cout << (passed ? "[Success] asc_copy_gm2l1_dn2nz preserved the MMAD operands/result."
                         : "[Failed] asc_copy_gm2l1_dn2nz result mismatch.") << std::endl;

    aclrtFree(a_device);
    aclrtFree(b_device);
    aclrtFree(output_device);
    aclrtResetDevice(0);
    aclFinalize();
    return passed ? 0 : 1;
}

免责声明:本站内容由 asc-devkit 仓 master 分支自动编译生成,属于持续开发版本,可能存在缺陷,仅供预览与参考。如需稳定及商用资料,请查阅官方 昇腾社区