SiP 信号处理加速库 Strmm 算子实战三角矩阵乘法 C 调用、编译运行与源码解析【免费下载链接】sip本项目是CANN提供的一款高效、可靠的高性能信号处理算子加速库基于华为Ascend AI处理器专门为信号处理领域而设计。项目地址: https://gitcode.com/cann/sip本文基于 SiPAscend SiP 信号处理加速库仓库中example/A2/BLAS/strmm目录的官方 Demo 文档展开完整讲解单精度三角矩阵乘法算子asdBlasStrmm的功能定义、接口参数、可运行的 C 示例代码以及环境配置、库编译与 Demo 构建的完整操作流程并结合仓库源码剖析其底层实现、参数校验与调用链路帮助读者在 Atlas A2/A3 等昇腾平台上快速上手 Strmm 算子并完成工程化集成。1. Strmm 算子功能三角矩阵与一般矩阵的乘法Strmm是 SiP 提供的单精度 BLAS 算子其功能是将一个三角矩阵 A与一个一般矩阵 B相乘得到新矩阵 C计算结果为C被覆盖写即输出直接写回 C 对应的设备内存。其计算公式为$$ C \alpha \cdot op(A) \cdot B \quad \text{side ASDBLAS_SIDE_LEFT} $$ $$ C \alpha \cdot B \cdot op(A) \quad \text{side ASDBLAS_SIDE_RIGHT} $$其中各符号含义为A三角矩阵仅存储下三角或上三角部分由uplo指定B一般矩阵shape 为[m, n]alpha实数标量矩阵乘法系数op(A)对 A 的可选操作可以是A本身trans ASDBLAS_OP_N或其转置A^Ttrans ASDBLAS_OP_T。官方 API 文档中给出了一个直观的数值示例见 Strmm 接口文档输入下三角矩阵A [ 1, 0 ] [ 3, 4 ]一般矩阵B [ 1, 2 ] [ 3, 4 ]在side LEFT、uplo LOWER、trans N、diag N、n 2、lda ldb ldc 2、alpha 2.345的调用下输出 C 为C [ 2.3450, 4.6900 ] [ 35.1750, 51.5900 ]这正对应C 2.345 × A × B下三角存储A 的第一行上三角元素视为 0 参与乘法可直接用于验证自己的调用是否正确。1.1 产品支持情况根据 接口文档 与 Demo 文档支持Atlas A2 训练系列产品、Atlas A3 训练系列产品、Atlas 800I A2 推理产品、Atlas A3 推理系列产品不支持Ascend 950PR/Ascend 950DT、Atlas 200I/500 A2 推理产品、Atlas 推理系列产品、Atlas 训练系列产品。2. 接口签名与参数说明Strmm 算子由两个接口配合使用二者均声明于 blas_api.h// 初始化句柄对应的 Strmm 算子配置计划绑定 AspbStatus asdBlasMakeStrmmPlan(asdBlasHandle handle); // 执行单精度三角矩阵乘法 AspbStatus asdBlasStrmm( asdBlasHandle handle, asdBlasSideMode_t side, asdBlasFillMode_t uplo, asdBlasOperation_t trans, asdBlasDiagType_t diag, const int64_t m, const int64_t n, const float alpha, aclTensor* A, const int64_t lda, aclTensor* B, const int64_t ldb, aclTensor* C, const int64_t ldc);2.1 asdBlasMakeStrmmPlan 参数参数名输入/输出描述handleasdBlasHandle输入算子的句柄返回状态码具体参见 SiP返回码。2.2 asdBlasStrmm 参数参数名输入/输出描述handleasdBlasHandle输入算子的句柄sideasdBlasSideMode_t输入指定矩阵 A 是乘法左侧还是右侧ASDBLAS_SIDE_LEFT左侧、ASDBLAS_SIDE_RIGHT右侧uploasdBlasFillMode_t输入指定矩阵 A 的存储格式ASDBLAS_FILL_MODE_LOWER下三角、ASDBLAS_FILL_MODE_UPPER上三角transasdBlasOperation_t输入指定是否对 A 转置ASDBLAS_OP_N不转置、ASDBLAS_OP_T转置diagasdBlasDiagType_t输入指定是否假定 A 对角线元素为 1ASDBLAS_DIAG_NON_UNIT不假定、ASDBLAS_DIAG_UNIT假定mint64_t输入矩阵 B 和 C 的行数nint64_t输入矩阵 B 和 C 的列数alphafloat输入公式中的乘法系数AaclTensor*输入对应公式中的 A。数据类型支持 FLOAT32数据格式支持 ND当 A 为乘法左矩阵时 shape 为[m, m]为右矩阵时为[n, n]ldaint64_t输入张量 A 中元素的间隔当前约束side 为 LEFT 时为 mside 为 RIGHT 时为 nBaclTensor*输入对应公式中的 B。FLOAT32、NDshape 为[m, n]ldbint64_t输入张量 B 中元素的间隔当前约束为 mCaclTensor*输出对应公式中的 C。FLOAT32、NDshape 为[m, n]ldcint64_t输入张量 C 中元素的间隔当前约束为 m返回状态码具体参见 SiP返回码。2.3 约束说明输入的元素个数m、n当前覆盖支持[1, 8193]当side ASDBLAS_SIDE_LEFT时算子输入 shape 为[m, m]、[m, n]输出 shape 为[m, n]当side ASDBLAS_SIDE_RIGHT时算子输入 shape 为[n, n]、[m, n]输出 shape 为[m, n]算子实际计算时不支持 ND 高维度运算不支持维度 ≥ 3 的运算从源码实现看见第 5 节当前版本对diag仅接受ASDBLAS_DIAG_NON_UNIT传入ASDBLAS_DIAG_UNIT会返回参数错误。3. C Demo 完整实现从数据准备到结果回读example/A2/BLAS/strmm目录下的 example_strmm.cpp 提供了最小化的可运行示例其参数配置为side ASDBLAS_SIDE_LEFT、uplo ASDBLAS_FILL_MODE_LOWER、trans ASDBLAS_OP_N、diag ASDBLAS_DIAG_NON_UNIT、m n 5、alpha 1.0、lda ldb ldc m。示例输入矩阵 A 每行为行下标值下三角存储形式、B 每行为行下标值用于直观观察乘法结果。示例中生成的数据不代表实际场景可根据具体业务修改。完整示例代码如下与仓库文件一致注释已合并#include iostream #include vector #include asdsip.h #include acl/acl.h #include acl_meta.h using namespace AsdSip; #define ASD_STATUS_CHECK(err) \ do { \ AsdSip::AspbStatus err_ (err); \ if (err_ ! AsdSip::ErrorType::ACL_SUCCESS) { \ std::cout Execute failed. std::endl; \ return -1; \ } \ } while (0) #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::vectorint64_t shape) { int64_t shapeSize 1; for (auto i : shape) { shapeSize * i; } return shapeSize; } int Init(int32_t deviceId, aclrtStream* stream) { // 固定写法acl初始化 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::vectorT hostData, const std::vectorint64_t shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size GetShapeSize(shape) * sizeof(T); // 调用aclrtMalloc申请device侧内存 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); // 调用aclrtMemcpy将host侧数据复制到device侧内存上 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); // 计算连续tensor的strides std::vectorint64_t strides(shape.size(), 1); for (int64_t i shape.size() - 2; i 0; i--) { strides[i] shape[i 1] * strides[i 1]; } // 调用aclCreateTensor接口创建aclTensor *tensor aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main(int argc, char** argv) { int deviceId 0; aclrtStream stream; auto ret Init(deviceId, stream); CHECK_RET(ret ::ACL_SUCCESS, LOG_PRINT(Init acl failed. ERROR: %d\n, ret); return ret); asdBlasSideMode_t side asdBlasSideMode_t::ASDBLAS_SIDE_LEFT; asdBlasFillMode_t uplo asdBlasFillMode_t::ASDBLAS_FILL_MODE_LOWER; asdBlasOperation_t trans asdBlasOperation_t::ASDBLAS_OP_N; asdBlasDiagType_t diag asdBlasDiagType_t::ASDBLAS_DIAG_NON_UNIT; const int64_t m 5; const int64_t n 5; float alpha 1.0; int64_t lda m; int64_t ldb m; int64_t ldc m; // 构造输入 A每行填充为行下标 i const int64_t tensorASize m * m; std::vectorfloat tensorInAData(tensorASize, 0.0); for (int64_t i 0; i m; i) { for (int64_t j 0; j m; j) { tensorInAData[m * i j] i; } } // 构造输入 B每行填充为行下标 i const int64_t tensorBSize m * n; std::vectorfloat tensorInBData(tensorBSize, 0.0); for (int64_t i 0; i m; i) { for (int64_t j 0; j n; j) { tensorInBData[n * i j] i; } } const int64_t tensorCSize m * n; std::vectorfloat tensorCData(tensorCSize, 0.0); // 打印侧信息side/uplo/trans/diag 的枚举值与输入矩阵便于结果比对 std::cout side static_castint32_t(side) std::endl; std::cout uplo static_castint32_t(uplo) std::endl; std::cout trans static_castint32_t(trans) std::endl; std::cout diag static_castint32_t(diag) std::endl; std::cout ------- input A ------- std::endl; for (int64_t i 0; i m; i) { for (int64_t j 0; j m; j) std::cout tensorInAData[i * m j] ; std::cout std::endl; } std::cout ------- input B ------- std::endl; for (int64_t i 0; i m; i) { for (int64_t j 0; j n; j) std::cout tensorInBData[i * n j] ; std::cout std::endl; } std::vectorint64_t aShape {tensorASize}; std::vectorint64_t bShape {tensorBSize}; std::vectorint64_t cShape {tensorCSize}; aclTensor* inputA nullptr; aclTensor* inputB nullptr; aclTensor* outputC nullptr; void* inputADeviceAddr nullptr; void* inputBDeviceAddr nullptr; void* outputCDeviceAddr nullptr; // 三个张量均为 FLOAT32 类型 ret CreateAclTensor(tensorInAData, aShape, inputADeviceAddr, aclDataType::ACL_FLOAT, inputA); CHECK_RET(ret ::ACL_SUCCESS, return ret); ret CreateAclTensor(tensorInBData, bShape, inputBDeviceAddr, aclDataType::ACL_FLOAT, inputB); CHECK_RET(ret ::ACL_SUCCESS, return ret); ret CreateAclTensor(tensorCData, cShape, outputCDeviceAddr, aclDataType::ACL_FLOAT, outputC); CHECK_RET(ret ::ACL_SUCCESS, return ret); // ---- SiP 算子调用标准流程Create → MakePlan → Workspace → SetStream → 计算 → Sync → Destroy ---- asdBlasHandle handle; asdBlasCreate(handle); size_t lwork 0; void* buffer nullptr; asdBlasMakeStrmmPlan(handle); asdBlasGetWorkspaceSize(handle, lwork); std::cout lwork lwork std::endl; if (lwork 0) { ret aclrtMalloc(buffer, static_castint64_t(lwork), ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ::ACL_SUCCESS, LOG_PRINT(allocate workspace failed. ERROR: %d\n, ret); return ret); } asdBlasSetWorkspace(handle, buffer); asdBlasSetStream(handle, stream); ASD_STATUS_CHECK( asdBlasStrmm(handle, side, uplo, trans, diag, m, n, alpha, inputA, lda, inputB, ldb, outputC, ldc)); asdBlasSynchronize(handle); asdBlasDestroy(handle); // 结果回读到 host 并打印 ret aclrtMemcpy(tensorCData.data(), tensorCSize * sizeof(float), outputCDeviceAddr, tensorCSize * 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); std::cout ------- output C ------- std::endl; for (int64_t i 0; i m; i) { for (int64_t j 0; j n; j) { std::cout tensorCData[i * n j] ; } std::cout std::endl; } std::cout Execute successfully. std::endl; // 资源释放 aclDestroyTensor(inputA); aclDestroyTensor(inputB); aclDestroyTensor(outputC); aclrtFree(inputADeviceAddr); aclrtFree(inputBDeviceAddr); aclrtFree(outputCDeviceAddr); aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例代码的执行脉络可归纳为四个阶段ACL 初始化aclInit→aclrtSetDevice→aclrtCreateStream创建流用于后续算子执行数据上卡CreateAclTensor封装了aclrtMalloc设备内存申请aclrtMemcpyHost→Device 拷贝aclCreateTensorND 格式、ACL_FLOAT类型张量构造注意示例按连续布局自行计算 strides算子调用遵循 SiP BLAS 的标准调用序列asdBlasCreate→asdBlasMakeStrmmPlan→asdBlasGetWorkspaceSize如需则分配工作区→asdBlasSetWorkspace→asdBlasSetStream→asdBlasStrmm→asdBlasSynchronize→asdBlasDestroy结果回读与清理aclrtMemcpy将 C 从设备端拷回主机打印随后销毁张量、释放显存、结束设备与 ACL 会话。需要强调该样例旨在提供快速上手、开发和调试算子的最小化实现不推荐直接将示例代码作为业务代码用于生产环境。4. 环境配置、编译与运行4.1 配置 CANN 环境变量source [CANN安装路径]/set_env.sh # 默认路径 source /usr/local/Ascend/ascend-toolkit/set_env.sh4.2 编译 SiP 加速库进入 SiP 仓库根目录执行编译并设置加速库环境变量cd ${SiP_root_path} bash build.sh source output/set_env.sh特别说明上述编译方式仅支持以 git 方式下载的加速库zip 压缩包下载的加速库不支持该编译方式编译过程需要联网下载依赖库编译环境必须联网该过程包含两个步骤获取并编译ascend-boost-comm昇腾分布式通信加速库组件以及编译信号处理加速库本身。更多命令说明可参考 build.sh 与 编译与构建文档。编译成功后source output/set_env.sh会设置ASDSIP_HOME_PATH以及其他相关环境变量指向 SiP 输出目录后续示例构建脚本依赖该变量。4.3 构建并运行 Demo进入示例所在目录执行其构建脚本cd ${示例所在目录} # 即 example/A2/BLAS/strmm bash build.shbuild.sh 的实际行为是先校验ASDSIP_HOME_PATH环境变量已设置且目录存在兼容以latest/latest/结尾的路径将其加入LD_LIBRARY_PATH然后使用g将example_strmm.cpp编译为可执行文件example并立即运行运行完成后删除该可执行文件。其核心编译命令为g example_strmm.cpp \ -I${ASCEND_HOME_PATH}/include/aclnn \ -I${ASCEND_HOME_PATH}/include \ -L${ASCEND_HOME_PATH}/lib64/ -lascendcl -lopapi -lnnopbase \ -I${ASDSIP_HOME_PATH}/include \ -L${ASDSIP_HOME_PATH}/lib -lmki \ -L${ASDSIP_HOME_PATH}/lib -lasdsip \ -L${ASDSIP_HOME_PATH}/lib -lasdsip_core \ -L${ASDSIP_HOME_PATH}/lib -lasdsip_host \ -o example从链接项可以看出示例依赖 CANN 侧的ascendcl/opapi/nnopbase以及 SiP 侧的mki计算内核接口、asdsip对外 API 层、asdsip_core算子核心实现与asdsip_hostHost 端支持等库这与仓库的分层结构相吻合。5. 源码级实现剖析asdBlasStrmm 的校验、参数映射与内核调度对外 API 的实现位于 core/blas/strmm.cpp阅读该文件可以确认示例中每个参数在库内部是如何被消费与校验的。5.1 计划绑定asdBlasMakeStrmmPlanasdBlasMakeStrmmPlan负责把BlasStrmmPlan实例绑定到 handle 上计划对象定义见 BlasStrmmPlan.cpp校验 handle 非空并防止重复绑定若该 handle 已绑定过计划将返回ACL_ERROR_INVALID_PARAM源码注释指明这是为了避免重复初始化导致的静默失败通过BlasPlanCache::MakePlan将BlasStrmmPlan存入计划缓存随后MarkInitialized标记计划就绪。因此示例中asdBlasMakeStrmmPlan(handle)必须在asdBlasStrmm之前调用一次且不能对同一 handle 调用两次。5.2 asdBlasStrmm 的执行流程asdBlasStrmm的完整处理顺序为加锁持有blas_mtx互斥锁保证多算子共用 handle 场景下的线程安全计划存在性检查通过BlasPlanCache::doesPlanExist(handle)确认已执行过asdBlasMakeStrmmPlan否则返回内部错误dtype 校验StrmmDtypeCheck强制 A、B、C 均为ACL_FLOATfloat32否则返回ACL_ERROR_UNSUPPORTED_DATA_TYPE——这与接口文档“数据类型支持 FLOAT32”的约束一致shape 校验StrmmShapeCheck检查m、n在(0, UINT32_MAX]区间内并按 side 计算 A 的元素数LEFT 为m*mRIGHT 为n*nB、C 元素数必须为m*n不匹配则返回ACL_ERROR_OP_INPUT_NOT_MATCHdiag 校验当前仅支持ASDBLAS_DIAG_NON_UNIT传入ASDBLAS_DIAG_UNIT直接返回ACL_ERROR_INVALID_PARAMld 检查lda/ldb/ldc 0时记录 INFO 日志当前约束下 ld 取 m 或 n参数映射将枚举参数编码为内核可识别的整型参数并按 side/uplo/trans 的组合计算真实矩阵乘尺寸sideIntLEFT → 0RIGHT → 1uploIntUPPER → 1LOWER → 0LEFT 情形realM mrealK mtransLeft (trans OP_N) ? 0 : 1RIGHT 情形realK ntransRight (trans OP_N) ? 0 : 1上三角uploInt 1时额外置lessFlag标记case 2/3下三角不置位另外ASDBLAS_OP_C共轭转置在单精度场景下会被归一化为ASDBLAS_OP_T处理。内核调度组装OpDescopName 为StrmmOperation与OpParam::Strmm参数结构按 side 决定输入张量顺序LEFT 为 [A, B]RIGHT 为 [B, A]输出为 [C]最终通过RunAsdOpsV2在计划绑定的 stream 上执行 AscendC 内核工作区使用plan.GetWorkspace()即第 4 节中通过asdBlasSetWorkspace设置的缓冲区异常处理dynamic_cast失败计划类型不匹配时捕获bad_cast并返回内部错误。这一结构说明asdBlasStrmm本质上是一层带完整参数校验的 Host API把 BLAS 语义转换为StrmmOperation内核的执行描述实际的三角矩阵乘法计算由编译进asdsip_core的 AscendC 算子完成。5.3 单元测试佐证test_strmm.cpp 中包含一个针对 shape 校验的负向用例issue #133构造元素数为m*n - 1的输出张量 C调用asdBlasStrmm时应返回ACL_ERROR_OP_INPUT_NOT_MATCH而不是让内核越界写。该测试同时验证了asdBlasCreate→asdBlasMakeStrmmPlan→asdBlasStrmm的标准调用序列与aclTensor的[m, m]、[m, n]二维 shape 构造方式可作为集成时排查参数错误的参考。6. 使用注意事项与常见问题shape 以元素总数传入设备端但接口语义为二维示例中aShape {tensorASize}等将张量按一维总长度创建ND 格式下元素数满足即可通过校验但m、n语义仍是二维矩阵的行数列数lda/ldb/ldc需按文档约束分别取 mA 为左侧时/ nA 为右侧时/ m / m仅支持单精度A、B、C 必须全部为float32混用 double 或半精度会在 Host 校验阶段被拒绝diag 当前仅支持 NON_UNIT若算法上需要单位对角unit diagonal场景需自行在对角线上填充 1 并以 NON_UNIT 调用不支持三维及以上 ND 张量实际计算要求张量为二维或元素总数合法的展平一维布局计划一次性绑定同一 handle 重复调用asdBlasMakeStrmmPlan会报错多算子复用同一 handle 时需注意 Plan 的互斥关系源码中dynamic_cast到BlasStrmmPlan失败即返回内部错误工作区asdBlasGetWorkspaceSize返回 0 时无需分配但asdBlasSetWorkspace仍需按示例流程调用以保证计划状态完整运行环境示例与算子均要求已配置 CANN 环境变量、已完成 SiP 库编译ASDSIP_HOME_PATH可用并在支持的产品Atlas A2/A3 系列训练/推理产品、Atlas 800I A2 等上运行结果验证调试阶段可参考第 1 节中官方文档给出的数值小例alpha 2.345 的 2×2 案例将本地输出与其比对快速判断 side/uplo/trans 组合是否符合预期。7. 小结SiP 的 Strmm 算子以“计划 句柄”的调用模型暴露了单精度三角矩阵乘法能力asdBlasMakeStrmmPlan完成一次性的计划绑定asdBlasStrmm在 Host 侧完成 dtype/shape/diag 等严格校验后将 side、uplo、trans、diag 语义映射为StrmmOperation内核参数并调度执行。仓库中 Demo 文档、可运行示例、构建脚本、Host 实现 与单元测试 共同构成了一条从编译、调用到结果验证的完整路径读者可按第 4 节的流程在本机完成一次最小化验证再依据第 5 节的参数映射规则将算子嵌入实际信号处理流水线。【免费下载链接】sip本项目是CANN提供的一款高效、可靠的高性能信号处理算子加速库基于华为Ascend AI处理器专门为信号处理领域而设计。项目地址: https://gitcode.com/cann/sip创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考