[AI][昇腾950]ND-DMA 详解
Ascend 950PR (3510) NDDMA 多维搬运参数填写教程
目标芯片:Ascend 950PR / DT(Atlas A5),
__NPU_ARCH__ == 3510
API 层级:c_api(asc_ndim_copy_gm2ub)+ basic_api(DataCopy+MultiCopyParams)
搬运方向:GM → UB(仅 AIV 核执行;)
支持维度:1 ~ 5 维
支持类型:int8/uint8/int16/uint16/half/bfloat16/int32/uint32/float + fp8(e4m3/e5m2/e8m0) + fp4x2(e1m2/e2m1);basic_api 额外支持 int64/uint64/double(仅常量 0 填充)
1. NDDMA 是什么
NDDMA(N-Dimensional DMA)是 3510 上专用的多维硬件搬运引擎,能在一次调用中完成最多 5 维张量的 GM→UB 搬运,并原生支持:
- 每维独立 stride(src/dst 分别设置,可做转置、广播、切片)
- 每维独立 pad(左/右各 0~255 元素)
- 两种填充模式:常量填充(constant fill)/ 邻边复制(nearest-value replicate)
- L2 cache 模式控制
- 32 KB NDDMA DataCache(多核共享 GM 时需
dci刷新)
相比传统 asc_copy_gm2ub(仅 2D burst 模式),NDDMA 省去了软件嵌套循环与多次 DMA 启动开销。
2. 两套 API 对照
| 维度 | c_api(低层) | basic_api(高层 C++) |
|---|---|---|
| 主搬运函数 | asc_ndim_copy_gm2ub(...) |
DataCopy<T, dim, config>(dst, src, params) |
| 参数传递 | 有状态:先调 setter 写 SPR 寄存器,再调 copy | 无状态:参数全在 MultiCopyParams 里一次性传入 |
| 维度声明 | 隐式(未用到的 loopN_size 填 1) |
显式模板参数 dim |
| 填充模式极性 | padding_mode=true 表示常量填充 |
isNearestValueMode=true 表示邻边复制(极性相反) |
| 填充值 | asc_set_ndim_pad_value(v)(全局 SPR) |
params.constantValue(按调用传入) |
| Stride | 5 次 asc_set_ndim_loopN_stride 调用 |
loopInfo.loopSrcStride[N] / loopDstStride[N] 数组 |
| Pad 计数 | dim0 在 copy 调用参数;dim1-4 在 asc_set_ndim_pad_count |
全部在 loopInfo.loopLpSize[N] / loopRpSize[N] |
| L2 cache | 调用参数 l2_cache_mode |
GlobalTensor::SetL2CacheHint(mode) 预设 |
| Cache 刷新 | asc_ndim_copy_dci() |
NdDmaDci() |
| 64 位类型 | ✗ 不支持 | ✓(int64/uint64/double,仅常量 0 填充) |
选择建议:新代码用 basic_api(无状态、类型安全、支持 b64);需要极致控制或与 c_api 代码混用时用 c_api。
3. 维度与索引约定
NDDMA 最多 5 维,索引 0 是最内层(lowest)维度,索引 dim-1 是最外层(highest)维度。
loop4 (outermost) ─┐
loop3 │
loop2 │ N 维张量
loop1 │
loop0 (innermost) ─┘
对于 2D 场景(如矩阵):loop0 = 行内元素(列方向),loop1 = 行间跳转(行方向)。
规则:
- 实际维度为
N时,loop{0..N-1}_size填真实元素数(≥1),loop{N..4}_size填1。 - 未使用的 stride setter 也建议显式调用
(0, 0),避免残留 SPR 状态污染。
4. 参数语义总表
4.1 每维参数
| 参数 | 单位 | 范围 | 含义 |
|---|---|---|---|
loopN_size |
元素 | [1, 2^20-1] |
该维实际搬运元素数(不含 pad) |
loopN_src_stride |
元素 | [0, 2^40-1] |
GM 中跳到下一元素的距离;0 = 广播(重读同一地址) |
loopN_dst_stride |
元素 | [0, 2^20-1] |
UB 中跳到下一元素的距离;0 = 覆写同一 UB 位置 |
loopN_lp_count |
元素 | [0, 255] |
该维左侧 pad 元素数 |
loopN_rp_count |
元素 | [0, 255] |
该维右侧 pad 元素数 |
4.2 全局参数
| 参数 | 类型 | 含义 |
|---|---|---|
padding_mode / isNearestValueMode |
bool | 填充策略 ) |
pad_value / constantValue |
同 T | 常量填充值(仅 padding_mode=true / isNearestValueMode=false 生效) |
l2_cache_mode |
enum | L2 缓存策略 |
4.3 地址对齐约束
- src 起始地址:按
sizeof(T)字节对齐 - dst 起始地址:32 字节对齐(硬性要求)
- 各维地址跨度累加不超过
2^40bit - 相邻维
dst_stride递增时,各维地址区间不得交错/重叠
5. 填充模式(两种极性,务必注意)
| 模式 | c_api 设置 | basic_api 设置 | 行为 |
|---|---|---|---|
| 常量填充 | padding_mode = true |
NdDmaConfig{isNearestValueMode = false} |
pad 区域填入 pad_value / constantValue |
| 邻边复制 | padding_mode = false |
NdDmaConfig{isNearestValueMode = true} |
pad 区域复制最近的源元素值(edge replicate);pad_value 被忽略 |
极性陷阱:c_api 的
padding_mode=true对应 basic_api 的isNearestValueMode=false。两者取反关系。混用两层 API 时最易出错。
填充值类型要求:
- 类型必须与
dst/src一致 - fp8 类型(e4m3/e5m2/e8m0)无专用重载,需先
reinterpret_cast为int8_t再调asc_set_ndim_pad_value - basic_api 的 b64 类型(int64/uint64/double)仅允许
constantValue = 0
6. L2 Cache 模式
asc_load_l2_cache_mode 枚举(include/c_api/utils/enum.h):
| 枚举值 | 数值 | 语义 | 推荐场景 |
|---|---|---|---|
NORMAL_FIRST_VICTIM |
0 | 缓存,标记为高替换优先级 | 通用默认 |
NORMAL_LAST_VICTIM |
1 | 缓存,标记为低替换优先级 | 数据将复用 |
NORMAL_PERSISTENT |
2 | 缓存,持久(仅被其他持久行替换) | 长期复用 |
NOTALLOC_KEEP |
4 | 不缓存,已有行不动 | 流式数据,避免污染 L2 |
NOTALLOC_CLEAN |
5 | 不缓存,同地址行标记 Clean | 流式 + 主动清理 |
NOTALLOC_DROP |
6 | 不缓存,同地址行立即丢弃 | 一次性读取 |
basic_api 中通过 GlobalTensor::SetL2CacheHint(mode) 预设,DataCopy 内部自动提取并转发。
7. basic_api 参数填写工作流
┌─ 1. 确定维度 N (1~5),作为模板参数 dim
├─ 2. 构建 NdDmaLoopInfo<dim>(5 个数组:srcStride, dstStride, size, lpSize, rpSize)
├─ 3. 构建 NdDmaConfig(编译期 constexpr 推荐)
├─ 4. 构建 NdDmaParams<T, dim> = {loopInfo, constantValue}
├─ 5. (可选) srcGlobal.SetL2CacheHint(mode)
├─ 6. (多核共享 GM) NdDmaDci()
└─ 7. DataCopy<T, dim, config>(dstLocal, srcGlobal, params)
结构体字段速查
struct NdDmaConfig {
bool isNearestValueMode = false; // true=邻边复制, false=常量填充
uint16_t loopLpSize = unsetPad; // 全局左 pad 覆盖(0xffff=不覆盖,用 per-dim 值)
uint16_t loopRpSize = unsetPad; // 全局右 pad 覆盖
bool ascOptimize = false; // 保留字段
};
template <uint8_t dim>
struct MultiCopyLoopInfo { // 又名 NdDmaLoopInfo<dim>
uint64_t loopSrcStride[dim] = {0}; // 索引 0 = 最内层
uint32_t loopDstStride[dim] = {0};
uint32_t loopSize[dim] = {0};
uint8_t loopLpSize[dim] = {0}; // [0,255]
uint8_t loopRpSize[dim] = {0}; // [0,255]
};
template <typename T, uint8_t dim>
struct MultiCopyParams { // 又名 NdDmaParams<T, dim>
MultiCopyLoopInfo<dim> loopInfo;
T constantValue; // 常量填充值(isNearestValueMode=false 时生效)
};
NdDmaConfig::loopLpSize/RpSize设为非unsetPad值时,该单一值会覆盖所有维度的 per-dim pad 设置。常用于"全维统一 pad 0"的简洁写法。
8. 实战示例
1 basic_api:2D 广播
// xGm [1,16] -> xLocal [3,16] (单行复制 3 次)
AscendC::NdDmaLoopInfo<2> loopInfo{
{1, 0}, // srcStride: dim1=0 → 重读同一源行(广播)
{1, 16}, // dstStride: dim1=16(目标行间距)
{16, 3}, // size: dim0=16, dim1=3(复制3次)
{0, 0}, {0, 0}
};
AscendC::NdDmaParams<T, 2> params{loopInfo, 0};
AscendC::NdDmaDci();
static constexpr AscendC::NdDmaConfig dmaConfig;
AscendC::DataCopy<T, 2, dmaConfig>(xLocal, xGm, params);
关键:srcStride = 0 触发硬件广播,省去多次读取。
2 basic_api:2D 切片
// xGm [32,64], 取 16×10 块中的 5×7 子块 -> xLocal [16,16]
AscendC::NdDmaLoopInfo<2> loopInfo{
{1, 64}, // srcStride: dim1=64(源行间距)
{1, 16}, // dstStride: dim1=16(目标行间距)
{16, 16}, // size: dim0=16, dim1=16
{0, 0}, {0, 0}
};
AscendC::NdDmaParams<T, 2> params{loopInfo, 0};
AscendC::NdDmaDci();
static constexpr AscendC::NdDmaConfig dmaConfig;
AscendC::DataCopy<T, 2, dmaConfig>(xLocal, xGm, params);
通过 srcGlobal.SetGlobalBuffer(base + offset) 调整起始地址定位子块。
3 basic_api:3D 无 pad
// 3D float, shape [10,8,2]
AscendC::NdDmaLoopInfo<3> loopInfo{
{1, 13, 13*15}, // srcStride: dim0=1, dim1=13, dim2=13*15
{1, 10, 8*10}, // dstStride: dim0=1, dim1=10, dim2=80
{10, 8, 2}, // size
{0, 0, 0}, {0, 0, 0} // 无 pad
};
AscendC::NdDmaParams<float, 3> params{loopInfo, 0.0f};
AscendC::NdDmaDci();
static constexpr AscendC::NdDmaConfig dmaConfig;
AscendC::DataCopy<float, 3, dmaConfig>(dstLocal, srcGm, params);
9. 常见陷阱
| 陷阱 | 后果 | 规避 |
|---|---|---|
padding_mode / isNearestValueMode 极性混淆 |
填充值被忽略或填充模式错误 | 牢记:c_api true=常量;basic_api true=邻边。取反关系 |
| c_api setter 残留 SPR 状态 | 下次搬运用错 stride/pad | 每次搬运前显式调全 5 个 stride setter + pad_count |
| dst 起始地址非 32B 对齐 | 硬件异常 | UB 分配时保证 32B 对齐(TPipe 默认满足) |
loopN_size 未用维度填 0 |
硬件视为 0 元素,搬运空 | 未用维度填 1,不是 0 |
src_stride=0 但不想要广播 |
反复重读同一地址 | 广播场景才用 0,否则填真实跨度 |
| b64 类型用非零 pad | 编译失败/运行异常 | b64 仅支持 constantValue=0 |
fp8 类型直接传 asc_set_ndim_pad_value |
无匹配重载 | 先 reinterpret_cast<int8_t> |
多核场景未调 dci |
读到旧 cache 数据 | 共享 GM 时搬运前调 NdDmaDci() |
dim0 pad 放进 pad_count_config |
dim0 pad 不生效 | dim0 pad 只在 asc_ndim_copy_gm2ub 调用参数中 |
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐



所有评论(0)