4. 向量加法内核实现
任务目标
在寒武纪MLU上实现一个向量加法内核:输入两个长度为N的浮点数组A、B,计算C = A + B。内核在MLU上并行执行,并将结果与CPU逐元素计算结果对比,验证正确性。
环境与依赖
- 寒武纪MLU开发环境(含
cncc编译器、cnrt运行时) - 已设置
NEUWARE_HOME环境变量 - 单卡MLU设备(代码中默认使用设备0)
完整实现代码
// vector_add.cpp
#include <cnrt.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define N 1024
#define THREADS 256
// MLU端内核:向量加法
__global__ void vectorAddKernel(const float* a, const float* b, float* c, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
c[idx] = a[idx] + b[idx];
}
}
int main() {
// 1. 初始化MLU设备
cnrtInit(0);
cnrtDev_t dev;
cnrtGetDeviceHandle(&dev, 0);
cnrtSetCurrentDevice(dev);
// 2. 分配主机内存并初始化数据
float *h_a = (float*)malloc(N * sizeof(float));
float *h_b = (float*)malloc(N * sizeof(float));
float *h_c = (float*)malloc(N * sizeof(float));
float *h_ref = (float*)malloc(N * sizeof(float));
for (int i = 0; i < N; i++) {
h_a[i] = (float)(rand() % 100) / 10.0f;
h_b[i] = (float)(rand() % 100) / 10.0f;
}
// 3. 分配MLU设备内存
float *d_a, *d_b, *d_c;
cnrtMalloc(&d_a, N * sizeof(float));
cnrtMalloc(&d_b, N * sizeof(float));
cnrtMalloc(&d_c, N * sizeof(float));
// 4. 主机数据拷贝到设备
cnrtMemcpy(d_a, h_a, N * sizeof(float), CNRT_MEM_TRANS_DIR_HOST2DEV);
cnrtMemcpy(d_b, h_b, N * sizeof(float), CNRT_MEM_TRANS_DIR_HOST2DEV);
// 5. 配置内核启动参数并执行
cnrtFunctionType_t ktype = CNRT_FUNC_TYPE_BLOCK;
cnrtDim3_t dim;
dim.x = (N + THREADS - 1) / THREADS;
dim.y = 1;
dim.z = 1;
cnrtKernelParamsBuffer_t params;
cnrtKernelParamsBufferInit(¶ms);
cnrtKernelParamsBufferAddParam(¶ms, &d_a, sizeof(d_a));
cnrtKernelParamsBufferAddParam(¶ms, &d_b, sizeof(d_b));
cnrtKernelParamsBufferAddParam(¶ms, &d_c, sizeof(d_c));
cnrtKernelParamsBufferAddParam(¶ms, &N, sizeof(N));
cnrtQueue_t queue;
cnrtCreateQueue(&queue);
cnrtLaunchKernel(vectorAddKernel, dim, ¶ms, ktype, queue);
cnrtSyncQueue(queue);
// 6. 结果拷贝回主机
cnrtMemcpy(h_c, d_c, N * sizeof(float), CNRT_MEM_TRANS_DIR_DEV2HOST);
// 7. CPU参考计算
for (int i = 0; i < N; i++) {
h_ref[i] = h_a[i] + h_b[i];
}
// 8. 验证正确性
float max_err = 0.0f;
for (int i = 0; i < N; i++) {
float err = fabsf(h_c[i] - h_ref[i]);
if (err > max_err) max_err = err;
}
printf("Max error: %f\n", max_err);
if (max_err < 1e-5) {
printf("PASSED: MLU result matches CPU reference.\n");
} else {
printf("FAILED: MLU result deviates from CPU reference.\n");
}
// 9. 清理资源
cnrtKernelParamsBufferDestroy(¶ms);
cnrtDestroyQueue(queue);
cnrtFree(d_a); cnrtFree(d_b); cnrtFree(d_c);
free(h_a); free(h_b); free(h_c); free(h_ref);
cnrtDestroy();
return 0;
}
编译与运行
# 编译(生成MLU可执行文件)
cncc -o vector_add vector_add.cpp -I$NEUWARE_HOME/include -L$NEUWARE_HOME/lib64 -lcnrt
# 运行
./vector_add
验证方法与判定标准
- 程序输出
Max error值,表示MLU计算结果与CPU参考结果的最大误差。 - 若输出包含
PASSED且最大误差小于1e-5,则判定内核实现正确。 - 若输出
FAILED,检查设备内存分配是否成功、队列同步是否完成、数据拷贝方向是否正确。
关键说明
- 内核使用
blockIdx.x * blockDim.x + threadIdx.x计算全局索引,通过边界判断idx < n保证线程数不是N整数倍时仍安全。 - 本例使用
CNRT_FUNC_TYPE_BLOCK类型,即每个线程独立执行一个元素加法,是MLU上最基础的并行模式。 - 随机数使用
rand()生成,每次运行数据不同,但CPU与MLU使用相同输入,因此对比结果始终有效。