
昇腾C代码是显式管理数据搬运和流水线的而CUDA代码更像是为每个线程独立编写逻辑硬件自动完成并行。对比同样做 C A B 的向量加法差异如下1. 昇腾 SIMD 风格 (基于 Ascend C)在昇腾上你是在为一个AI Core编程关注的是如何把数据切成小块高效地喂给SIMD流水线。Single Instruction, Multiple Data单指令多数据流显式流水线代码被划分为 CopyIn搬入、Compute计算、CopyOut搬出三个阶段使用 TQue 队列和 TPipe 内存管理来实现流水线并行。核心指令调用 Add(zLocal, xLocal, yLocal, tileLength) 完成向量相加tileLength 代表这一批处理的数据量。数据搬运数据要搬进 LocalTensor 才能计算计算完再主动搬回显存。特点这种风格对程序员要求更高但能榨干硬件在特定任务上的能效。以下是基于Ascend C的完整核函数Kernel写法核心是管理数据流水线每个物理核心上只能运行一个thread.// 文件名: add_custom.cpp #include kernel_operator.h // 定义核函数 extern C __global__ __aicore__ void add_custom(__gm__ float* a, __gm__ float* b, __gm__ float* c) { // 1. 获取当前核的索引和总核数用于数据切分 uint32_t blockIdx AscendC::GetBlockIdx(); uint32_t blockNum AscendC::GetBlockNum(); // 2. 初始化流水线管理对象 (TPipe) AscendC::TPipe pipe; // 声明内存块用于存放数据 (UB: Unified Buffer, 片上存储) AscendC::TQueAscendC::QuePosition::VECIN, 1 inQueueA, inQueueB; // 输入队列 AscendC::TQueAscendC::QuePosition::VECOUT, 1 outQueueC; // 输出队列 pipe.InitBuffer(inQueueA, 256 * sizeof(float)); // 分配单次处理256个float pipe.InitBuffer(inQueueB, 256 * sizeof(float)); pipe.InitBuffer(outQueueC, 256 * sizeof(float)); // 3. 循环Tiling分块处理整段数据 uint32_t totalLength N; // 假设全局N已知 uint32_t perBlockLength totalLength / blockNum; uint32_t startIdx blockIdx * perBlockLength; uint32_t endIdx (blockIdx 1) * perBlockLength; for (uint32_t i startIdx; i endIdx; i 256) { // 4. 搬运数据: 从全局内存(GM)搬入片上内存(UB) AscendC::LocalTensorfloat aLocal inQueueA.AllocTensorfloat(); AscendC::LocalTensorfloat bLocal inQueueB.AllocTensorfloat(); AscendC::DataCopy(aLocal, a i, 256); AscendC::DataCopy(bLocal, b i, 256); // 5. 【核心SIMD计算】一条指令同时计算256个数 AscendC::LocalTensorfloat cLocal outQueueC.AllocTensorfloat(); AscendC::Add(cLocal, aLocal, bLocal, 256); // 硬件执行向量加法 // 6. 搬运结果: 从片上内存搬回全局内存 AscendC::DataCopy(c i, cLocal, 256); // 7. 释放资源 inQueueA.FreeTensor(aLocal); inQueueB.FreeTensor(bLocal); outQueueC.FreeTensor(cLocal); } }2. NVIDIA SIMT 风格 (基于 CUDA)你是在为海量并发线程编程每个线程只处理一个元素硬件负责将它们组织成Warp32线程一组协同执行。SIMT(Single Instruction, Multiple Threads单指令多线程线程即核心一个线程只负责计算 C[tid] A[tid] B[tid]。通过内置变量 threadIdx 和 blockIdx 找到自己是哪个“工人”。计算方式硬件将大量线程组成 Warp以32个线程为一组执行相同的指令处理不同的数据这就是SIMT的核心。数据搬运调用 cudaMemcpy 在启动内核前搬运整块数据核函数直接读写全局内存。特点编程模型更通用灵活对开发者更友好但在部分场景下能效不如专门优化的SIMD。这是标准的CUDA核函数核心是利用线程ID处理对应元素。// 文件名: add_kernel.cu #include cuda_runtime.h // 核函数定义__global__表示在设备端执行 __global__ void add_kernel(const float* a, const float* b, float* c, int N) { // 1. 计算当前线程负责处理的全局索引, 全局线程ID计算 int idx blockIdx.x * blockDim.x threadIdx.x; // ↑ ↑ ↑ // Block索引 Block大小 Thread索引 // 2. 步长 (用于处理超出线程总数的情况) int stride blockDim.x * gridDim.x; // ↑ ↑ // Block大小(多少个thread) Grid大小(多少个Block) // 3. 循环处理每个线程处理多个元素 (Grid Stride Loop) for (int i idx; i N; i stride) { // 4. 【核心SIMT计算】每个线程执行一次标量加法 // 硬件以Warp(32线程)为单位执行相同指令处理不同数据 c[i] a[i] b[i]; } } // 宿主端调用代码 (Host侧) int main() { int N 1 20; // 1M个元素 size_t bytes N * sizeof(float); // ... (分配内存cudaMalloc、拷贝数据cudaMemcpy省略) // 启动内核: 配置网格和线程块 int threadsPerBlock 256; int blocksPerGrid (N threadsPerBlock - 1) / threadsPerBlock; add_kernelblocksPerGrid, threadsPerBlock(d_a, d_b, d_c, N); // ... (拷回结果cudaMemcpy、释放内存) return 0; }CUDA编程模型中threadIdx.x、blockIdx.x、blockDim.x、gridDim.x是CUDA运行时环境在启动内核时自动提供的内置变量编译器自动设置这些变量。变量值示例threadIdx.x当前线程在Block中的位置0, 1, 2, ..., 255blockIdx.x当前Block在Grid中的位置0, 1, 2, ..., 1023blockDim.x每个Block的线程数256你设置的gridDim.xGrid中的Block总数1024你设置的Grid、Block、Thread、Warp对应的概念这是CUDA编程的基础。层次软件概念硬件概念通信范围同步成本Grid所有Block的集合整个GPU全局同步极慢需要核函数结束Block线程组SM流多处理器共享内存 同步__syncthreads()中等Thread单个线程CUDA Core寄存器/局部内存无独立执行Warp32个连续线程隐形Warp调度单元洗牌指令__shfl零延迟同一Warp内3.昇腾与英伟达对比维度昇腾 (Ascend C)NVIDIA (CUDA)并行模式数据并行 流水并行代码描述的是“搬运一块-计算一块-搬出一块”的流水线。线程并行代码描述的是“一个线程负责一个或几个元素”的逻辑。核心计算AscendC::Add(... , 256)一条指令操作256个数据。c[i] a[i] b[i]一条指令只操作1个数据由硬件并行执行。数据管理显式管理必须使用DataCopy搬入/搬出LocalTensor控制片上存储。隐式管理直接读写全局内存指针由L1/L2缓存辅助。内存模型分层手动控制显存(GM) - 片上(UB) - 计算单元。统一内存模型虽然也有共享内存但基础计算直接面向全局内存。单次处理量固定块大小如256由代码定义。单个元素由threadIdx定位。简单来说Ascend C代码在解决“如何高效搬运数据”而CUDA代码在解决“如何分配任务给线程”。前者类似编排工厂流水线后者类似调动工人团队。