aclnnMhcPreSinkhorn

产品支持情况

产品 是否支持
Ascend 950PR/Ascend 950DT x
Atlas A3 训练系列产品/Atlas A3 推理系列产品
Atlas A2 训练系列产品/Atlas A2 推理系列产品
Atlas 200I/500 A2 推理产品 ×
Atlas 推理系列产品 ×
Atlas 训练系列产品 ×

功能说明

  • 接口功能:基于一系列计算得到MHC架构中hidden层的Hres′\mathbf{H}'_{\text{res}}Hpost\mathbf{H}_{\text{post}}投影矩阵以及Attention或MLP层的输入矩阵hin\mathbf{h}_{\text{in}}。对Hres′\mathbf{H}'_{\text{res}}矩阵执行Sinkhorn迭代归一化变换,最终得到双随机矩阵Hres\mathbf{H}_{\text{res}};支持输出中间计算结果,用于反向梯度计算。包括sigmoid计算之后的Hlpre\mathbf{H^{pre}_l}矩阵、xl′⃗\vec{x^{'}_{l}}φ\mathbf{\varphi}矩阵乘的结果,输入x的RmsNorm结果xl′⃗\mathbf{\vec{x^{'}_{l}}}、迭代过程中的中间归一化结果和normOut\mathbf{normOut}和求和结果sumOut\mathbf{sumOut}

  • 计算公式

    xl′⃗=11d∑dim⁡=−2,keepdim=Truexi2+ϵHlpre=αlpre⋅(xl′⃗φlpre)+blpreHlpost=αlpost⋅(xl′⃗φlpost)+blpostHlres=αlres⋅(xl′⃗φlres)+blresHlpre=σ(Hlpre)Hlpost=2σ(Hlpost)hin=xl⃗Hlpre\begin{aligned} \vec{x^{'}_{l}} &= \frac{1}{\sqrt{\frac{1}{d} \sum_{\dim=-2,\text{keepdim}=\text{True}} x_i^2 + \epsilon}}\\ H^{pre}_l &= \alpha^{pre}_{l} ·(\vec{x^{'}_{l}}\varphi^{pre}_{l}) + b^{pre}_{l}\\ H^{post}_l &= \alpha^{post}_{l} ·(\vec{x^{'}_{l}}\varphi^{post}_{l}) + b^{post}_{l}\\ H^{res}_l &= \alpha^{res}_{l} ·(\vec{x^{'}_{l}}\varphi^{res}_{l}) + b^{res}_{l}\\ H^{pre}_l &= \sigma (H^{pre}_{l})\\ H^{post}_l &= 2\sigma (H^{post}_{l})\\ h_{in} &=\vec{x_{l}}H^{pre}_l \end{aligned}

    • Hlres\mathbf{H^{res}_l}作为输入,Sinkhorn变换共执行numIters\mathbf{numIters}次迭代,迭代过程中生成中间归一化结果normOut[k]\mathbf{normOut}[k]和求和结果sumOut[k]\mathbf{sumOut}[k],最终输出最后一次迭代的normOut\mathbf{normOut}作为变换结果。

      第一次迭代(初始化):

      normOut[0]=softmax(Hlres,dim⁡=−1)+ϵ,sumOut[1]=∑dim⁡=−2,keepdim=TruenormOut[0]+ϵ,normOut[1]=normOut[0]sum_out[1],\begin{aligned} \mathbf{normOut}[0] &= \text{softmax}(\mathbf{H^{res}_l}, \dim=-1) + \epsilon, \\ \mathbf{sumOut}[1] &= \sum_{\dim=-2,\text{keepdim}=\text{True}} \mathbf{normOut}[0] + \epsilon, \\ \mathbf{normOut}[1] &= \frac{\mathbf{normOut}[0]}{\mathbf{sum\_out}[1]}, \\ \end{aligned}

      ii次迭代(i=1,2,…,(num_iters−1)i = 1, 2, \dots, \mathbf({num\_iters}-1)):

      sumOut[2i]=∑dim⁡=−1,keepdim=TruenormOut[2i−1]+ϵ,normOut[2i]=normOut[2i−1]sum_out[2i],sumOut[2i+1]=∑dim⁡=−2,keepdim=TruenormOut[2i]+ϵ,normOut[2i+1]=normOut[2i]sum_out[2i+1],\begin{aligned} \mathbf{sumOut}[2i] &= \sum_{\dim=-1,\text{keepdim}=\text{True}} \mathbf{normOut}[2i-1] + \epsilon, \\ \mathbf{normOut}[2i] &= \frac{\mathbf{normOut}[2i-1]}{\mathbf{sum\_out}[2i]}, \\ \mathbf{sumOut}[2i+1] &= \sum_{\dim=-2,\text{keepdim}=\text{True}} \mathbf{normOut}[2i] + \epsilon, \\ \mathbf{normOut}[2i+1] &= \frac{\mathbf{normOut}[2i]}{\mathbf{sum\_out}[2i+1]}, \\ \end{aligned}

    • 最终输出

    normOut[2×num_iters−1]\mathbf{normOut}[2 \times \mathbf{num\_iters} - 1]

    sumOut[2×num_iters−1]\mathbf{sumOut}[2 \times \mathbf{num\_iters} - 1]

    • 符号说明

      符号 含义
      x\mathbf{x} 输入张量(mHC层的Hres′\mathbf{H}'_{\text{res}}矩阵)
      ϵ\epsilon 防除零参数(对应入参eps
      softmax(⋅,dim⁡=−1)\text{softmax}(\cdot, \dim=-1) 在最后一维执行softmax归一化
      ∑dim⁡=d,keepdim=True\sum_{\dim=d,\text{keepdim}=\text{True}} 在指定维度dd上求和并保持维度
      normOut[k]\mathbf{normOut}[k] kk步归一化中间结果
      sumOut[k]\mathbf{sumOut}[k] kk步求和中间结果
      numIters\mathbf{numIters} 迭代次数(入参)

函数原型

每个算子分为两段式接口,必须先调用aclnnMhcPreSinkhornGetWorkspaceSize接口获取计算所需workspace大小以及包含了算子计算流程的执行器,再调用aclnnMhcPreSinkhorn执行实际计算。

aclnnStatus aclnnMhcPreSinkhornGetWorkspaceSize(
    const aclTensor *x,
    const aclTensor *phi,
    const aclTensor *alpha,
    const aclTensor *bias,
    int              hcMult,
    int              numIters,
    double           hcEps,
    double           normEps,
    bool             outFlag,
    aclTensor       *hin,
    aclTensor       *hPost,
    aclTensor       *hRes,
    aclTensor       *hPre,
    aclTensor       *hcBeforeNorm,
    aclTensor       *invRms,
    aclTensor       *sumOut,
    aclTensor       *normOut,
    uint64_t       **workspaceSize,
    aclOpExecutor  **executor)
aclnnStatus aclnnMhcPreSinkhorn(
    void          *workspace,
    uint64_t       workspaceSize,
    aclOpExecutor *executor,
    aclrtStream    stream)

aclnnMhcPreSinkhornGetWorkspaceSize

  • 参数说明

    参数名 输入/输出 描述 使用说明 数据类型 数据格式 维度(shape) 非连续Tensor
    x(aclTensor*) 输入 待计算数据,表示网络中mHC层的输入数据。 支持空Tensor。 BFLOAT16 ND (bs, seq_len, n, c)
    phi(aclTensor*) 输入 mHC的参数矩阵。 支持空Tensor。 FLOAT32 ND (n * n + 2 * n, n * c)
    alpha(aclTensor*) 输入 mHC的缩放参数。 支持空Tensor。 FLOAT32 ND (3)
    bias(aclTensor*) 输入 mHC的bias参数。 支持空Tensor。 FLOAT32 ND (n * n + 2 * n)
    hcMult(int64_t) 输入 残差流数量,HC维度大小。 当前仅支持4。 - - - -
    numIters(int64_t) 输入 表示sinkhorn算法迭代次数。 当前仅支持20。 INT32 - - -
    hcEps(double) 输入 $H_{pre}$的sigmoid后的eps参数。 建议值:1e-6。 - - - -
    normEps(double) 输入 RmsNorm的防除零参数。 建议值:1e-6。 - - - -
    needGrad(bool) 输入 是否需要输出额外属性。 建议值为true。 - - -
    hin(aclTensor*) 输出 输出的h_in作为Atten/MLP层的输入。 - BFLOAT16 ND (bs, seq_len, c) ×
    hPost(aclTensor*) 输出 输出的mHC的h_post变换矩阵。 - FLOAT32 ND (bs, seq_len, n) ×
    hRes(aclTensor*) 输出 输出的mHC的h_res变换矩阵。 - FLOAT32 ND (bs, seq_len, n * n) ×
    hPre(aclTensor*) 可选输出 需要反向时输出,做完sigmoid计算之后的hPre矩阵。 根据needGrad决定是否输出。 FLOAT32 ND (bs, seq_len, n) ×
    hcBeforeNorm(aclTensor*) 可选输出 需要反向时输出,x与phi矩阵乘的结果。 根据needGrad决定是否输出。 FLOAT32 ND (bs, seq_len, n*n + 2*n) ×
    invRms(aclTensor*) 可选输出 需要反向时输出,RmsNorm计算得到的1/r。 根据needGrad决定是否输出。 FLOAT32 ND (bs, seq_len, 1) ×
    sumOut(aclTensor*) 可选输出 需要反向时输出,每一次迭代的colSum/rowSum结果。 根据needGrad决定是否输出。 FLOAT32 ND (sk_iter_count * 2, bs, seq_len, n) ×
    normOut(aclTensor*) 可选输出 需要反向时输出,每一次colSum/rowSum迭代后的comb结果。 根据needGrad决定是否输出。 FLOAT32 ND (sk_iter_count * 2, bs, seq_len, n, n) ×
    workspaceSize(uint64_t**) 输出 返回需要在Device侧申请的workspace大小。 - - - - -
    executor(aclOpExecutor**) 输出 返回op执行器,包含了算子计算流程。 - - - - -
  • 返回值

    aclnnStatus:返回状态码,具体参见aclnn返回码

    第一段接口完成入参校验,出现以下场景时报错:

    返回值 错误码 描述
    ACLNN_ERR_PARAM_NULLPTR 161001 传入参数是必选输入,输出或者必选属性,且是空指针。
    ACLNN_ERR_PARAM_INVALID 161002 输入变量的数据类型和数据格式不在支持的范围内。
    numIters不为20。
    n值非4。
    ACLNN_ERR_RUNTIME_ERROR 361001 调用NPU Runtime接口申请内存/创建Tensor失败。

aclnnMhcPreSinkhorn

  • 参数说明

    参数名 输入/输出 描述
    workspace 输入 在Device侧申请的workspace内存地址。
    workspaceSize 输入 在Device侧申请的workspace大小,由第一段接口aclnnMhcPreSinkhornGetWorkspaceSize获取。
    executor 输入 op执行器,包含了算子计算流程。
    stream 输入 指定执行任务的Stream。
  • 返回值

    返回aclnnStatus状态码,具体参见aclnn返回码

约束说明

  • 确定性计算

    • aclnnMhcPreSinkhorn默认采用确定性实现,相同输入多次调用结果一致。
  • 规格约束

    规格项 规格 规格说明
    numIters 20 迭代次数超出该范围会返回参数无效错误。
    n 4 目前只支持4。
    C [1280, 1920, 2560] 目前只支持这三个数

调用示例

调用示例代码如下,仅供参考,具体编译和执行过程请参考编译与运行样例

#include <iostream>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_mhc_pre_sinkhorn.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)

// 计算Tensor形状对应的总元素数
int64_t GetShapeSize(const std::vector<int64_t>& shape) {
  int64_t size = 1;
  for (int64_t dim : shape) {
    size *= dim;
  }
  return size;
}

// 将Device侧Tensor数据拷贝到Host侧并打印
void PrintTensorData(const std::vector<int64_t>& shape, void* device_addr) {
  int64_t size = GetShapeSize(shape);
  std::vector<float> host_data(size, 0.0f);

  // Device -> Host 数据拷贝
  aclError ret = aclrtMemcpy(
      host_data.data(), size * sizeof(float),
      device_addr, size * sizeof(float),
      ACL_MEMCPY_DEVICE_TO_HOST
  );
  CHECK_RET(ret(ret == ACL_SUCCESS, 
            LOG_PRINT("Memcpy device to host failed, error: %d\n", ret); 
            return);

  // 打印前10个元素(示例)
  LOG_PRINT("Tensor data (first 10 elements): ");
  for (int i = 0; i < std::min((int64_t)10, size); ++i) {
    LOG_PRINT("%f ", host_data[i]);
  }
  LOG_PRINT("\n");
}

// 初始化AscendCL环境(Device/Context/Stream)
int InitAcl(int32_t device_id, aclrtContext& context, aclrtStream& stream) {
  // 1. 初始化ACL
  aclError ret = aclInit(nullptr);
  CHECK_RET(ret == ACL_SUCCESS, 
            LOG_PRINT("aclInit failed, error: %d\n", ret); 
            return -1);

  // 2. 设置Device
  ret = aclrtSetDevice(device_id);
  CHECK_RET(ret == ACL_SUCCESS, 
            LOG_PRINT("aclrtSetDevice failed, error: %d\n", ret); 
            return -1);

  // 3. 创建Context
  ret = aclrtCreateContext(&context, device_id);
  CHECK_RET(ret == ACL_SUCCESS, 
            LOG_PRINT("aclrtCreateContext failed, error: %d\n", ret); 
            return -1);

  // 4. 设置当前Context
  ret = aclrtSetCurrentContext(context);
  CHECK_RET(ret == ACL_SUCCESS, 
            LOG_PRINT("aclrtSetCurrentContext failed, error: %d\n", ret); 
            return -1);

  // 5. 创建Stream
  ret = aclrtCreateStream(&stream);
  CHECK_RET(ret == ACL_SUCCESS, 
            LOG_PRINT("aclrtCreateStream failed, error: %d\n", ret); 
            return -1);

  return 0;
}

// 创建Device侧aclTensor(含数据拷贝)
int CreateAclTensor(
    const std::vector<float>& host_data,
    const std::vector<int64_t>& shape,
    void*& device_addr,
    aclTensor*& tensor) {
  // 1. 计算内存大小
  int64_t size = GetShapeSize(shape) * sizeof(float);

  // 2. 申请Device侧内存
  aclError ret = aclrtMalloc(&device_addr, size, ACL_MEM_MALLOC_HUGE_FIRST);
  CHECK_RET(ret == ACL_SUCCESS, 
            LOG_PRINT("aclrtMalloc failed, error: %d\n", ret); 
            return -1);

  // 3. Host -> Device 数据拷贝
  ret = aclrtMemcpy(
      device_addr, size,
      host_data.data(), size,
      ACL_MEMCPY_HOST_TO_DEVICE
  );
  CHECK_RET(ret == ACL_SUCCESS, 
            LOG_PRINT("aclrtMemcpy failed, error: %d\n", ret); 
            return -1);

  // 4. 计算Tensor的strides(连续Tensor)
  std::vector<int64_t> strides(shape.size(), 1);
  for (int64_t i = shape.size() - 2; i >= 0; --i) {
    strides[i] = strides[i + 1] * shape[i + 1];
  }

  // 5. 创建aclTensor
  tensor = aclCreateTensor(
      shape.data(), shape.size(),
      ACL_FLOAT, strides.data(), 0,
      ACL_FORMAT_ND, shape.data(), shape.size(),
      device_addr
  );
  CHECK_RET(tensor != nullptr, 
            LOG_PRINT("aclCreateTensor failed\n"); 
            return -1);

  return 0;
}

int main() {
  // ========== 1. 初始化环境 ==========
  int32_t device_id = 0;  // 根据实际Device ID调整
  aclrtContext context = nullptr;
  aclrtStream stream = nullptr;

  int ret = InitAcl(device_id, context, stream);
  CHECK_RET(ret == 0, 
            LOG_PRINT("InitAcl failed, error: %d\n", ret); 
            return -1);

  // ========== 2. 构造输入/输出参数 ==========
  // 输入h_res的形状:B=1, S=1024, n=4 → (1024,4,4)(合并B*S为T=1024)
  std::vector<int64_t> h_res_shape = {1024, 4, 4};
  int64_t h_res_size = GetShapeSize(h_res_shape);
  std::vector<float> h_res_host_data(h_res_size, 1.0f);  // 初始化输入数据为1.0

  // 输出h_res_sinkhorn的形状与h_res一致
  std::vector<int64_t> output_shape = h_res_shape;
  void* output_device_addr = nullptr;
  aclTensor* output_tensor = nullptr;

  // 输入h_res的Device Tensor
  void* h_res_device_addr = nullptr;
  aclTensor* h_res_tensor = nullptr;
  ret = CreateAclTensor(h_res_host_data, h_res_shape, h_res_device_addr, h_res_tensor);
  CHECK_RET(ret == 0, 
            LOG_PRINT("Create h_res_tensor failed\n"); 
            return -1);

  // 输出h_res_sinkhorn的Device Tensor(仅申请内存,无初始数据)
  ret = aclrtMalloc(&output_device_addr, GetShapeSize(output_shape)*sizeof(float), ACL_MEM_MALLOC_HUGE_FIRST);
  CHECK_RET(ret == ACL_SUCCESS, 
            LOG_PRINT("Malloc output failed, error: %d\n", ret); 
            return -1);
  output_tensor = (aclCreateTensor(
      output_shape.data(), output_shape.size(),
      ACL_FLOAT, nullptr, 0, ACL_FORMAT_ND,
      output_shape.data(), output_shape.size(),
      output_device_addr
  );

  // MhcPreSinkhorn算子参数
  float eps = 1e-6f;       // 防除零参数
  int64_t num_iters = 20;  // 迭代次数
  int out_flag = 0;        // 输出标志位(当前版本仅支持0)

  // ========== 3. 调用第一段接口:获取Workspace大小 ==========
  uint64_t workspace_size = 0;
  aclOpExecutor* executor = nullptr;

  aclnnStatus aclnn_ret = aclnnMhcPreSinkhornGetWorkspaceSize(
      h_res_tensor,
      eps,
      num_iters,
      out_flag,
      output_tensor,
      nullptr,  // norm_out(outFlag=0时为nullptr)
      nullptr,  // sum_out(outFlag=0时为nullptr)
      &workspace_size,
      &executor
  );
  CHECK_RET(aclnn_ret == ACL_SUCCESS, 
            LOG_PRINT("aclnnMhcPreSinkhornGetWorkspaceSize failed, error: %d\n", aclnn_ret); 
            return -1);

  // ========== 4. 申请Workspace内存 ==========
  void* workspace_addr = nullptr;
  if (workspace_size > 0) {
    ret = aclrtMalloc(&workspace_addr, workspace_size, ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(ret == ACL_SUCCESS, 
              LOG_PRINT("aclrtMalloc workspace failed, error: %d\n", ret); 
              return -1);
  }

  // ========== 5. 调用第二段接口:执行MhcPreSinkhorn计算 ==========
  aclnn_ret = aclnnMhcPreSinkhorn(
      workspace_addr,
      workspace_size,
      executor,
      stream
  );
  CHECK_RET(aclnn_ret == ACL_SUCCESS, 
            LOG_PRINT("aclnnMhcPreSinkhorn failed, error: %d\n", aclnn_ret); 
            return -1);

  // ========== 6. 同步Stream并打印结果 ==========
  ret = aclrtSynchronizeStream(stream);
  CHECK_RET(ret == ACL_SUCCESS, 
            LOG_PRINT("aclrtSynchronizeStream failed, error: %d\n", ret); 
            return -1);

  LOG_PRINT("MhcPreSinkhorn compute success!\n");
  LOG_PRINT("Output tensor data: ");
  PrintTensorData(output_shape, output_device_addr);

  // ========== 7. 释放资源 ==========
  // 销毁Tensor
  aclDestroyTensor(h_res_tensor);
  aclDestroyTensor(output_tensor);

  // 释放Device内存
  aclrtFree(h_res_device_addr);
  aclrtFree(output_device_addr);
  if (workspace_size > 0) {
    aclrtFree(workspace_addr);
  }

  // 销毁Stream/Context,重置Device
  aclrtDestroyStream(stream);
  aclrtDestroyContext(context);
  aclrtResetDevice(device_id);
  aclFinalize();

  LOG_PRINT("All resources released successfully!\n");
  return 0;
}