已合并
【docs】更新多核tiling 算子实践 #4157
Chen Ning创建于 7月8日
【docs】更新多核tiling 算子实践 #4157
已合并
Chen Ning创建于 7月8日
24 个文件变更+702-847
Adocs/zh/guide/figures/maintile_even.png+3-0
@@ -0,0 +1,3 @@
1+version https://git-lfs.github.com/spec/v1
2+oid sha256:5c7d3b244bf8a48c90c6b4853286475db328eb001564c7c0691f68ac993fbfd8
3+size 11083
Rdocs/zh/guide/figures/多核及Tiling示意图.pngdocs/zh/guide/figures/operator_implementation_components.png+2-2
@@ -1,3 +1,3 @@
1version https://git-lfs.github.com/spec/v11version https://git-lfs.github.com/spec/v1
2-oid sha256:0cb62b9830229bae4ce03acda937a1c79c4675941337a9de1e4fed3ae5530dc82+oid sha256:681bd210b51a623f4f22784385d4aebea8830114688f08fb6ac9b890a8613a3e
3-size 99233+size 6943
Adocs/zh/guide/figures/tailblock-tailcore.png+3-0
@@ -0,0 +1,3 @@
1+version https://git-lfs.github.com/spec/v1
2+oid sha256:a8a8028d39c3b0ab8a8f217e2257fff21dfefe880693e5ffd4ffa0f70e42f005
3+size 28373
Adocs/zh/guide/figures/tailblock_tiling.png+3-0
@@ -0,0 +1,3 @@
1+version https://git-lfs.github.com/spec/v1
2+oid sha256:bf71a885875cb6a8f72f58e79963ff15816139658f2182af7a7f1568ba429cc8
3+size 19101
Adocs/zh/guide/figures/tailcore_tiling.png+3-0
@@ -0,0 +1,3 @@
1+version https://git-lfs.github.com/spec/v1
2+oid sha256:7aec122245c269e69acaf7bf844d7a8d20f599d062fa7ac5dc934cdfed5ea75e
3+size 22892
Adocs/zh/guide/figures/vector_tiling_intro.png+3-0
@@ -0,0 +1,3 @@
1+version https://git-lfs.github.com/spec/v1
2+oid sha256:88b99a303d1c5097363433f433e7108b1c69cf635646b1e7f35210abd2fc2bce
3+size 23576
Mdocs/zh/guide/index.md+6-6
@@ -199,12 +199,12 @@
199 - [概述](算子实践参考/SIMD算子实现/矢量编程/概述.md)199 - [概述](算子实践参考/SIMD算子实现/矢量编程/概述.md)
200 - [基础矢量算子](算子实践参考/SIMD算子实现/矢量编程/基础矢量算子.md)200 - [基础矢量算子](算子实践参考/SIMD算子实现/矢量编程/基础矢量算子.md)
201 - [TBuf的使用](算子实践参考/SIMD算子实现/矢量编程/TBuf的使用.md)201 - [TBuf的使用](算子实践参考/SIMD算子实现/矢量编程/TBuf的使用.md)
202- - [多核&Tiling切分](算子实践参考/SIMD算子实现/矢量编程/多核-Tiling切分/概述.md)202+ - [多核Tiling切分](算子实践参考/SIMD算子实现/矢量编程/多核Tiling切分/概述.md)
203- - [概述](算子实践参考/SIMD算子实现/矢量编程/多核-Tiling切分/概述.md)203+ - [概述](算子实践参考/SIMD算子实现/矢量编程/多核Tiling切分/概述.md)
204- - [多核Tiling](算子实践参考/SIMD算子实现/矢量编程/多核-Tiling切分/多核Tiling.md)204+ - [主块均分](算子实践参考/SIMD算子实现/矢量编程/多核Tiling切分/主块均分.md)
205- - [尾块Tiling](算子实践参考/SIMD算子实现/矢量编程/多核-Tiling切分/尾块Tiling.md)205+ - [尾块均分](算子实践参考/SIMD算子实现/矢量编程/多核Tiling切分/尾块均分.md)
206- - [尾核Tiling](算子实践参考/SIMD算子实现/矢量编程/多核-Tiling切分/尾核Tiling.md)206+ - [尾核切分](算子实践参考/SIMD算子实现/矢量编程/多核Tiling切分/尾核切分.md)
207- - [尾核&尾块](算子实践参考/SIMD算子实现/矢量编程/多核-Tiling切分/尾核-尾块.md)207+ - [尾核尾块切分](算子实践参考/SIMD算子实现/矢量编程/多核Tiling切分/尾核尾块切分.md)
208 208 
209 - [DoubleBuffer场景](算子实践参考/SIMD算子实现/矢量编程/DoubleBuffer场景.md)209 - [DoubleBuffer场景](算子实践参考/SIMD算子实现/矢量编程/DoubleBuffer场景.md)
210 - [Broadcast场景](算子实践参考/SIMD算子实现/矢量编程/Broadcast场景.md)210 - [Broadcast场景](算子实践参考/SIMD算子实现/矢量编程/Broadcast场景.md)
Mdocs/zh/guide/算子实践参考/SIMD算子实现/矢量编程/DoubleBuffer场景.md+2-2
@@ -20,7 +20,7 @@
20 totalLength : ((totalLength + alignNum - 1) / alignNum) * alignNum;20 totalLength : ((totalLength + alignNum - 1) / alignNum) * alignNum;
21 ```21 ```
22 22 
23-2. 根据totalLengthAligned,计算每个核的计算数据长度blockLength,分核策略可参照[尾核Tiling](多核-Tiling切分/尾核Tiling.md)。23+2. 根据totalLengthAligned,计算每个核的计算数据长度blockLength,分核策略可参照[尾核切分](多核Tiling切分/尾核切分.md)。
243. 计算其余Tiling参数。243. 计算其余Tiling参数。
25 25 
26 对当前Unified Buffer可用空间以32字节为粒度,进行切分,计算出数据块个数UB\_BLOCK\_NUM。根据是否开启DoubleBuffer计算出当前可用的最大数据块个数,记作MAX\_AVAILABLE\_UB\_BLOCK\_NUM。最后,以MAX\_AVAILABLE\_UB\_BLOCK\_NUM为粒度,对blockLength进行切分。为方便演示,如下代码直接给出UB\_BLOCK\_NUM,作为当前Unified Buffer可用空间包含的block(32字节)数。26 对当前Unified Buffer可用空间以32字节为粒度,进行切分,计算出数据块个数UB\_BLOCK\_NUM。根据是否开启DoubleBuffer计算出当前可用的最大数据块个数,记作MAX\_AVAILABLE\_UB\_BLOCK\_NUM。最后,以MAX\_AVAILABLE\_UB\_BLOCK\_NUM为粒度,对blockLength进行切分。为方便演示,如下代码直接给出UB\_BLOCK\_NUM,作为当前Unified Buffer可用空间包含的block(32字节)数。
@@ -115,7 +115,7 @@ __aicore__ inline void Init(__gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t
115}115}
116```116```
117 117 
118-由于开启DoubleBuffer后,切分后的主块数据块个数翻倍,在Process函数中,需要将BUFFER\_NUM带入计算循环次数;尾块独立计算,不开启DoubleBuffer。后续主尾块在CopyIn、Compute、CopyOut阶段中的处理,与[尾块tiling处理](多核-Tiling切分/尾块Tiling.md)相同。118+由于开启DoubleBuffer后,切分后的主块数据块个数翻倍,在Process函数中,需要将BUFFER\_NUM带入计算循环次数;尾块独立计算,不开启DoubleBuffer。后续主尾块在CopyIn、Compute、CopyOut函数中的处理,与[尾块均分](多核Tiling切分/尾块均分.md)相同。
119 119 
120```120```
121__aicore__ inline void Process()121__aicore__ inline void Process()
Ddocs/zh/guide/算子实践参考/SIMD算子实现/矢量编程/多核-Tiling切分/多核Tiling.md+0-219
@@ -1,219 +0,0 @@
1-# 多核Tiling<a name="ZH-CN_TOPIC_0000002532228159"></a>
2- 
3-基于Ascend C方式实现带有Tiling的算子的开发流程如下图所示。
4- 
5-**图1** 算子开发流程<a name="zh-cn_topic_0000002236197677_fig18330201774715"></a>
6-![](../../../../figures/算子开发流程.png "算子开发流程")
7- 
8-## 算子分析<a name="zh-cn_topic_0000002236197677_section179012044886"></a>
9- 
10-本样例为输入数据在核间均分、核内均分场景。本样例的Tiling策略为:数据整体长度TOTAL\_LENGTH为8 \* 2048,数据平均分配到8个核上运行,每个核上计算的数据长度BLOCK\_LENGTH为2048,将单核上的数据切分成16块(此处切分成16块仅用来作为Tiling的样例,并不代表性能最佳,仅供参考),每块数据的长度TILE\_LENGTH为128。数据切分示意如下图所示:
11- 
12-**图2** 数据切分示意图<a name="zh-cn_topic_0000002236197677_fig1986021174914"></a>
13-![](../../../../figures/数据切分示意图.png "数据切分示意图")
14- 
15-通过以上分析,得到Ascend C Add算子的设计规格如下:
16- 
17-- 算子类型(OpType):Add
18-- 算子输入输出:
19- 
20- **表1** Add算子输入输出规格
21- 
22- <a name="table4934296305"></a>
23- <table><thead align="left"><tr id="row59358913304"><th class="cellrowborder" valign="top" width="25%" id="mcps1.2.5.1.1"><p id="p5503181819300"><a name="p5503181819300"></a><a name="p5503181819300"></a><strong id="b1850331853010"><a name="b1850331853010"></a><a name="b1850331853010"></a>name</strong></p>
24- </th>
25- <th class="cellrowborder" valign="top" width="25%" id="mcps1.2.5.1.2"><p id="p1550381833017"><a name="p1550381833017"></a><a name="p1550381833017"></a><strong id="b7503171811309"><a name="b7503171811309"></a><a name="b7503171811309"></a>shape</strong></p>
26- </th>
27- <th class="cellrowborder" valign="top" width="25%" id="mcps1.2.5.1.3"><p id="p1950391883014"><a name="p1950391883014"></a><a name="p1950391883014"></a><strong id="b2503111803020"><a name="b2503111803020"></a><a name="b2503111803020"></a>data type</strong></p>
28- </th>
29- <th class="cellrowborder" valign="top" width="25%" id="mcps1.2.5.1.4"><p id="p14503218133015"><a name="p14503218133015"></a><a name="p14503218133015"></a><strong id="b8503141818301"><a name="b8503141818301"></a><a name="b8503141818301"></a>format</strong></p>
30- </th>
31- </tr>
32- </thead>
33- <tbody><tr id="row393589203016"><td class="cellrowborder" valign="top" width="25%" headers="mcps1.2.5.1.1 "><p id="p1950331810308"><a name="p1950331810308"></a><a name="p1950331810308"></a>x(输入)</p>
34- </td>
35- <td class="cellrowborder" valign="top" width="25%" headers="mcps1.2.5.1.2 "><p id="p12328141215344"><a name="p12328141215344"></a><a name="p12328141215344"></a>(8, 2048)</p>
36- </td>
37- <td class="cellrowborder" valign="top" width="25%" headers="mcps1.2.5.1.3 "><p id="p135031118173010"><a name="p135031118173010"></a><a name="p135031118173010"></a>half</p>
38- </td>
39- <td class="cellrowborder" valign="top" width="25%" headers="mcps1.2.5.1.4 "><p id="p19503131815305"><a name="p19503131815305"></a><a name="p19503131815305"></a>ND</p>
40- </td>
41- </tr>
42- <tr id="row6935119173013"><td class="cellrowborder" valign="top" width="25%" headers="mcps1.2.5.1.1 "><p id="p75031182305"><a name="p75031182305"></a><a name="p75031182305"></a>y(输入)</p>
43- </td>
44- <td class="cellrowborder" valign="top" width="25%" headers="mcps1.2.5.1.2 "><p id="p532871211344"><a name="p532871211344"></a><a name="p532871211344"></a>(8, 2048)</p>
45- </td>
46- <td class="cellrowborder" valign="top" width="25%" headers="mcps1.2.5.1.3 "><p id="p45031818103018"><a name="p45031818103018"></a><a name="p45031818103018"></a>half</p>
47- </td>
48- <td class="cellrowborder" valign="top" width="25%" headers="mcps1.2.5.1.4 "><p id="p1503918103012"><a name="p1503918103012"></a><a name="p1503918103012"></a>ND</p>
49- </td>
50- </tr>
51- <tr id="row59354943016"><td class="cellrowborder" valign="top" width="25%" headers="mcps1.2.5.1.1 "><p id="p1450316186305"><a name="p1450316186305"></a><a name="p1450316186305"></a>z(输出)</p>
52- </td>
53- <td class="cellrowborder" valign="top" width="25%" headers="mcps1.2.5.1.2 "><p id="p632831214347"><a name="p632831214347"></a><a name="p632831214347"></a>(8, 2048)</p>
54- </td>
55- <td class="cellrowborder" valign="top" width="25%" headers="mcps1.2.5.1.3 "><p id="p15503918173017"><a name="p15503918173017"></a><a name="p15503918173017"></a>half</p>
56- </td>
57- <td class="cellrowborder" valign="top" width="25%" headers="mcps1.2.5.1.4 "><p id="p1503101813012"><a name="p1503101813012"></a><a name="p1503101813012"></a>ND</p>
58- </td>
59- </tr>
60- </tbody>
61- </table>
62- 
63-- 核函数名称:tiling\_strategy\_custom
64-- 使用的主要接口:
65- - DataCopy:数据搬移接口
66- - Add:矢量基础算术接口
67- - EnQue、DeQue等接口:Queue队列管理接口
68- 
69-- 算子实现文件名称:tiling\_strategy.asc
70- 
71-## Tiling实现<a name="zh-cn_topic_0000002236197677_section480741815522"></a>
72- 
73-前述场景中算子的输入和输出均为固定shape,然而在实际的算子开发场景中,这些信息是支持动态变化的,场景会更加灵活和复杂。动态shape场景下,输入的shape是未知的。一些与输入shape相关的变量(比如每次搬运的块大小等),需要通过Tiling计算出来,然后传递到kernel侧,kernel侧使用该参数进行后续的计算。
74- 
75-具体实现方式为:分析设计Tiling参数、定义Tiling结构体,在Host侧通过上下文获取输入输出的shape信息,根据shape信息,计算Tiling参数并设置到对应的Tiling结构体中;通过核函数入口参数将Tiling信息传入核函数,在核函数内通过解析Tiling结构体,获取并使用相关参数来实现核函数内部逻辑,详细介绍请参考[Host侧tiling实现](../../../../编程指南/高级编程/Aclnn算子工程化开发/设计与实现/Host侧Tiling实现.md)。本节将以上述分析中的切分策略为例,说明如何实现Tiling。
76- 
77-基于本节的切分策略,Tiling需要定义如下参数:
78- 
79-- blockLength:每个核的计算数据长度;
80-- tileNum:每个核需要计算的数据块个数;
81-- tileLength:每个核内每个数据块的长度。
82- 
83-根据确定的Tiling参数,使用C++语法定义TilingData结构体,代码如下。
84- 
85-```
86-struct AddCustomTilingData {
87- uint32_t blockLength;
88- uint32_t tileNum;
89- uint32_t tileLength;
90- ...
91-}
92-```
93- 
94-接下来完成Tiling参数的计算。由于每个核内数据被切分为16块,根据使用的核数和核内切分数,计算Tiling参数,并写入到Tiling结构体内。代码示例如下:
95- 
96-```
97-constexpr int32_t NUM_BLOCKS = 8; // 使用的核数
98-constexpr int32_t TILE_NUM = 16; // 核内切分数量
99-void GenerateTilingData(uint8_t* tilingBuf, uint32_t numBlocks)
100-{
101- uint32_t totalLength;
102- // 此处省略如何获取数据总长TOTAL_LENGTH,可以根据具体情况实现。本章节仅介绍Tiling相关内容。
103- AddCustomTilingData* tiling = reinterpret_cast<AddCustomTilingData *>(tilingBuf);
104- uint32_t blockLength = TOTAL_LENGTH / numBlocks;
105- uint32_t tileNum = TILE_NUM;
106- uint32_t tileLength = blockLength / tileNum;
107- 
108- tiling->blockLength = blockLength;
109- tiling->tileNum = tileNum;
110- tiling->tileLength = tileLength;
111-}
112-```
113- 
114-最后,在Host侧调用程序中,调用上述Tiling参数计算函数,计算出相关参数,然后传递到Kernel侧核函数。
115- 
116-```
117- constexpr int32_t NUM_BLOCKS = 8;
118- ...
119- uint8_t *tiling = nullptr;
120- size_t tilingSize = sizeof(AddCustomTilingData);
121- GenerateTilingData(tiling, NUM_BLOCKS); // 调用tiling参数计算函数
122- ....
123-
124- tiling_strategy_custom<<<NUM_BLOCKS, 0, stream>>>(xDevice, yDevice, zDevice,
125- *reinterpret_cast<AddCustomTilingData*>(tiling));
126- ....
127-```
128- 
129-## 算子类实现<a name="zh-cn_topic_0000002236197677_section849945172010"></a>
130- 
131-Kernel侧算子实现仍遵循[矢量算子核函数实现流程](../基础矢量算子.md#zh-cn_topic_0000002201157438_fig16061570280),接下来重点介绍本场景中算子类实现的不同点。
132- 
133-- 设置输入输出Global Tensor的Global Memory内存地址。
134- 
135- 由于本样例中将数据分配到了多个核上进行处理,每个核处理不同的数据,因此不同核要处理的数据在Global Memory上的地址不同,在初始化函数Init中,需要获取单核所需处理的输入输出在Global Memory上的内存偏移地址,并将该偏移地址设置到GlobalTensor中。
136- 
137- 以获取输入x在Global Memory上的内存偏移地址为例,数据整体长度TOTAL\_LENGTH为8 \* 2048,平均分配到8个核上运行,每个核上处理的数据长度blockLength为2048,调用[GetBlockIdx](../../../../../api/SIMD-API/basic_api/tool_interface/system_resources_and_variables/GetBlockIdx.md)接口获取当前核的index,x + blockLength \* GetBlockIdx\(\)即为单核处理程序中x在Global Memory上的内存偏移地址,获取偏移地址后,使用GlobalTensor类的[SetGlobalBuffer](../../../../../api/SIMD-API/basic_api/data_structures/GlobalTensor/SetGlobalBuffer.md)接口设定该核上Global Memory的起始地址以及长度,具体示意图请参考[图3](#zh-cn_topic_0000002236197677_fig398721711313)。代码如下所示:
138- 
139- ```
140- xGm.SetGlobalBuffer((__gm__ half *)x + this->blockLength * AscendC::GetBlockIdx(), this->blockLength);
141- ```
142- 
143- **图3** 多核并行处理示意图<a name="zh-cn_topic_0000002236197677_fig398721711313"></a>
144- ![](../../../../figures/多核并行处理示意图-38.png "多核并行处理示意图-38")
145- 
146-- 通过Pipe内存管理对象为输入输出Queue分配内存。
147- 
148- 对于单核上的处理数据,可以进行数据切块(Tiling),在本示例中,仅作为参考,将单核上的数据(2048个数)切分成16块(并不意味着16块就是性能最优),每块tileLength(128)个数据。数据切分示意图如[图4](#zh-cn_topic_0000002236197677_fig1319211154719)所示。
149- 
150- **图4** 单核数据切分示意图<a name="zh-cn_topic_0000002236197677_fig1319211154719"></a>
151- ![](../../../../figures/单核数据切分示意图-39.png "单核数据切分示意图-39")
152- 
153- 与[基础矢量算子](../基础矢量算子.md)相比,在通过Pipe内存管理对象为输入输出Queue分配内存时,需使用单核内每个数据块的长度tileLength作为分配内存的长度。比如,为输入x的Queue分配内存,可以通过如下代码段实现,Pipe为inQueueX分配了一块大小为tileLength \* sizeof\(half\)个字节的内存块,每个内存块能容纳tileLength(128)个half类型数据。
154- 
155- ```
156- pipe->InitBuffer(inQueueX, 1, this->tileLength * sizeof(half))
157- ```
158- 
159-具体的初始化函数代码如下:
160- 
161-```
162-__aicore__ inline void Init(__gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t* z, AddCustomTilingData tiling, AscendC::TPipe* pipeIn)
163-{
164- pipe = pipeIn;
165- this->blockLength = tiling.blockLength;
166- this->tileNum = tiling.tileNum;
167- this->tileLength = tiling.tileLength;
168- // 计算每个核上的地址偏移
169- xGm.SetGlobalBuffer((__gm__ half *)x + this->blockLength * AscendC::GetBlockIdx(), this->blockLength);
170- yGm.SetGlobalBuffer((__gm__ half *)y + this->blockLength * AscendC::GetBlockIdx(), this->blockLength);
171- zGm.SetGlobalBuffer((__gm__ half *)z + this->blockLength * AscendC::GetBlockIdx(), this->blockLength);
172- // pipe alloc memory to queue, the unit is Bytes
173- pipe->InitBuffer(inQueueX, 1, this->tileLength * sizeof(half));
174- pipe->InitBuffer(inQueueY, 1, this->tileLength * sizeof(half));
175- pipe->InitBuffer(outQueueZ, 1, this->tileLength * sizeof(half));
176-}
177-```
178- 
179-每个核需要对tileNum个数据块分别进行搬入、计算、搬出处理,因此Process函数内将tileNum作为循环上限。
180- 
181-```
182-__aicore__ inline void Process()
183-{
184- int32_t loopCount = this->tileNum;
185- // tiling strategy, pipeline parallel
186- for (int32_t i = 0; i < loopCount; i++) {
187- CopyIn(i, this->tileLength);
188- Compute(i, this->tileLength);
189- CopyOut(i, this->tileLength);
190- }
191-}
192-```
193- 
194-对应的,每个核内搬入、搬出每个数据块时,需定位到每个数据块所在Global Memory上的内存偏移地址,因此在CopyIn和CopyOut函数内部使用DataCopy接口时,需增加每个数据块的地址偏移。Compute函数没有变化,与[基础矢量算子](../基础矢量算子.md)相同。
195- 
196-CopyIn函数实现代码如下:
197- 
198-```
199-__aicore__ inline void CopyIn(int32_t progress, uint32_t tileLength)
200-{
201- ...
202- // copy progress_th tile from global tensor to local tensor
203- AscendC::DataCopy(xLocal, xGm[progress * this->tileLength], tileLength);
204- AscendC::DataCopy(yLocal, yGm[progress * this->tileLength], tileLength);
205- ...
206-}
207-```
208- 
209-CopyOut函数实现代码如下:
210- 
211-```
212- __aicore__ inline void CopyOut(int32_t progress, uint32_t tileLength)
213-{
214- ...
215- // copy progress_th tile from local tensor to global tensor
216- AscendC::DataCopy(zGm[progress * this->tileLength], zLocal, tileLength);
217- ...
218-}
219-```
Ddocs/zh/guide/算子实践参考/SIMD算子实现/矢量编程/多核-Tiling切分/尾块Tiling.md+0-207
@@ -1,207 +0,0 @@
1-# 尾块Tiling<a name="ZH-CN_TOPIC_0000002500548090"></a>
2- 
3-如下图中的示例,算子的输入shape为(1,2048),支持的数据类型为half类型,输入数据可以对齐到一个datablock的大小(32字节),输入数据为2048 \* 2 / 32 = 128个datablock,因此可以平均分配到每个核上(假设使用8个核),每个核上处理256个数,16个datablock。此时不需要进行尾块处理。
4- 
5-**图1** shape对齐场景<a name="zh-cn_topic_0000002201157442_fig55115212126"></a>
6-![](../../../../figures/shape对齐场景.png "shape对齐场景")
7- 
8-针对一些shape,比如算子的输入shape为(1,1904),支持的数据类型为half类型,输入数据可以对齐到一个datablock的大小(32字节),可以平均分配到每个核上(假设使用8个核),每个核上处理238个数,238个数无法均分到datablock上,分满14个datablock后,剩余14个数(28字节),多核切分后需要进行尾块处理。
9- 
10-对于不同shape的输入进行数据切分时,可能会发生Tiling后的数据平均分配到多核上,但每个核内的数据无法均分的情况。针对此种场景,在Tiling参数中增加变量lastTileLength,用来表示最后一个分块,即尾块的大小。因此,在定义算子的Tiling结构体时包含以下四个成员:
11- 
12-- blockLength:每个核上计算的数据长度;
13-- tileNum:每个核上切分的主块数据块的个数;
14-- tileLength:每个核上主块数据块的长度;
15-- lastTileLength:每个核上尾块的长度。
16- 
17-**图2** 多核Tiling尾块示意图<a name="zh-cn_topic_0000002201157442_fig48081401102"></a>
18-![](../../../../figures/多核Tiling尾块示意图.png "多核Tiling尾块示意图")
19- 
20-## Tiling实现<a name="zh-cn_topic_0000002201157442_section469034017306"></a>
21- 
22-算子的Tiling结构体定义如下:
23- 
24-```
25-struct AddCustomTilingData {
26- uint32_t blockLength;
27- uint32_t tileNum;
28- uint32_t tileLength;
29- uint32_t lastTileLength;
30- ...
31-};
32-```
33- 
34-Host侧Tiling实现的主要内容为计算以上四个成员变量。步骤如下:
35- 
36-1. 判断数据总长度totalLength是否满足32字节对齐,如不满足,则计算totalLength向上32字节对齐后的长度totalLengthAligned。
37- 
38- ```
39- constexpr uint32_t BLOCK_SIZE = 32;
40- // 为方便计算,这里根据数据类型定义变量alignNum作为对齐数
41- uint32_t alignNum = BLOCK_SIZE / dataTypeSize;
42- // totalLength为数据总量
43- totalLengthAligned = (totalLength % alignNum == 0U) ?
44- static_cast<uint32_t>(totalLength) :
45- ((static_cast<uint32_t>(totalLength) + alignNum - 1) / alignNum) * alignNum;
46- ```
47- 
48-2. 判断totalLengthAligned是否能被使用的核数NumBlocks均分,如果可以,则计算每个核上计算数据长度blockLength。
49- 
50- ```
51- constexpr uint32_t NUM_BLOCKS = 8;
52- constexpr uint32_t UB_BLOCK_NUM = 100; // 此处为方便验证,使用UB_BLOCK_NUM作为Unified Buffer可用的Block数量,因此可得出可用UB空间的大小为UB_BLOCK_NUM * BLOCK_SIZE
53- uint32_t blockLength, tileNum;
54- if ((totalLengthAligned / alignNum) % NUM_BLOCKS == 0U) {
55- blockLength = totalLengthAligned / NUM_BLOCKS;
56- }
57- ```
58- 
59-3. 计算tileNum。为了减少数据搬运开销,应尽量使用核内的Unified Buffer空间。基于每个核上的计算量以及可用Unified Buffer空间的大小,计算tileNum。
60- 
61- ```
62- tileNum = blockLength / (alignNum * UB_BLOCK_NUM);
63- ```
64- 
65-4. 根据计算出的tileNum,计算tileLength和lastTileLength。
66- 
67- 如果每个核的计算量能够被当前可用Unified Buffer空间均分,则按照无尾块场景处理。
68- 
69- ```
70- if (static_cast<uint32_t>(blockLength / alignNum) % UB_BLOCK_NUM == 0U) {
71- // 单核的计算量能被当前可用UB空间均分,仅有主块,无尾块
72- tileLength = UB_BLOCK_NUM * alignNum;
73- lastTileLength = 0U;
74- }
75- ```
76- 
77- 反之,按照尾块场景处理,尾块长度为单核计算数据长度 - tileNum \* tileLength。
78- 
79- ```
80- if (tileNum == 0U) {
81- // 单核需要计算的长度小于UB可用空间,按照仅有尾块处理
82- tileLength = 0U;
83- lastTileLength = static_cast<uint32_t>(((blockLength + alignNum - 1) / alignNum) * alignNum);
84- } else {
85- // 同时有主块和尾块
86- tileLength = UB_BLOCK_NUM * alignNum;
87- lastTileLength = static_cast<uint32_t>(blockLength - tileNum * tileLength);
88- }
89- ```
90- 
91-Host侧Tiling实现的代码如下:
92- 
93-```
94-constexpr uint32_t BLOCK_SIZE = 32;
95-constexpr uint32_t NUM_BLOCKS = 8;
96-constexpr uint32_t UB_BLOCK_NUM = 100; // 此处为方便验证,使用UB_BLOCK_NUM作为UB可用的Block数量,因此可得出可用UB空间的大小为UB_BLOCK_NUM * BLOCK_SIZE
97-...
98- 
99-uint32_t alignNum = BLOCK_SIZE / dataTypeSize; // 为方便计算,这里根据数据类型定义变量alignNum作为对齐数,dataTypeSize为运算数据的数据类型对应的字节数
100-// totalLength为数据总量
101-totalLengthAligned = (totalLength % alignNum == 0U) ?
102- static_cast<uint32_t>(totalLength) :
103- ((static_cast<uint32_t>(totalLength) + alignNum - 1) / alignNum) * alignNum;
104-uint32_t blockLength, tileNum;
105-if ((totalLengthAligned / alignNum) % NUM_BLOCKS == 0U) {
106- blockLength = totalLengthAligned / NUM_BLOCKS;
107- tileNum = blockLength / alignNum / UB_BLOCK_NUM;
108- 
109- if (tileNum == 0) {
110- // 单核需要计算的长度小于UB可用空间,按照仅有尾块处理
111- tileLength = 0;
112- lastTileLength = ((blockLength + alignNum - 1) / alignNum) * alignNum;
113- } else if ((blockLength / alignNum) % UB_BLOCK_NUM == 0) {
114- // 单核的计算量能被当前可用UB空间均分,仅有主块,无尾块
115- tileLength = UB_BLOCK_NUM * alignNum;
116- lastTileLength = 0;
117- } else {
118- // 同时有主块和尾块
119- tileLength = UB_BLOCK_NUM * alignNum;
120- lastTileLength = blockLength - tileNum * tileLength;
121- }
122- ...
123-}
124-```
125- 
126-\(1,1904\)形状的输入数据计算后,tiling结构体内各个变量的值如下:
127- 
128-```
129-struct AddCustomTilingData {
130- uint32_t blockLength = 238; // 每个核计算238个half,8个核共计算1904个half
131- uint32_t tileNum = 0; // 可用的UB空间足够,为仅有尾块的场景
132- uint32_t tileLength = 0; // 没有主块,主块长度为0
133- uint32_t lastTileLength = 240; // 238个half未32B对齐,对齐到240个half搬运
134- ...
135-};
136-```
137- 
138-## 算子类实现<a name="zh-cn_topic_0000002201157442_section2027413973119"></a>
139- 
140-与[多核Tiling](多核Tiling.md)相比,在Init函数中通过Pipe内存管理对象为输入输出Queue分配内存时,取tileLength与lastTileLength中的最大值作为分配内存的长度。例如,当单核需要计算的长度小于UB可用空间时,按照仅有尾块处理,此时tileLength为0,而lastTileLength为数据块长度。因此,需要取两者中的较大值来分配内存。
141- 
142-```
143-uint32_t initBufferLength = AscendC::Std::max(this->tileLength, this->lastTileLength);
144-pipe->InitBuffer(inQueueX, 1, this->initBufferLength * sizeof(dataType));
145-```
146- 
147-由于尾块长度为lastTileLength,与主块数据块的长度不同,因此在CopyIn函数、Compute函数、CopyOut函数中传入本次循环待处理的数据块长度参数tileLength,即待处理的主块或尾块的数据长度。
148- 
149-Process函数实现代码如下:
150- 
151-```
152-__aicore__ inline void Process()
153-{
154- // 计算主块数据,对应数据块长度为tileLength
155- for (uint32_t i = 0; i < this->tileNum; i++) {
156- CopyIn(i, this->tileLength);
157- Compute(i, this->tileLength);
158- CopyOut(i, this->tileLength);
159- }
160- // 计算尾块数据,对应数据块长度为lastTileLength
161- if (this->lastTileLength > 0) {
162- CopyIn(this->tileNum, this->lastTileLength);
163- Compute(this->tileNum, this->lastTileLength);
164- CopyOut(this->tileNum, this->lastTileLength);
165- }
166-}
167-```
168- 
169-CopyIn函数实现代码如下:
170- 
171-```
172-__aicore__ inline void CopyIn(int32_t progress, uint32_t tileLength)
173-{
174- AscendC::LocalTensor<T> xLocal = inQueueX.AllocTensor<T>();
175- AscendC::LocalTensor<T> yLocal = inQueueY.AllocTensor<T>();
176- AscendC::DataCopy(xLocal, xGm[progress * this->tileLength], tileLength);
177- AscendC::DataCopy(yLocal, yGm[progress * this->tileLength], tileLength);
178- inQueueX.EnQue(xLocal);
179- inQueueY.EnQue(yLocal);
180-}
181-```
182- 
183-Compute函数实现代码如下:
184- 
185-```
186-__aicore__ inline void Compute(int32_t progress, uint32_t tileLength)
187-{
188- AscendC::LocalTensor<T> xLocal = inQueueX.DeQue<T>();
189- AscendC::LocalTensor<T> yLocal = inQueueY.DeQue<T>();
190- AscendC::LocalTensor<T> zLocal = outQueueZ.AllocTensor<T>();
191- AscendC::Add(zLocal, xLocal, yLocal, tileLength);
192- outQueueZ.EnQue<T>(zLocal);
193- inQueueX.FreeTensor(xLocal);
194- inQueueY.FreeTensor(yLocal);
195-}
196-```
197- 
198-CopyOut函数实现代码如下:
199- 
200-```
201-__aicore__ inline void CopyOut(int32_t progress, uint32_t tileLength)
202-{
203- AscendC::LocalTensor<T> zLocal = outQueueZ.DeQue<T>();
204- AscendC::DataCopy(zGm[progress * this->tileLength], zLocal, tileLength);
205- outQueueZ.FreeTensor(zLocal);
206-}
207-```
Ddocs/zh/guide/算子实践参考/SIMD算子实现/矢量编程/多核-Tiling切分/尾核-尾块.md+0-117
@@ -1,117 +0,0 @@
1-# 尾核&尾块<a name="ZH-CN_TOPIC_0000002500468234"></a>
2- 
3-对于不同shape的输入进行数据切分时,可能会发生数据无法平均分配到多个核、同时每个核内的数据无法均分的情况。参考[核间均分场景下的尾块处理](尾块Tiling.md)与[核间不均分场景下的尾核处理](尾核Tiling.md)的处理方式,将两者结合起来考虑整核的尾块、尾核的尾块的处理方式。
4- 
5-## Tiling实现<a name="zh-cn_topic_0000002236277533_section132981926144714"></a>
6- 
7-由于本场景中核间、核内的数据均无法均分,在[核间不均分场景下的尾核处理](尾核Tiling.md)定义的Tiling结构体的基础上增加两个成员变量:
8- 
9-- formerLastTileLength:数据量多的核最后一个分块大小,即整核的尾块大小。
10- 
11- 计算时,先按[尾核Tiling](尾核Tiling.md)中提到的分核策略,切分数据量多的核。
12- 
13- ```
14- // shape需要对齐到的datablock
15- uint32_t totalLengthAligned = (totalLength % alignNum == 0U) ?
16- static_cast<uint32_t>(totalLength) :
17- ((static_cast<uint32_t>(totalLength) + alignNum - 1) / alignNum) * alignNum;
18- // 计算整核数量
19- uint32_t formerNum = (totalLengthAligned / alignNum) % numBlocks;
20- // 计算整核的数据量
21- uint32_t formerLength = static_cast<uint32_t>(((totalLengthAligned + numBlocks - 1) / numBlocks + alignNum - 1) / alignNum) * alignNum;
22- ```
23- 
24- 再按[尾块Tiling](尾块Tiling.md)中的切分策略,计算尾块长度。
25- 
26- ```
27- TilingParamsCalc(formerLength, alignNum, formerTileNum, formerTileLength, formerLastTileLength);
28-
29- void TilingParamsCalc(uint32_t length, uint32_t alignNum, uint32_t& tileNum, uint32_t& tileLength,
30- uint32_t& lastTileLength)
31- {
32- tileNum = length / (alignNum * UB_BLOCK_NUM);
33- if (tileNum == 0U) {
34- tileLength = 0U;
35- lastTileLength = static_cast<uint32_t>(((length + alignNum - 1) / alignNum) * alignNum);
36- } else if (static_cast<uint32_t>(length / alignNum) % UB_BLOCK_NUM == 0U) {
37- tileLength = UB_BLOCK_NUM * alignNum;
38- lastTileLength = 0U;
39- } else {
40- tileLength = UB_BLOCK_NUM * alignNum;
41- lastTileLength = static_cast<uint32_t>(length - tileNum * tileLength);
42- }
43- }
44- ```
45- 
46-- tailLastTileLength:数据量少的核最后一个分块大小,即尾核的尾块大小。
47- 
48- 计算时,先按[尾核Tiling](尾核Tiling.md)中提到的分核策略,切分数据量少的核。
49- 
50- ```
51- // 计算尾核数量
52- uint32_t tailNum = numBlocks - formerNum;
53- // 计算尾核的数据量
54- uint32_t tailLength = (totalLengthAligned / numBlocks / alignNum) * alignNum;
55- ```
56- 
57- 再按[尾块Tiling](尾块Tiling.md)中的切分策略,计算尾块长度。
58- 
59- ```
60- TilingParamsCalc(tailLength, alignNum, tailTileNum, tailTileLength, tailLastTileLength);
61-
62- void TilingParamsCalc(uint32_t length, uint32_t alignNum, uint32_t& tileNum, uint32_t& tileLength,
63- uint32_t& lastTileLength)
64- {
65- tileNum = length / (alignNum * UB_BLOCK_NUM);
66- if (tileNum == 0U) {
67- tileLength = 0U;
68- lastTileLength = static_cast<uint32_t>(((length + alignNum - 1) / alignNum) * alignNum);
69- } else if (static_cast<uint32_t>(length / alignNum) % UB_BLOCK_NUM == 0U) {
70- tileLength = UB_BLOCK_NUM * alignNum;
71- lastTileLength = 0U;
72- } else {
73- tileLength = UB_BLOCK_NUM * alignNum;
74- lastTileLength = static_cast<uint32_t>(length - tileNum * tileLength);
75- }
76- }
77- ```
78- 
79-## 算子类实现<a name="zh-cn_topic_0000002236277533_section182075114713"></a>
80- 
81-Kernel侧Init函数和Process函数的实现需将[核间均分场景下的尾块处理](尾块Tiling.md)与[核间不均分场景下的尾核处理](尾核Tiling.md)的实现结合起来。
82- 
83-Init函数中由于整核和尾核对应的tileLength和lastTileLength不同。因此需按照[核间不均分场景下的尾核处理](尾核Tiling.md)中提到的分别处理整核和尾核。后续对主块和尾块的CopyIn、Compute、CopyOut函数的处理方式与[核间均分场景下的处理](尾块Tiling.md)方式相同。
84- 
85-Init函数实现代码如下:
86- 
87-```
88-__aicore__ inline void Init(__gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t* z, AddCustomTilingData tiling, AscendC::TPipe* pipeIn)
89-{
90- pipe = pipeIn;
91- if (AscendC::GetBlockIdx() < tiling.formerNum) {
92- this->tileNum = tiling.formerTileNum;
93- this->tileLength = tiling.formerTileLength;
94- this->lastTileLength = tiling.formerLastTileLength;
95- uint64_t offset = tiling.formerLength * AscendC::GetBlockIdx();
96- xGm.SetGlobalBuffer((__gm__ half *)x + offset, tiling.formerLength);
97- yGm.SetGlobalBuffer((__gm__ half *)y + offset, tiling.formerLength);
98- zGm.SetGlobalBuffer((__gm__ half *)z + offset, tiling.formerLength);
99- } else {
100- this->tileNum = tiling.tailTileNum;
101- this->tileLength = tiling.tailTileLength;
102- this->lastTileLength = tiling.tailLastTileLength;
103- uint64_t offset = tiling.formerLength * tiling.formerNum
104- + tiling.tailLength * (AscendC::GetBlockIdx() - tiling.formerNum);
105- xGm.SetGlobalBuffer((__gm__ half *)x + offset, tiling.tailLength);
106- yGm.SetGlobalBuffer((__gm__ half *)y + offset, tiling.tailLength);
107- zGm.SetGlobalBuffer((__gm__ half *)z + offset, tiling.tailLength);
108- }
109-
110- // 只有尾块的场景下,tileLength为0,因此取tileLength和lastTileLength的最大值来初始化
111- uint32_t initBufferLength = AscendC::Std::max(this->tileLength, this->lastTileLength);
112- pipe->InitBuffer(inQueueX, 1, this->initBufferLength * sizeof(half));
113- pipe->InitBuffer(inQueueY, 1, this->initBufferLength * sizeof(half));
114- pipe->InitBuffer(outQueueZ, 1, this->initBufferLength * sizeof(half));
115-}
116-```
117- 
Ddocs/zh/guide/算子实践参考/SIMD算子实现/矢量编程/多核-Tiling切分/尾核Tiling.md+0-113
@@ -1,113 +0,0 @@
1-# 尾核Tiling<a name="ZH-CN_TOPIC_0000002532388125"></a>
2- 
3-对于不同shape的输入进行数据切分时,可能会发生数据无法平均分配到多个核的情况。例如当算子的输入shape为\[1, 1999\],使用核数为8,数据类型为half时,需要计算的数据总量为1 \* 1999 \* sizeof\(half\) = 3998字节,3998字节既不满足32字节对齐,也无法平均分配到8个核上。因此该场景下,对数据进行多核切分后,每个核的计算数据量不同。此种情况下,应该尽可能均匀的分配数据,所有核上的计算数据量有两种情况,将计算量较多的核称为整核,计算量较少的核称为尾核。
4- 
5-**图1** 数据对齐示意图<a name="zh-cn_topic_0000002201317270_fig5261155043319"></a>
6-![](../../../../figures/数据对齐示意图.png "数据对齐示意图")
7- 
8-## Tiling实现<a name="zh-cn_topic_0000002201317270_section209650864618"></a>
9- 
10-- 因为AI处理器在进行数据搬运和Vector计算时,对于搬运的数据长度和Unified Buffer首地址都有必须32字节对齐的要求,**首先待处理数据需要先保证向上对齐到32字节的大小**。该场景下后续搬运和计算的处理细节请参考[非对齐场景](../非对齐场景.md)。如下代码片段展示了将数据对齐到datablock大小的示例:
11- 
12- ```
13- constexpr uint32_t SIZE_OF_HALF = 2;
14- constexpr uint32_t BLOCK_SIZE = 32;
15- constexpr uint32_t NUM_BLOCKS = 8;
16- constexpr uint32_t ALIGN_NUM = BLOCK_SIZE / SIZE_OF_HALF;
17- // shape需要对齐到的32字节,假设原totalLength为1999,向上满足32字节对齐后为2000
18- uint32_t totalLengthAligned = (totalLength % ALIGN_NUM == 0U) ?
19- static_cast<uint32_t>(totalLength) :
20- ((static_cast<uint32_t>(totalLength) + ALIGN_NUM - 1) / ALIGN_NUM) * ALIGN_NUM;
21- ```
22- 
23-- 满足32字节对齐后的数据,应尽可能的均分到每个核上。如果无法均分,那么先将可以均分的部分平均分配,剩余的部分分配给部分核,会有部分核多算一个datablock。为了保证切分后的数据仍是满足32字节对齐的,以ALIGN\_NUM(ALIGN\_NUM个数据为32字节)为粒度,将数据分配到所有核上。在本样例中,数据类型为half,ALIGN\_NUM = BLOCK\_SIZE / sizeof\(half\) = 16。将对齐后的数据总量按ALIGN\_NUM为粒度分成x个数据块,x = 2000 / 16 = 125。
24- 
25- AI处理器的核数NUM\_BLOCKS为8,无法将125个数据块均分到8个核上。按照以下步骤将数据块尽可能的均分到每个核上:
26- 
27- 1. 计算x / NUM\_BLOCKS = 15;
28- 2. 计算x % NUM\_BLOCKS = 5。
29- 
30- 根据上述步骤得出,如果每个核上分配15个数据块,那么将有5个数据块剩余。将这5个剩余的数据块分配到5个核上,这样可以得到5个计算16个数据块的整核和3个计算15个数据块的尾核。下图展示了数据无法均分时多核切分的示例。
31- 
32- **图2** 无法均分到每个核上的示例<a name="zh-cn_topic_0000002201317270_fig68506184496"></a>
33- ![](../../../../figures/无法均分到每个核上的示例.png "无法均分到每个核上的示例")
34- 
35-基于上文,设计如下的算子Tiling结构体成员:
36- 
37-- formerNum:分配到数据量较多的核数,即整核的核数。
38-- tailNum:分配到数据量较少的核数,即尾核的核数。
39-- formerLength:整核计算的数据长度。
40-- tailLength:尾核计算的数据长度。
41- 
42-Tiling参数的计算代码如下:
43- 
44-```
45-constexpr uint32_t NUM_BLOCKS = 8;
46-constexpr uint32_t SIZE_OF_HALF = 2;
47-constexpr uint32_t BLOCK_SIZE = 32;
48-// shape需要对齐到的最小单位
49-constexpr uint32_t ALIGN_NUM = BLOCK_SIZE / SIZE_OF_HALF;
50-...
51-void GenerateTilingData(uint8_t* tilingBuf, uint32_t numBlocks)
52-{
53- // shape需要对齐到的datablock,假设原totalLength为1999,向上满足32字节对齐后为2000
54- uint32_t totalLengthAligned = (totalLength % ALIGN_NUM == 0U) ?
55- static_cast<uint32_t>(totalLength) :
56- ((static_cast<uint32_t>(totalLength) + ALIGN_NUM - 1) / ALIGN_NUM) * ALIGN_NUM;
57- // 核心数为8,一个datablock包含16个数,那么:datablock的总数:2000 / 16 = 125
58- // 有5个核会分到16个datablock:125 % 8 =5,可以称之为整核
59- // 有3个核会分到15个datablock:8 - 5 = 3,可以称之为尾核
60- uint32_t formerNum = (totalLengthAligned / ALIGN_NUM) % numBlocks;
61- uint32_t tailNum = numBlocks - formerNum;
62- // 整核计算的数据长度:totalLengthAligned / NUM_BLOCKS为每个核上计算的元素个数,formerLength为上述元素个数向上32字节对齐的结果
63- uint32_t formerLength =
64- static_cast<uint32_t>(((totalLengthAligned + numBlocks - 1) / numBlocks + ALIGN_NUM - 1) / ALIGN_NUM) * ALIGN_NUM;
65- // 尾核计算的数据长度:totalLengthAligned / NUM_BLOCKS为每个核上计算的元素个数,tailLength为上述元素个数向下32字节对齐的结果
66- uint32_t tailLength = (totalLengthAligned / numBlocks / ALIGN_NUM) * ALIGN_NUM;
67- ...
68-}
69-```
70- 
71-## 算子类实现<a name="zh-cn_topic_0000002201317270_section11477133219466"></a>
72- 
73-在Kernel侧的Init函数中,计算输入在Global Memory上的内存偏移地址时,应对整核和尾核加以区分。
74- 
75-整核上,输入的内存偏移地址计算代码如下:
76- 
77-```
78-xGm.SetGlobalBuffer((__gm__ T*)x + formerLength * AscendC::GetBlockIdx(), formerLength);
79-```
80- 
81-尾核上,计算输入的内存偏移地址时,需在全部整核的数据长度基础上加上尾核的偏移量,代码如下:
82- 
83-```
84-xGm.SetGlobalBuffer((__gm__ T*)x + formerLength * formerNum + tailLength * (AscendC::GetBlockIdx() - formerNum), tailLength);
85-```
86- 
87-完整的Init函数实现代码如下:
88- 
89-```
90-__aicore__ inline void Init(__gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t* z, AddCustomTilingData tiling, AscendC::TPipe* pipeIn)
91-{
92- pipe = pipeIn;
93- if (AscendC::GetBlockIdx() < tiling.formerNum) {
94- this->tileLength = tiling.formerLength;
95- uint64_t offset = tiling.formerLength * AscendC::GetBlockIdx();
96- xGm.SetGlobalBuffer((__gm__ half *)x + offset, tiling.formerLength);
97- yGm.SetGlobalBuffer((__gm__ half *)y + offset, tiling.formerLength);
98- zGm.SetGlobalBuffer((__gm__ half *)z + offset, tiling.formerLength);
99- } else {
100- this->tileLength = tiling.tailLength;
101- uint64_t offset = tiling.formerLength * tiling.formerNum
102- + tiling.tailLength * (AscendC::GetBlockIdx() - tiling.formerNum);
103- xGm.SetGlobalBuffer((__gm__ half *)x +offset, tiling.tailLength);
104- yGm.SetGlobalBuffer((__gm__ half *)y + offset, tiling.tailLength);
105- zGm.SetGlobalBuffer((__gm__ half *)z + offset, tiling.tailLength);
106- }
107- pipe->InitBuffer(inQueueX, 1, this->tileLength * sizeof(half));
108- pipe->InitBuffer(inQueueY, 1, this->tileLength * sizeof(half));
109- pipe->InitBuffer(outQueueZ, 1, this->tileLength * sizeof(half));
110-}
111-```
112- 
113-其余实现与[多核Tiling](多核Tiling.md)中的实现一致,这里不重复进行说明。
Ddocs/zh/guide/算子实践参考/SIMD算子实现/矢量编程/多核-Tiling切分/概述.md+0-34
@@ -1,34 +0,0 @@
1-# 概述<a name="ZH-CN_TOPIC_0000002500468232"></a>
2- 
3-Ascend C核函数是运行在一个核上的处理函数,上述介绍的[基础矢量算子](../基础矢量算子.md)与[TBuf的使用](../TBuf的使用.md)样例均为在单核上运行的算子,不涉及Host侧Tiling实现。矢量算子实现的组成如下图所示。
4- 
5-为了提高算子的执行效率,通常在算子中实现多核并行计算,即对输入数据进行切分,并将不同的数据块分配到不同的核上处理。此外,由于单个核上内部存储Local Memory大小有限,存在无法一次完整地容纳算子的输入和输出数据的场景,因此需要每次搬运一部分输入进行计算然后搬出,再搬运下一部分输入进行计算,直到获得最终的完整结果,这个数据切分、分块计算的过程称之为**Tiling**。切分数据的算法称为Tiling算法或者Tiling策略。根据算子的shape等信息来确定数据切分算法相关参数(比如每次搬运的块大小,以及总共循环多少次)的计算程序,称之为**Tiling实现**,也叫Tiling函数(Tiling Function)。由于Tiling实现中完成的均为标量计算,AI Core并不擅长,所以我们将其独立出来放在Host侧CPU上执行。核函数内部通过解析Host侧传入的Tiling结构体获取Tiling信息,根据Tiling信息控制数据搬入、搬出Local Memory的流程;通过调用计算、数据搬运、内存管理、任务同步API,实现算子逻辑。
6- 
7-**图1** 算子实现组成<a name="zh-cn_topic_0000002256238457_fig2052955220511"></a>
8-![](../../../../figures/算子实现组成.png "算子实现组成")
9- 
10-由于硬件限制,在对输入数据进行数据切分时应遵循以下几个原则:
11- 
12-1. 由于AI Core中Unified Buffer上的物理限制,要求Unified Buffer上的数据存储空间必须保持32字节对齐。
13- - 输入数据不满足32字节对齐时,需要取输入数据长度向上对齐到32字节的长度作为输入数据总长度。
14- - 进行Tiling有关计算时,以32字节为最小单位进行计算。
15- 
16-2. 尽可能最大利用Unified Buffer空间。
17- 
18- AI Core与外部存储交互时会产生性能开销,频繁的进行数据搬运会导致性能瓶颈,因此应尽可能充分利用Unified Buffer空间,减少从Global Memory上搬运数据的次数。
19- 
20-3. AI处理器包含多个AI Core,应该充分均衡利用多核计算能力,将计算均衡分配到多个AI Core上。
21- 
22-本章将基于以上原则对几种典型场景进行说明。
23- 
24-**图2** 多核及Tiling示意图<a name="zh-cn_topic_0000002256238457_fig188456591607"></a>
25-![](../../../../figures/多核及Tiling示意图.png "多核及Tiling示意图")
26- 
27-数据切分示意如上图所示,将长度为TOTAL\_LENGTH的算子输入分配到多个核上进行计算,每个核上计算的数据长度为BLOCK\_LENGTH。对于每个核的计算数据,基于Local Memory的大小进一步切分,切分数据块的个数为TILE\_NUM,得到的每个数据块的长度为TILE\_LENGTH。
28- 
29-根据每个核计算的数据量是否相同、核内每个数据块的数据量是否相同,切分策略可能会存在以下几种场景:
30- 
31-1. 核间均分,核内均分:每个核处理的数据量相同,核内每个数据块的数据量相同。在此场景中,通过[多核Tiling](多核Tiling.md)将数据均匀分配到各个核上执行,每个核上每次计算的数据长度相同。
32-2. 核间均分,核内不均分:每个核处理的数据量相同,核内各数据块的数据量不完全相同。此场景基于多核Tiling,核内数据不能切分为多个数据量相同且32字节对齐的数据块,需要通过[尾块Tiling](尾块Tiling.md)处理尾块数据的计算。
33-3. 核间不均分,核内均分:每个核处理的数据量不同,核内每个数据块的数据量相同。在此场景中,通过[尾核Tiling](尾核Tiling.md)的处理解决数据无法在各核间均匀分配的问题。
34-4. 核间不均分,核内不均分:每个核处理的数据量不同,核内各数据块的数据量不完全相同。该场景下需要同时考虑[尾核&尾块](尾核-尾块.md),处理多核间及核内数据的合理切分。
Adocs/zh/guide/算子实践参考/SIMD算子实现/矢量编程/多核Tiling切分/主块均分.md+127-0
@@ -0,0 +1,127 @@
1+# 主块均分
2+ 
3+长度为`totalLength`的输入数据可以按参与计算的核数和主块长度`mainTileLength`均匀切分,主块均分后无剩余数据。各核处理的数据量相同,每个核处理`tileNum`个主块,核内只包含主块。
4+ 
5+**图 1** 数据切分示意图
6+ 
7+![数据切分示意图](../../../../figures/maintile_even.png "数据切分示意图")
8+ 
9+本场景样例的完整代码请参考[场景0:主块均分](../../../../../../../examples/01_simd_cpp_api/02_features/02_tiling_selector/vector_tiling_strategy/README.md#scenario0-main-tile-only)。样例中固定使用8个核,主块长度`mainTileLength`为3200个`half`元素,数据整体长度为256000个`half`元素。数据平均分配到8个核上处理,每个核计算32000个元素;每个核内共切分成10个主块,不涉及尾块和尾核处理。
10+ 
11+## Tiling实现
12+ 
13+基于本节的切分策略,Tiling需要定义如下参数:
14+ 
15+- blockLength:每个核处理的数据长度,单位为元素。
16+- tileNum:每个核需要计算的主块个数。
17+ 
18+据此定义`MainTileOnlyTiling`结构体,代码如下:
19+ 
20+```cpp
21+struct MainTileOnlyTiling {
22+ uint32_t blockLength; // 每个核处理的元素数量
23+ uint32_t tileNum; // 主块数量
24+};
25+```
26+ 
27+接下来完成Tiling参数的计算。涉及的主要变量包括:逻辑核数`numBlocks`、一个DataBlock的字节数`dataBlockSize`、一个`half`元素的字节数`halfSize`、一个DataBlock包含的`half`元素数量`alignNum`、一个主块包含的`half`元素数量`mainTileLength`。步骤如下:
28+ 
29+1. 根据`dataBlockSize``halfSize`计算`alignNum`,本样例中`alignNum`为16。`totalLength`为输入数据的元素总数,将其按`alignNum`向上对齐后得到对齐后的元素总数`totalLengthAligned`
30+ 
31+ ```cpp
32+ constexpr uint32_t dataBlockSize = 32;
33+ constexpr uint32_t halfSize = 2;
34+ constexpr uint32_t alignNum = dataBlockSize / halfSize;
35+ 
36+ uint32_t totalLengthAligned = AlignUp(totalLength, alignNum);
37+ ```
38+ 
39+2.`totalLengthAligned`除以`numBlocks * mainTileLength`得到每个核均分到的主块数量`mainTileNum`,取余得到主块均分后的剩余元素数量`mainTileRemainder`;再将`mainTileNum`乘以`mainTileLength`得到每个核已分配的元素数量`baseLength``baseTileNum`直接取`mainTileNum`
40+ 
41+ ```cpp
42+ constexpr uint32_t numBlocks = 8;
43+ constexpr uint32_t mainTileLength = 3200;
44+ 
45+ uint32_t mainTileNum = totalLengthAligned / (numBlocks * mainTileLength);
46+ uint32_t mainTileRemainder = totalLengthAligned % (numBlocks * mainTileLength);
47+ uint32_t baseLength = mainTileNum * mainTileLength;
48+ uint32_t baseTileNum = mainTileNum;
49+ ```
50+ 
51+3.`mainTileRemainder`为0时,表示所有数据都能按主块均匀分配到各核,此时使用`MainTileOnlyTiling`结构体传递Tiling切分信息。
52+ 
53+ ```cpp
54+ if (mainTileRemainder == 0U) {
55+ MainTileOnlyTiling tiling{baseLength, baseTileNum};
56+ add_custom<MainTileOnlyTiling><<<numBlocks, 0, stream>>>(xDevice, yDevice, zDevice, tiling);
57+ return;
58+ }
59+ ```
60+ 
61+## 算子类实现
62+ 
63+Kernel侧算子采用[静态Tensor编程](../../../../编程指南/编程模型/AI-Core-SIMD编程/基于Tensor的CPP编程/静态Tensor编程.md)方式实现,通过`LocalTensor`构造函数直接指定Local Memory地址。静态Tensor编程中的内存管理和同步管理请参考对应章节,本节重点说明各核GM地址的计算以及主块数据的循环处理流程。
64+ 
65+- Kernel入口接收Host侧传入的Tiling结构体,并依次调用算子类的`Init``Process`函数完成初始化与计算。主块均分场景中,传入的结构体类型为`MainTileOnlyTiling`
66+ 
67+ ```cpp
68+ template <typename TilingData>
69+ __vector__ __global__ void add_custom(__gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t* z, TilingData tiling)
70+ {
71+ AscendC::InitSocState();
72+ KernelAdd op;
73+ op.Init(x, y, z, tiling);
74+ op.Process();
75+ AscendC::PipeBarrier<PIPE_ALL>();
76+ }
77+ ```
78+ 
79+- 由于数据被分配到多个核上处理,每个核访问的Global Memory地址范围各不相同。`Init`函数通过`tiling.blockLength`获取每个核处理的元素数量,并结合[GetBlockIdx](../../../../../api/SIMD-API/basic_api/tool_interface/system_resources_and_variables/GetBlockIdx.md)计算当前核所处理数据在Global Memory上的起始偏移`offset`,随后将偏移与长度传入`InitGm`完成GM地址设置;同时将`tiling.tileNum`保存到成员变量,供`Process`函数使用。
80+ 
81+ ```cpp
82+ __aicore__ inline void Init(__gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t* z,
83+ MainTileOnlyTiling tiling)
84+ {
85+ uint32_t offset = AscendC::GetBlockIdx() * tiling.blockLength;
86+ InitGm(x, y, z, offset, tiling.blockLength);
87+ this->tileNum = tiling.tileNum;
88+ this->lastTileLength = 0U;
89+ }
90+ ```
91+ 
92+- `InitGm`函数通过[SetGlobalBuffer](../../../../../api/SIMD-API/basic_api/data_structures/GlobalTensor/SetGlobalBuffer.md)为输入`xGm``yGm`和输出`zGm`设置Global Memory地址。以输入x为例,`(__gm__ half*)x + offset`即为当前核处理数据在Global Memory上的起始地址,`length`为该核处理的元素数量。
93+ 
94+ ```cpp
95+ __aicore__ inline void InitGm(
96+ __gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t* z, uint32_t offset, uint32_t length)
97+ {
98+ xGm.SetGlobalBuffer((__gm__ half*)x + offset, length);
99+ yGm.SetGlobalBuffer((__gm__ half*)y + offset, length);
100+ zGm.SetGlobalBuffer((__gm__ half*)z + offset, length);
101+ }
102+ ```
103+ 
104+- `Process`函数基于成员变量`tileNum`循环处理主块数据。`xAddr``yAddr``zAddr`为静态Tensor预先分配的Local Memory地址。循环次数采用`tileNum + (lastTileLength > 0U ? 1U : 0U)`的通用形式计算,主块均分场景下`lastTileLength`为0,循环次数即等于`tileNum`。每轮循环以`mainTileLength`作为当前主块的元素数量,通过`loopIdx * mainTileLength`计算核内偏移`startElement`,依次完成输入搬入、Add计算和输出搬出。
105+ 
106+ ```cpp
107+ __aicore__ inline void Process()
108+ {
109+ AscendC::LocalTensor<half> xLocal(AscendC::TPosition::VECCALC, xAddr, mainTileLength);
110+ AscendC::LocalTensor<half> yLocal(AscendC::TPosition::VECCALC, yAddr, mainTileLength);
111+ AscendC::LocalTensor<half> zLocal(AscendC::TPosition::VECCALC, zAddr, mainTileLength);
112+ 
113+ uint32_t totalLoop = this->tileNum + (this->lastTileLength > 0U ? 1U : 0U);
114+ for (uint32_t loopIdx = 0; loopIdx < totalLoop; loopIdx++) {
115+ uint32_t curLen = (loopIdx < this->tileNum) ? mainTileLength : this->lastTileLength;
116+ uint32_t startElement = loopIdx * mainTileLength;
117+ // ... 同步管理代码省略,请参考完整样例代码 ...
118+ AscendC::DataCopy(xLocal, xGm[startElement], curLen);
119+ AscendC::DataCopy(yLocal, yGm[startElement], curLen);
120+ // ... 同步管理代码省略 ...
121+ AscendC::Add(zLocal, xLocal, yLocal, curLen);
122+ // ... 同步管理代码省略 ...
123+ AscendC::DataCopy(zGm[startElement], zLocal, curLen);
124+ // ... 同步管理代码省略 ...
125+ }
126+ }
127+ ```
Adocs/zh/guide/算子实践参考/SIMD算子实现/矢量编程/多核Tiling切分/尾块均分.md+94-0
@@ -0,0 +1,94 @@
1+# 尾块均分
2+ 
3+该场景中,完成主块分配后仍有剩余数据,但剩余数据无法继续按主块均匀分配到每个核。为了均衡利用多核计算能力,继续以DataBlock为最小单位将主块均分后剩余数据`mainTileRemainder`均衡分配到每个核,每个核分配到`tailBlockNumEachCore`个DataBlock的数据,且按DataBlock均分后无剩余。最终各核处理的数据量相同,核内除主块外还存在等长尾块。
4+ 
5+本场景样例的完整代码请参考[场景1:尾块均分](../../../../../../../examples/01_simd_cpp_api/02_features/02_tiling_selector/vector_tiling_strategy/README.md#scenario1-main-tile-tail-block)。样例中固定使用8个核,主块长度`mainTileLength`为3200个`half`元素,数据整体长度为260096个`half`元素。数据平均分配到8个核上处理,每个核计算32512个元素;每个核内包含10个主块和512个元素的尾块。数据切分示意如下图所示。
6+ 
7+**图 1** 数据切分示意图
8+ 
9+![数据切分示意图](../../../../figures/tailblock_tiling.png "数据切分示意图")
10+ 
11+与主块均分相比,本场景每个核内除主块外还存在尾块,因此Tiling参数在`blockLength``tileNum`基础上新增`lastTileLength`,用于记录尾块的元素数量。
12+ 
13+## Tiling实现
14+ 
15+算子的Tiling结构体定义如下:
16+ 
17+```cpp
18+struct MainTileWithTailBlockTiling {
19+ uint32_t blockLength; // 每个核处理的元素数量
20+ uint32_t tileNum; // 主块数量
21+ uint32_t lastTileLength; // 尾块元素数量
22+};
23+```
24+ 
25+Host侧Tiling沿用[主块均分](主块均分.md)中的DataBlock对齐和主块分配计算,得到:主块均分后的剩余元素数量`mainTileRemainder`、每个核已分配的基础元素数量`baseLength`、以及对应的主块数量`baseTileNum`。当`mainTileRemainder`不为0时,主块均分分支不成立,继续执行以下步骤:
26+ 
27+1. 以DataBlock为最小单位,将主块均分后的剩余数据`mainTileRemainder`均匀分配到每个核上。一个DataBlock包含`alignNum``half`元素,因此代码中以`alignNum`作为均分的单位。用`mainTileRemainder`除以`numBlocks * alignNum`得到每个核分配到的DataBlock数量`tailBlockNumEachCore`,取余得到按DataBlock均分后仍无法均分到每个核的剩余元素数量`remainingTailLength`;再将`tailBlockNumEachCore`乘以`alignNum`得到每个核基础尾块的元素数量`baseLastTileLength`,并累加到`baseLength`上得到每个核的基础元素数量。
28+ 
29+ ```cpp
30+ uint32_t tailBlockNumEachCore = mainTileRemainder / (numBlocks * alignNum);
Chen Ning
Chen NingChen Ning7月16日

增加align num和datablock的关系

likedislike
31+ uint32_t remainingTailLength = mainTileRemainder % (numBlocks * alignNum);
32+ uint32_t baseLastTileLength = tailBlockNumEachCore * alignNum;
33+ baseLength += baseLastTileLength;
34+ ```
35+ 
36+2. 判断`remainingTailLength`是否为0。等于0表示`mainTileRemainder`能以DataBlock为最小单位均匀分配到每个核,此时进入尾块均分场景,使用`MainTileWithTailBlockTiling`结构体传递Tiling切分信息。
37+ 
38+ ```cpp
39+ if (remainingTailLength == 0U) {
40+ MainTileWithTailBlockTiling tiling{baseLength, baseTileNum, baseLastTileLength};
41+ add_custom<MainTileWithTailBlockTiling><<<numBlocks, 0, stream>>>(xDevice, yDevice, zDevice, tiling);
42+ ...
43+ }
44+ ```
45+ 
46+对形状为`(1, 260096)`的输入数据计算后,Tiling结构体内各个变量的值如下:
47+ 
48+```cpp
49+MainTileWithTailBlockTiling tiling{
50+ 32512, // blockLength:每个核计算32512个half,8个核共计算260096个half
51+ 10, // tileNum:每个核包含10个主块
52+ 512 // lastTileLength:每个核包含512个half的尾块
53+};
54+```
55+ 
56+## 算子类实现
57+ 
58+Kernel侧算子仍采用[静态Tensor编程](../../../../编程指南/编程模型/AI-Core-SIMD编程/基于Tensor的CPP编程/静态Tensor编程.md)方式实现。与[主块均分](主块均分.md)相比,本场景中每个核处理的数据长度仍相同,因此Global Memory偏移仍由`tiling.blockLength``GetBlockIdx()`计算;差异在于`Init`函数需要额外保存`tiling.lastTileLength`,供`Process`函数判断最后一次循环是否处理尾块。
59+ 
60+```cpp
61+__aicore__ inline void Init(__gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t* z,
62+ MainTileWithTailBlockTiling tiling)
63+{
64+ uint32_t offset = AscendC::GetBlockIdx() * tiling.blockLength;
65+ InitGm(x, y, z, offset, tiling.blockLength);
66+ this->tileNum = tiling.tileNum;
67+ this->lastTileLength = tiling.lastTileLength;
68+}
69+```
70+ 
71+由于尾块长度`lastTileLength`与主块长度`mainTileLength`不同,`Process`函数需要根据当前循环位置确定本次待处理的数据长度。`totalLoop`为当前核需要处理的循环次数:前`tileNum`轮处理主块,最后一轮处理尾块;`curLen`为本轮处理的元素数量,`startElement`为本轮在核内的元素偏移。
72+ 
73+```cpp
74+__aicore__ inline void Process()
75+{
76+ AscendC::LocalTensor<half> xLocal(AscendC::TPosition::VECCALC, xAddr, mainTileLength);
77+ AscendC::LocalTensor<half> yLocal(AscendC::TPosition::VECCALC, yAddr, mainTileLength);
78+ AscendC::LocalTensor<half> zLocal(AscendC::TPosition::VECCALC, zAddr, mainTileLength);
79+ 
80+ uint32_t totalLoop = this->tileNum + (this->lastTileLength > 0U ? 1U : 0U);
81+ for (uint32_t loopIdx = 0; loopIdx < totalLoop; loopIdx++) {
82+ uint32_t curLen = (loopIdx < this->tileNum) ? mainTileLength : this->lastTileLength;
83+ uint32_t startElement = loopIdx * mainTileLength;
84+ // ... 同步管理代码省略,请参考完整样例代码 ...
85+ AscendC::DataCopy(xLocal, xGm[startElement], curLen);
86+ AscendC::DataCopy(yLocal, yGm[startElement], curLen);
87+ // ... 同步管理代码省略 ...
88+ AscendC::Add(zLocal, xLocal, yLocal, curLen);
89+ // ... 同步管理代码省略 ...
90+ AscendC::DataCopy(zGm[startElement], zLocal, curLen);
91+ // ... 同步管理代码省略 ...
92+ }
93+}
94+```
Adocs/zh/guide/算子实践参考/SIMD算子实现/矢量编程/多核Tiling切分/尾核切分.md+121-0
@@ -0,0 +1,121 @@
1+# 尾核切分
2+ 
3+在实际算子开发中,受输入数据总量与切分单位的制约,并非所有场景都能将计算完全均匀地分配到每个核上。该场景中,主块均分后存在剩余数据`mainTileRemainder`,但剩余数据无法以DataBlock为最小单位均匀分配到每个核(`tailBlockNumEachCore`为0,`remainingTailLength`不为0),只能继续分配给部分核。获得额外DataBlock的核为整核,其余为尾核;此时核间负载不均衡,需要分别处理整核和尾核。由于`tailBlockNumEachCore`为0,各核未分配到基础尾块,因此尾核内只包含主块,整核比尾核多处理一个DataBlock的尾块。数据切分后,前`formerNum`个核为整核,各处理`formerLength`个元素(含主块和尾块);其余核为尾核,各处理`tailLength`个元素(仅含主块)。
4+ 
5+本场景样例的完整代码请参考[场景2:尾核切分](../../../../../../../examples/01_simd_cpp_api/02_features/02_tiling_selector/vector_tiling_strategy/README.md#scenario2-main-tile-tail-core)。样例中固定使用8个核,主块长度`mainTileLength`为3200个`half`元素,数据整体长度为256064个`half`元素。数据按DataBlock对齐后,前4个核各处理32016个元素,后4个核各处理32000个元素;整核包含10个主块和16个元素的尾块,尾核只包含10个主块。数据切分示意如下图所示。
6+ 
7+**图 1** 数据切分示意图
8+ 
9+![数据切分示意图](../../../../figures/tailcore_tiling.png "数据切分示意图")
10+ 
11+本场景中整核和尾核处理的元素数量、主块数量及尾块情况不同,无法仅用一组`blockLength``tileNum``lastTileLength`描述所有核。因此Tiling参数新增`formerNum`记录整核数量,并分别使用`former*``tail*`参数描述记录整核和尾核相关Tiling信息的参数;由于尾核内只包含主块,无需定义`tailLastTileLength`。Tiling结构体包含以下成员:
12+ 
13+- formerNum:分配到数据量较多的核数,即整核的核数。
14+- formerLength:整核计算的元素数量。
15+- formerTileNum:整核中主块的数量。
16+- formerLastTileLength:整核中尾块的元素数量。
17+- tailLength:尾核计算的元素数量。
18+- tailTileNum:尾核中主块的数量。
19+ 
20+## Tiling实现
21+ 
22+Tiling结构体定义如下:
23+ 
24+```cpp
25+struct MainTileWithTailCoreTiling {
26+ uint32_t formerNum; // 整核数量
27+ uint32_t formerLength; // 整核处理的元素数量
28+ uint32_t formerTileNum; // 整核中主块的数量
29+ uint32_t formerLastTileLength; // 整核中尾块的元素数量
30+ uint32_t tailLength; // 尾核处理的元素数量
31+ uint32_t tailTileNum; // 尾核中主块的数量
32+};
33+```
34+ 
35+Host侧Tiling沿用[尾块均分](尾块均分.md)中的DataBlock对齐、主块分配和基础尾块计算,得到:按DataBlock均分后仍无法均分到每个核的剩余元素数量`remainingTailLength`、每个核分配到的DataBlock数量`tailBlockNumEachCore`、每个核基础尾块的元素数量`baseLastTileLength`、每个核已分配的基础元素数量`baseLength`,以及对应的主块数量`baseTileNum`。当`remainingTailLength`不为0时,尾块均分分支不成立,继续执行以下步骤:
36+ 
37+1. `remainingTailLength`对应的剩余数据无法均分到每个核,只能分配给前`formerNum`个整核。用`remainingTailLength`除以`alignNum`得到整核数量`formerNum`;整核比尾核多处理一个DataBlock(含`alignNum``half`元素),因此`formerLength``baseLength`加上`alignNum`得到,而`tailLength`直接取`baseLength`
38+ 
39+ ```cpp
40+ if (remainingTailLength == 0U) {
41+ ...
42+ return;
43+ }
44+ 
45+ uint32_t formerNum = remainingTailLength / alignNum;
46+ uint32_t formerLength = baseLength + alignNum;
47+ uint32_t tailLength = baseLength;
48+ ```
49+ 
50+2. 整核和尾核的主块数量相同,均等于`baseTileNum`
51+ 
52+ ```cpp
53+ uint32_t formerTileNum = baseTileNum;
54+ uint32_t tailTileNum = baseTileNum;
55+ ```
56+ 
57+3. 进一步判断`tailBlockNumEachCore`是否为0。等于0表示各核未分配到基础尾块(`baseLastTileLength`为0),因此尾核内只包含主块;整核比尾核多处理一个DataBlock,因此整核尾块长度`formerLastTileLength`直接设置为`alignNum`。此时进入尾核切分场景,使用`MainTileWithTailCoreTiling`结构体传递Tiling切分信息。
58+ 
59+ ```cpp
60+ if (tailBlockNumEachCore == 0U) {
61+ uint32_t formerLastTileLength = alignNum;
62+ MainTileWithTailCoreTiling tiling{
63+ formerNum, formerLength, formerTileNum, formerLastTileLength, tailLength, tailTileNum};
64+ add_custom<MainTileWithTailCoreTiling><<<numBlocks, 0, stream>>>(xDevice, yDevice, zDevice, tiling);
65+ }
66+ ```
67+ 
68+对形状为`(1, 256064)`的输入数据计算后,Tiling结构体内各个变量的值如下:
69+ 
70+```cpp
71+MainTileWithTailCoreTiling tiling{
72+ 4, // formerNum:前4个核为整核
73+ 32016, // formerLength:整核计算32016个half
74+ 10, // formerTileNum:整核包含10个主块
75+ 16, // formerLastTileLength:整核包含16个half的尾块
76+ 32000, // tailLength:尾核计算32000个half
77+ 10 // tailTileNum:尾核包含10个主块
78+};
79+```
80+ 
81+## 算子类实现
82+ 
83+Kernel侧算子仍采用[静态Tensor编程](../../../../编程指南/编程模型/AI-Core-SIMD编程/基于Tensor的CPP编程/静态Tensor编程.md)方式实现。与[尾块均分](尾块均分.md)相比,本场景中整核和尾核处理的数据长度不同,因此`Init`函数需要根据`GetBlockIdx()`判断当前核是整核还是尾核,并分别设置Global Memory偏移和循环参数。`blockIdx`表示当前逻辑核的索引。
84+ 
85+整核使用`formerLength``formerTileNum``formerLastTileLength`,计算如下:
86+ 
87+```cpp
88+uint32_t offset = tiling.formerLength * blockIdx;
89+InitGm(x, y, z, offset, tiling.formerLength);
90+```
91+ 
92+尾核使用`tailLength``tailTileNum`,且尾核内只包含主块,因此`lastTileLength`设置为0。尾核的Global Memory偏移需要在全部整核数据之后继续计算,代码如下:
93+ 
94+```cpp
95+uint32_t offset = tiling.formerLength * tiling.formerNum + tiling.tailLength * (blockIdx - tiling.formerNum);
96+InitGm(x, y, z, offset, tiling.tailLength);
97+```
98+ 
99+完整的`Init`函数实现代码如下:
100+ 
101+```cpp
102+__aicore__ inline void Init(__gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t* z,
103+ MainTileWithTailCoreTiling tiling)
104+{
105+ uint32_t blockIdx = AscendC::GetBlockIdx();
106+ if (blockIdx < tiling.formerNum) {
107+ uint32_t offset = tiling.formerLength * blockIdx;
108+ InitGm(x, y, z, offset, tiling.formerLength);
109+ this->tileNum = tiling.formerTileNum;
110+ this->lastTileLength = tiling.formerLastTileLength;
111+ } else {
112+ uint32_t offset =
113+ tiling.formerLength * tiling.formerNum + tiling.tailLength * (blockIdx - tiling.formerNum);
114+ InitGm(x, y, z, offset, tiling.tailLength);
115+ this->tileNum = tiling.tailTileNum;
116+ this->lastTileLength = 0U;
117+ }
118+}
119+```
120+ 
121+完成`Init`后,`Process`函数仍按照`tileNum``lastTileLength`进行循环处理。整核需要处理主块和尾块,尾核只处理主块;循环逻辑与[尾块均分](尾块均分.md)中的`Process`函数一致,此处不再展开。
Adocs/zh/guide/算子实践参考/SIMD算子实现/矢量编程/多核Tiling切分/尾核尾块切分.md+105-0
@@ -0,0 +1,105 @@
1+# 尾核尾块切分
2+ 
3+该场景中,主块均分后存在剩余数据`mainTileRemainder`,剩余数据部分能以DataBlock为最小单位均匀分配到每个核,但仍有少量DataBlock无法均分(`remainingTailLength`不为0),只能继续分配给部分核。获得额外DataBlock的核为整核,其余为尾核;整核和尾核都包含尾块,且整核处理的数据量更多。由于`tailBlockNumEachCore`不为0,各核都分配到基础尾块,因此尾核内也存在尾块;整核在尾核基础上多处理一个DataBlock。数据切分后,前`formerNum`个核为整核,各处理`formerLength`个元素(含主块和较长的尾块);其余核为尾核,各处理`tailLength`个元素(含主块和较短的尾块)。
4+ 
5+本场景样例的完整代码请参考[场景3:尾核尾块切分](../../../../../../../examples/01_simd_cpp_api/02_features/02_tiling_selector/vector_tiling_strategy/README.md#scenario3-tail-block-tail-core)。样例中固定使用8个核,主块长度`mainTileLength`为3200个`half`元素,数据整体长度为258112个`half`元素。数据按DataBlock对齐后,前4个核各处理32272个元素,后4个核各处理32256个元素;整核包含10个主块和272个元素的尾块,尾核包含10个主块和256个元素的尾块。数据切分示意如下图所示。
6+ 
7+**图 1** 数据切分示意图
8+ 
9+![数据切分示意图](../../../../figures/tailblock-tailcore.png "数据切分示意图")
10+ 
11+本场景中整核和尾核处理的元素数量、主块数量及尾块长度均不同,需要分别记录两类核的Tiling参数。与[尾核切分](尾核切分.md)相比,由于尾核内也存在尾块,Tiling参数在`former*``tail*`基础上新增`tailLastTileLength`记录尾核的尾块元素数量。Tiling结构体包含以下成员:
12+ 
13+- formerNum:分配到数据量较多的核数,即整核的核数。
14+- formerLength:整核计算的元素数量。
15+- formerTileNum:整核中主块的数量。
16+- formerLastTileLength:整核中尾块的元素数量。
17+- tailLength:尾核计算的元素数量。
18+- tailTileNum:尾核中主块的数量。
19+- tailLastTileLength:尾核中尾块的元素数量。
20+ 
21+## Tiling实现
22+ 
23+Tiling结构体定义如下:
24+ 
25+```cpp
26+struct TailBlockAndTailCoreTiling {
27+ uint32_t formerNum; // 整核数量
28+ uint32_t formerLength; // 整核处理的元素数量
29+ uint32_t formerTileNum; // 整核中主块的数量
30+ uint32_t formerLastTileLength; // 整核中尾块的元素数量
31+ uint32_t tailLength; // 尾核处理的元素数量
32+ uint32_t tailTileNum; // 尾核中主块的数量
33+ uint32_t tailLastTileLength; // 尾核中尾块的元素数量
34+};
35+```
36+ 
37+Host侧Tiling沿用[尾核切分](尾核切分.md)中的DataBlock对齐、主块分配、基础尾块计算及整核/尾核划分,得到:整核数量`formerNum`、每个整核处理的元素数量`formerLength`、每个尾核处理的元素数量`tailLength`、每个核基础尾块的元素数量`baseLastTileLength`、整核中主块的数量`formerTileNum`,以及尾核中主块的数量`tailTileNum`。当`remainingTailLength``tailBlockNumEachCore`均不为0时,尾核切分分支不成立,进入尾核尾块切分场景。此时整核比尾核多处理一个DataBlock,整核尾块长度`formerLastTileLength`为基础尾块长度`baseLastTileLength``alignNum`(一个DataBlock包含的`half`元素数量)之和;若两者之和等于`mainTileLength`,则将其合并为一个主块(`formerTileNum`加1,`formerLastTileLength`置0)。尾核尾块长度`tailLastTileLength`直接取`baseLastTileLength`
38+ 
39+```cpp
40+uint32_t formerLastTileLength = baseLastTileLength + alignNum;
41+if (formerLastTileLength == mainTileLength) {
42+ formerTileNum += 1U;
43+ formerLastTileLength = 0U;
44+}
45+uint32_t tailLastTileLength = baseLastTileLength;
46+TailBlockAndTailCoreTiling tiling{
47+ formerNum, formerLength, formerTileNum, formerLastTileLength,
48+ tailLength, tailTileNum, tailLastTileLength};
49+add_custom<TailBlockAndTailCoreTiling><<<numBlocks, 0, stream>>>(xDevice, yDevice, zDevice, tiling);
50+```
51+ 
52+对形状为`(1, 258112)`的输入数据计算后,Tiling结构体内各个变量的值如下:
53+ 
54+```cpp
55+TailBlockAndTailCoreTiling tiling{
56+ 4, // formerNum:前4个核为整核
57+ 32272, // formerLength:整核计算32272个half
58+ 10, // formerTileNum:整核包含10个主块
59+ 272, // formerLastTileLength:整核包含272个half的尾块
60+ 32256, // tailLength:尾核计算32256个half
61+ 10, // tailTileNum:尾核包含10个主块
62+ 256 // tailLastTileLength:尾核包含256个half的尾块
63+};
64+```
65+ 
66+## 算子类实现
67+ 
68+Kernel侧算子仍采用[静态Tensor编程](../../../../编程指南/编程模型/AI-Core-SIMD编程/基于Tensor的CPP编程/静态Tensor编程.md)方式实现。与[尾核切分](尾核切分.md)相比,本场景中整核和尾核都包含尾块,因此`Init`函数需要根据`GetBlockIdx()`判断当前核是整核还是尾核,并分别设置Global Memory偏移、`tileNum``lastTileLength`
69+整核使用`formerLength``formerTileNum``formerLastTileLength`,计算如下:
70+ 
71+```cpp
72+uint32_t offset = tiling.formerLength * blockIdx;
73+InitGm(x, y, z, offset, tiling.formerLength);
74+```
75+ 
76+尾核使用`tailLength``tailTileNum``tailLastTileLength`。尾核的Global Memory偏移需要在全部整核数据之后继续计算,代码如下:
77+ 
78+```cpp
79+uint32_t offset = tiling.formerLength * tiling.formerNum + tiling.tailLength * (blockIdx - tiling.formerNum);
80+InitGm(x, y, z, offset, tiling.tailLength);
81+```
82+ 
83+完整的`Init`函数实现代码如下:
84+ 
85+```cpp
86+__aicore__ inline void Init(__gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t* z,
87+ TailBlockAndTailCoreTiling tiling)
88+{
89+ uint32_t blockIdx = AscendC::GetBlockIdx();
90+ if (blockIdx < tiling.formerNum) {
91+ uint32_t offset = tiling.formerLength * blockIdx;
92+ InitGm(x, y, z, offset, tiling.formerLength);
93+ this->tileNum = tiling.formerTileNum;
94+ this->lastTileLength = tiling.formerLastTileLength;
95+ } else {
96+ uint32_t offset =
97+ tiling.formerLength * tiling.formerNum + tiling.tailLength * (blockIdx - tiling.formerNum);
98+ InitGm(x, y, z, offset, tiling.tailLength);
99+ this->tileNum = tiling.tailTileNum;
100+ this->lastTileLength = tiling.tailLastTileLength;
101+ }
102+}
103+```
104+ 
105+完成`Init`后,`Process`函数仍按照`tileNum``lastTileLength`进行循环处理。整核和尾核都需要处理主块和尾块,循环逻辑与[尾块均分](尾块均分.md)中的`Process`函数一致,此处不再展开。
Adocs/zh/guide/算子实践参考/SIMD算子实现/矢量编程/多核Tiling切分/概述.md+45-0
@@ -0,0 +1,45 @@
1+# 概述
2+ 
3+Ascend C核函数是在Device侧执行的并行函数。为了提高算子的执行效率,通常采用多核并行计算,将输入数据切分后分配到不同的逻辑核上处理;同时,由于单个核的Local Memory容量有限,无法一次完整容纳算子的输入和输出数据,需要分批搬运数据进行计算。这种对输入数据进行切分、分块计算的过程称为**Tiling**
4+ 
5+切分数据的算法称为Tiling算法或Tiling策略;根据算子shape等信息计算切分相关参数(如每次搬运的块大小、循环次数等)的程序,称为**Tiling实现**,也叫Tiling函数(Tiling Function)。[基础矢量算子](../基础矢量算子.md)与[TBuf的使用](../TBuf的使用.md)样例均在单核上运行,不涉及Host侧Tiling实现。
6+ 
7+由于Tiling实现完成的均为标量计算,而AI Core并不擅长这类计算,因此通常将其独立出来由Host侧执行;对于动态shape算子,切分参数无法在编译期确定,也需要在Host侧计算。
8+ 
9+开发者需要分析并设计Tiling参数、定义Tiling结构体,在Host侧通过上下文获取输入输出shape信息,根据shape信息计算Tiling参数并设置到对应的Tiling结构体中;随后通过核函数入口参数将Tiling信息传入核函数。核函数内部解析Host侧传入的Tiling结构体,根据Tiling信息控制数据搬入、搬出Local Memory的流程,并完成计算逻辑。
10+ 
11+**图 1** 算子实现组成
12+ 
13+![算子实现组成](../../../../figures/operator_implementation_components.png "算子实现组成")
14+ 
15+如上图所示,算子实现由Device侧的Kernel实现与Host侧的Tiling实现两部分组成:Kernel实现负责在Device侧完成数据搬运、矢量计算、内存管理和任务同步等逻辑;Tiling实现用于在Kernel执行前,根据输入shape等信息在Host侧计算Kernel所需的切分参数。对于输入和输出shape固定、切分策略固定的简单算子,Host侧Tiling实现可以省略;对于动态shape或多核并行场景,每个核处理的数据量、核内分块数量等参数通常需要在Host侧计算后传递给Kernel使用。
16+ 
17+对输入数据进行切分时应遵循以下原则:
18+ 
19+1. 以DataBlock为最小切分单位。受硬件限制,进行Unified Buffer相关的数据搬运和矢量计算时,搬运的数据长度和操作数的起始地址都需要保证32字节对齐,即以DataBlock为单位进行操作,一个DataBlock大小为32字节;输入数据不满足DataBlock大小对齐要求时,需按DataBlock大小向上对齐。
20+ 
21+2. 充分利用Unified Buffer空间。AI Core与外部存储交互时会产生性能开销,频繁地进行数据搬运会导致性能瓶颈,因此应尽可能充分利用Unified Buffer空间,减少从Global Memory搬运数据的次数。
22+ 
23+3. 均衡利用多核计算能力。AI处理器包含多个AI Core,应将计算均衡分配到各核上,充分发挥多核并行优势。
24+ 
25+本章基于[矢量计算多核Tiling切分策略样例](../../../../../../../examples/01_simd_cpp_api/02_features/02_tiling_selector/vector_tiling_strategy/README.md),以两个一维Tensor相加的Add矢量算子为例,对多核Tiling切分的典型场景进行说明。样例实现文件为[vector_tiling_strategy.asc](../../../../../../../examples/01_simd_cpp_api/02_features/02_tiling_selector/vector_tiling_strategy/vector_tiling_strategy.asc)。
26+ 
27+**图 2** 多核Tiling示意图<a name="fig-2"></a>
28+ 
29+![多核Tiling示意图](../../../../figures/vector_tiling_intro.png "多核Tiling示意图")
30+ 
31+如上图所示,将长度为`totalLength`的算子输入分配到多个核上进行计算。Host侧Tiling先将输入数据量按DataBlock对齐,再按主块长度`mainTileLength`在各核上分配相同数量的主块,随后把剩余数据以DataBlock为最小单位尽量均衡地分配到各核。当剩余数据能均分到每个核时,各核处理的数据量相同;无法均分时,各核处理的数据量分为两类——处理数据量较多的整核和处理数据量较少的尾核,其中整核数量记为`formerNum`。对于单个核的计算数据,核内先处理`tileNum`个长度为`mainTileLength`的主块;如果最后剩余的数据长度不足一个主块,则通过`lastTileLength`记录尾块长度。Tiling切分完成后,上述参数被封装到Tiling结构体并通过核函数入口传入Kernel侧;Kernel侧根据`GetBlockIdx()`和Tiling参数计算当前核需处理的输入数据的Global Memory偏移,并按`tileNum``lastTileLength`循环完成数据搬入、计算和搬出。
32+ 
33+本章涉及如下概念:
34+ 
35+- 主块:基于Local Memory可用空间确定的单个核一次处理的最大数据块。主块长度使用`mainTileLength`表示,且需要按32字节对齐。
36+- 尾块:单个核内最后一个长度小于`mainTileLength`的数据块。尾块长度使用`lastTileLength`表示。
37+- 整核:核间数据无法完全均分时,处理数据量较多的核。
38+- 尾核:核间数据无法完全均分时,处理数据量较少的核。
39+ 
40+根据数据切分的情况,有如下几种场景:
41+ 
42+1. [主块均分](主块均分.md):输入数据可以按参与计算的核数和主块长度均匀切分。完成主块分配后没有剩余数据,各核处理的数据量相同,且核内只包含主块。
43+2. [尾块均分](尾块均分.md):完成主块分配后仍有剩余数据,但剩余数据能以DataBlock为最小单位均匀分配到每个核。各核处理的数据量相同,核内除主块外还存在等长尾块。
44+3. [尾核切分](尾核切分.md):完成主块分配后仍有剩余数据,但剩余数据不足以为每个核各分配一个DataBlock,只能以DataBlock为最小单位分配给部分核。获得额外DataBlock的核为整核,其余为尾核;尾核内只包含主块。
45+4. [尾核尾块切分](尾核尾块切分.md):完成主块分配后仍有剩余数据,每个核都能分配到若干DataBlock,但仍有少量DataBlock只能分配给部分核。此时既存在处理数据量更多的整核,尾核也需要处理尾块。
Mdocs/zh/guide/算子实践参考/SIMD算子实现/矢量编程/概述.md+5-4
@@ -4,10 +4,11 @@
4 4 
5- [基础矢量算子](基础矢量算子.md):开发一个简单的Add矢量算子。5- [基础矢量算子](基础矢量算子.md):开发一个简单的Add矢量算子。
6- [TBuf的使用](TBuf的使用.md):在算子计算过程中使用临时空间存储运算的中间结果。6- [TBuf的使用](TBuf的使用.md):在算子计算过程中使用临时空间存储运算的中间结果。
7-- [多核Tiling](多核-Tiling切分/多核Tiling.md):算子在AI处理器的上运行,所有核的计算数据量相等且32字节对齐7+- [多核Tiling切分](多核Tiling切分/概述.md):算子需要多核计算或支持动态shape时对输入数据进行切分、分块计算,并将不同数据块分配到多个AI Core上处理
8-- [Tiling](多核-Tiling切分/Tiling.md):算子在AI处理器的多个核上运行,所有核的计算数据量相等,每个核上除最后一个数据块(尾块)外,其余数据块的数据量相等,每个核都需要处理尾块数据的计算8+ - [均分](多核Tiling切分/均分.md):算子在AI处理器的多个核上运行,所有核的计算数据量相等且32字节对齐
9-- [尾核Tiling](多核-Tiling切分/尾核Tiling.md):算子在AI处理器的多个核上运行,数据无法平均分配到每个核。将所有核分为多个整核和多个尾核,整核的计算数据量相等,核的计算数据量相等。9+ - [尾块均分](多核Tiling切分/尾块均分.md):算子在AI处理器的多个核上运行,所有核的计算数据量相等,每个上除最后一个数据块(尾块)外,其余数据块的数据量相等,每个核都需要处理尾块数据的计算
10-- [尾核&尾块](多核-Tiling切分/尾核-尾块.md):算子在AI处理器的多个核上运行,数据无法平均分配到每个核,同时每个内的数据无法均,除最后一数据块(块)外其余数据块的数据量相等,每个核都需要单独处理块数据的计算。10+ - [尾核切分](多核Tiling切分/尾核切分.md):算子在AI处理器的多个核上运行,数据无法平均分配到每个核。将所有核分为多整核和多个整核计算数据量相等,尾的计算数据量相等
11+ - [尾核尾块切分](多核Tiling切分/尾核尾块切分.md):算子在AI处理器的多个核上运行,数据无法平均分配到每个核,同时每个核内的数据无法均分,除最后一个数据块(尾块)外,其余数据块的数据量相等,每个核都需要单独处理尾块数据的计算。
11- [DoubleBuffer场景](DoubleBuffer场景.md):开启double buffer,算子中的多条流水并行执行。12- [DoubleBuffer场景](DoubleBuffer场景.md):开启double buffer,算子中的多条流水并行执行。
12- [Broadcast场景](Broadcast场景.md):算子中两个输入的shape(形状)不相等,需要将一个输入的shape进行Broadcast(广播)后,再执行计算。13- [Broadcast场景](Broadcast场景.md):算子中两个输入的shape(形状)不相等,需要将一个输入的shape进行Broadcast(广播)后,再执行计算。
13- [非对齐场景](非对齐场景.md):更多数据非32字节对齐场景的处理方案。14- [非对齐场景](非对齐场景.md):更多数据非32字节对齐场景的处理方案。
Mdocs/zh/guide/算子实践参考/SIMD算子性能优化/Tiling策略/核间负载均衡.md+2-0
@@ -4,6 +4,8 @@
4 4 
5【描述】AI处理器的物理核数是固定的,当L2 Cache切分之后,可能发生部分核有计算拖尾的情况,即每次所有核计算量除以每个核处理的数据量不能被核数整除,导致最后需要部分尾核来计算尾块数据。而在尾核计算时,部分核始终处于空闲状态,从而使得算子的整体性能变差。如[图1](#fig26395321518),假设总的数据量为TotalSize,L2 Cache切分之后分为两份TotalSize / 2,每个核每次的计算量为TotalSize / 2 / 25,即需要25个核进行处理,由于AI处理器的核数为20,因此每次计算时,1到5核的每个核需要多算一份数据,导致发生拖尾的情况。5【描述】AI处理器的物理核数是固定的,当L2 Cache切分之后,可能发生部分核有计算拖尾的情况,即每次所有核计算量除以每个核处理的数据量不能被核数整除,导致最后需要部分尾核来计算尾块数据。而在尾核计算时,部分核始终处于空闲状态,从而使得算子的整体性能变差。如[图1](#fig26395321518),假设总的数据量为TotalSize,L2 Cache切分之后分为两份TotalSize / 2,每个核每次的计算量为TotalSize / 2 / 25,即需要25个核进行处理,由于AI处理器的核数为20,因此每次计算时,1到5核的每个核需要多算一份数据,导致发生拖尾的情况。
6 6 
7+更详细的多核Tiling切分过程请参见[多核Tiling切分](../../SIMD算子实现/矢量编程/多核Tiling切分/概述.md)。
8+ 
7【反例】9【反例】
8 10 
9**图1** 计算拖尾示意图<a name="fig26395321518"></a> 11**图1** 计算拖尾示意图<a name="fig26395321518"></a>
Mexamples/01_simd_cpp_api/02_features/02_tiling_selector/vector_tiling_strategy/CMakeLists.txt+4-4
@@ -15,10 +15,10 @@ set(CMAKE_ASC_ARCHITECTURES "dav-2201" CACHE STRING "NPU architecture: dav-2201,
15 15 
16# 场景参数(通过命令行传入)16# 场景参数(通过命令行传入)
17# SCENARIO_NUM: Tiling切分场景编号17# SCENARIO_NUM: Tiling切分场景编号
18-# 0 - 主块18+# 0 - 主块均分
19-# 1 - 主块+尾块19+# 1 - 尾块均分
20-# 2 - 主块+尾核20+# 2 - 尾核切分
21-# 3 - 尾块+尾21+# 3 - 尾核尾块切分
22set(SCENARIO_NUM "0" CACHE STRING "Tiling scenario number: 0-3")22set(SCENARIO_NUM "0" CACHE STRING "Tiling scenario number: 0-3")
23 23 
24message(STATUS "[INFO] SCENARIO_NUM is ${SCENARIO_NUM}")24message(STATUS "[INFO] SCENARIO_NUM is ${SCENARIO_NUM}")
Mexamples/01_simd_cpp_api/02_features/02_tiling_selector/vector_tiling_strategy/README.md+59-35
@@ -4,7 +4,7 @@
4 4 
5本样例以两个一维Tensor相加为例,介绍矢量计算多核Tiling切分策略。该策略根据输入数据量计算不同的核间和核内切分参数,使数据在多个AI Core之间尽量均衡分配,从而充分利用多核计算能力实现高效计算。5本样例以两个一维Tensor相加为例,介绍矢量计算多核Tiling切分策略。该策略根据输入数据量计算不同的核间和核内切分参数,使数据在多个AI Core之间尽量均衡分配,从而充分利用多核计算能力实现高效计算。
6 6 
7-样例固定使用8个AI Core,核内主块数据量为3200个`half`元素(即200个DataBlock),通过CMake编译参数`SCENARIO_NUM`选择不同数据量场景。7+样例固定使用8个,核内主块数据量为3200个`half`元素(即200个DataBlock),通过CMake编译参数`SCENARIO_NUM`选择不同数据量场景。
8 8 
9## 本样例支持的产品及CANN软件版本9## 本样例支持的产品及CANN软件版本
10 10 
@@ -31,30 +31,38 @@
31 31 
32本样例实现两个一维Tensor相加,计算逻辑为`z = x + y`。样例通过CMake编译参数`SCENARIO_NUM`选择不同数据量场景,对应不同的Tiling切分场景,所有场景数据格式为ND,输入输出均为`half`类型,核函数名为`add_custom`32本样例实现两个一维Tensor相加,计算逻辑为`z = x + y`。样例通过CMake编译参数`SCENARIO_NUM`选择不同数据量场景,对应不同的Tiling切分场景,所有场景数据格式为ND,输入输出均为`half`类型,核函数名为`add_custom`
33 33 
34-样例固定使用8个AI Core,主块数据量为3200个`half`元素,其中“主块”(`MAIN_TILE_LENGTH`)是用户基于UB可用空间确定的一次可以处理的最大数据量,且满足32字节对齐。34+样例固定使用8个,主块数据量为3200个`half`元素,其中“主块”(`mainTileLength`)是用户基于UB可用空间确定的一次可以处理的最大数据量,且满足32字节对齐。
35 35 
36-**场景0:仅主块**36+<a name="scenario0-main-tile-only"></a>
37+ 
38+### 场景0:主块均分
37 39 
38- 输入:`x``y`均为[1, 256000]个`half`元素40- 输入:`x``y`均为[1, 256000]个`half`元素
39- 输出:`z`为[1, 256000]个`half`元素41- 输出:`z`为[1, 256000]个`half`元素
40- Tiling切分:8个核各处理32000个元素;每个核包含10个主块,每个主块3200个元素42- Tiling切分:8个核各处理32000个元素;每个核包含10个主块,每个主块3200个元素
41-- 说明:所有核处数据量相同,核内只包含主块43+- 说明:所有核处数据量相同,核内只包含主块
42 44 
43-**场景1:主块+尾块**45+<a name="scenario1-main-tile-tail-block"></a>
46+ 
47+### 场景1:尾块均分
44 48 
45- 输入:`x``y`均为[1, 260096]个`half`元素49- 输入:`x``y`均为[1, 260096]个`half`元素
46- 输出:`z`为[1, 260096]个`half`元素50- 输出:`z`为[1, 260096]个`half`元素
47- Tiling切分:8个核各处理32512个元素;每个核包含10个主块和512个元素的尾块51- Tiling切分:8个核各处理32512个元素;每个核包含10个主块和512个元素的尾块
48- 说明:所有核处理数据量相同,每个核在主块后都有等长尾块52- 说明:所有核处理数据量相同,每个核在主块后都有等长尾块
49 53 
50-**场景2:主块+尾核**54+<a name="scenario2-main-tile-tail-core"></a>
55+ 
56+### 场景2:尾核切分
51 57 
52- 输入:`x``y`均为[1, 256064]个`half`元素58- 输入:`x``y`均为[1, 256064]个`half`元素
53- 输出:`z`为[1, 256064]个`half`元素59- 输出:`z`为[1, 256064]个`half`元素
54- Tiling切分:前4个整核各处理32016个元素,后4个尾核各处理32000个元素;整核包含10个主块和16个元素的尾块,尾核包含10个主块60- Tiling切分:前4个整核各处理32016个元素,后4个尾核各处理32000个元素;整核包含10个主块和16个元素的尾块,尾核包含10个主块
55- 说明:前4个整核比尾核多处理1个DataBlock,尾核无尾块61- 说明:前4个整核比尾核多处理1个DataBlock,尾核无尾块
56 62 
57-**场景3:尾块+尾核**63+<a name="scenario3-tail-block-tail-core"></a>
64+ 
65+### 场景3:尾核尾块切分
58 66 
59- 输入:`x``y`均为[1, 258112]个`half`元素67- 输入:`x``y`均为[1, 258112]个`half`元素
60- 输出:`z`为[1, 258112]个`half`元素68- 输出:`z`为[1, 258112]个`half`元素
@@ -113,42 +121,58 @@
113 totalLengthAligned = AlignUp(totalLength, alignNum);121 totalLengthAligned = AlignUp(totalLength, alignNum);
114 ```122 ```
115 123 
116-2. 先在所有核上分配相同数量的主块`MAIN_TILE_LENGTH`。124+2. 先在所有核上分配相同数量的主块`mainTileLength`。
117 125 
118 ```cpp126 ```cpp
119- mainTileNum = totalLengthAligned / (numBlocks * MAIN_TILE_LENGTH);127+ mainTileNum = totalLengthAligned / (numBlocks * mainTileLength);
120- mainTileRemainder = totalLengthAligned % (numBlocks * MAIN_TILE_LENGTH);128+ mainTileRemainder = totalLengthAligned % (numBlocks * mainTileLength);
121 ```129 ```
122 130 
123- 此时每个核至少处理`mainTileNum * MAIN_TILE_LENGTH`个元素,`mainTileRemainder`表示完成主块分配后的剩余数据量。131+ 此时每个核至少处理`mainTileNum * mainTileLength`个元素,`mainTileRemainder`表示完成主块分配后的剩余数据量。当`mainTileRemainder`为0时,进入主块均分场景并结束Tiling计算。
124- 
125-3. `mainTileRemainder`再按DataBlock给所有核补齐等长尾块。
126 132 
127 ```cpp133 ```cpp
128- tailBlockNumEachCore = mainTileRemainder / (numBlocks * alignNum);134+ if (mainTileRemainder == 0) {
129- formerCoreRemainder = mainTileRemainder % (numBlocks * alignNum);135+ // 使用MainTileOnlyTiling。
130- baseLength = mainTileNum * MAIN_TILE_LENGTH + tailBlockNumEachCore * alignNum;136+ return;
131- ```
132- 
133- `baseLength`表示每个核处理的基础数据量。`formerCoreRemainder`表示每核补齐等长DataBlock尾块后,剩余需要分给前若干个核的数据量。
134- 
135-4. 最后剩余的DataBlock分给前若干个整核。
136- 
137- ```cpp
138- if (formerCoreRemainder == 0) {
139- formerNum = numBlocks;
140- formerLength = baseLength;
141- tailNum = 0;
142- tailLength = 0;
143- } else {
144- formerNum = formerCoreRemainder / alignNum;
145- formerLength = baseLength + alignNum;
146- tailNum = numBlocks - formerNum;
147- tailLength = baseLength;
148 }137 }
149 ```138 ```
150 139 
151- `formerCoreRemainder == 0`存在尾核,所有核都处理`baseLength`个元素。`formerCoreRemainder != 0`时,`formerNum`表示整核数量,前`formerNum`个核处理`formerLength`个元素;`tailNum`表示核数量,剩余核处理`tailLength`个元素140+3. `mainTileRemainder`为0时再按DataBlock给所有核补齐等长
141+ 
142+ ```cpp
143+ tailBlockNumEachCore = mainTileRemainder / (numBlocks * alignNum);
144+ remainingTailLength = mainTileRemainder % (numBlocks * alignNum);
145+ baseLength = mainTileNum * mainTileLength + tailBlockNumEachCore * alignNum;
146+ ```
147+ 
148+ `baseLength`表示每个核处理的基础数据量。`remainingTailLength`表示每核补齐等长DataBlock尾块后尚未分配的元素数量,其值为`alignNum`的整数倍。
149+ 
150+4. 判断尾块均分场景。当`remainingTailLength`为0时,表示所有核均分到等长尾块,进入尾块均分场景。
151+ 
152+ ```cpp
153+ if (remainingTailLength == 0) {
154+ // 使用MainTileWithTailBlockTiling。
155+ return;
156+ }
157+ ```
158+ 
159+5. `remainingTailLength`不为0时,将最后剩余的DataBlock分给前若干个整核,并计算整核和尾核的数据长度。
160+ 
161+ ```cpp
162+ formerNum = remainingTailLength / alignNum;
163+ formerLength = baseLength + alignNum;
164+ tailLength = baseLength;
165+ ```
166+ 
167+6. 判断尾核切分场景。`tailBlockNumEachCore`为0时,尾核内只包含主块,进入尾核切分场景;否则,进入尾核尾块切分场景。
168+ 
169+ ```cpp
170+ if (tailBlockNumEachCore == 0) {
171+ // 使用MainTileWithTailCoreTiling。
172+ } else {
173+ // 使用TailBlockAndTailCoreTiling。
174+ }
175+ ```
152 176 
153## 编译运行177## 编译运行
154 178 
@@ -193,7 +217,7 @@
193 |------|--------|------|217 |------|--------|------|
194 | `CMAKE_ASC_RUN_MODE` | `npu`(默认)、`cpu``sim` | 运行模式:NPU 运行、CPU调试、NPU仿真 |218 | `CMAKE_ASC_RUN_MODE` | `npu`(默认)、`cpu``sim` | 运行模式:NPU 运行、CPU调试、NPU仿真 |
195 | `CMAKE_ASC_ARCHITECTURES` | `dav-2201`(默认)、`dav-3510` | NPU 架构:dav-2201 对应 Atlas A2 训练系列产品/Atlas A2 推理系列产品和 Atlas A3 训练系列产品/Atlas A3 推理系列产品,dav-3510 对应 Ascend 950PR/Ascend 950DT |219 | `CMAKE_ASC_ARCHITECTURES` | `dav-2201`(默认)、`dav-3510` | NPU 架构:dav-2201 对应 Atlas A2 训练系列产品/Atlas A2 推理系列产品和 Atlas A3 训练系列产品/Atlas A3 推理系列产品,dav-3510 对应 Ascend 950PR/Ascend 950DT |
196- | `SCENARIO_NUM` | `0`(默认)、`1`、`2`、`3` | 场景编号:0(主块)、1(主块+尾块)、2(主块+尾核)、3(尾块+尾核) |220+ | `SCENARIO_NUM` | `0`(默认)、`1`、`2`、`3` | 场景编号:0(主块均分)、1(尾块均分)、2(尾核切分)、3(尾核尾块切分) |
197 221 
198- 执行结果222- 执行结果
199 223 
Mexamples/01_simd_cpp_api/02_features/02_tiling_selector/vector_tiling_strategy/README_en.md+50-34
@@ -4,7 +4,7 @@
4 4 
5This sample uses two one-dimensional Tensors as inputs for addition. It introduces a multi-core Tiling split strategy for vector computing. The strategy calculates different inter-core and intra-core split parameters based on the input data volume. It distributes data across multiple AI Cores as evenly as possible to improve multi-core computing efficiency.5This sample uses two one-dimensional Tensors as inputs for addition. It introduces a multi-core Tiling split strategy for vector computing. The strategy calculates different inter-core and intra-core split parameters based on the input data volume. It distributes data across multiple AI Cores as evenly as possible to improve multi-core computing efficiency.
6 6 
7-The sample uses 8 AI Cores. The main tile data volume in each core is 3200 `half` elements, which equals 200 DataBlocks. Use the CMake build parameter `SCENARIO_NUM` to select a data volume scenario.7+The sample uses 8 Cores. The main tile data volume in each core is 3200 `half` elements, which equals 200 DataBlocks. Use the CMake build parameter `SCENARIO_NUM` to select a data volume scenario.
8 8 
9## Supported Products and CANN Software Versions9## Supported Products and CANN Software Versions
10 10 
@@ -31,30 +31,30 @@ The sample uses 8 AI Cores. The main tile data volume in each core is 3200 `half
31 31 
32This sample adds two one-dimensional Tensors. The compute logic is `z = x + y`. Use the CMake build parameter `SCENARIO_NUM` to select a data volume scenario. Each scenario maps to a different Tiling split scenario. All scenarios use the ND data format. The input and output data type is `half`. The kernel function name is `add_custom`.32This sample adds two one-dimensional Tensors. The compute logic is `z = x + y`. Use the CMake build parameter `SCENARIO_NUM` to select a data volume scenario. Each scenario maps to a different Tiling split scenario. All scenarios use the ND data format. The input and output data type is `half`. The kernel function name is `add_custom`.
33 33 
34-The sample uses 8 AI Cores. The main tile data volume is 3200 `half` elements. The main tile (`MAIN_TILE_LENGTH`) is the maximum data volume that one operation can process based on the available Unified Buffer (UB) space. It meets 32-byte alignment.34+The sample uses 8 Cores. The main tile data volume is 3200 `half` elements. The main tile (`mainTileLength`) is the maximum data volume that one operation can process based on the available Unified Buffer (UB) space. It meets 32-byte alignment.
35 35 
36-**Scenario 0: Main Tiles Only**36+**Scenario 0: Main Tile Equal Split**
37 37 
38- Input: `x` and `y` each contain [1, 256000] `half` elements.38- Input: `x` and `y` each contain [1, 256000] `half` elements.
39- Output: `z` contains [1, 256000] `half` elements.39- Output: `z` contains [1, 256000] `half` elements.
40- Tiling split: Each of the 8 cores processes 32000 elements. Each core contains 10 main tiles, and each main tile contains 3200 elements.40- Tiling split: Each of the 8 cores processes 32000 elements. Each core contains 10 main tiles, and each main tile contains 3200 elements.
41- Description: All cores process the same data volume. Each core contains only main tiles.41- Description: All cores process the same data volume. Each core contains only main tiles.
42 42 
43-**Scenario 1: Main Tiles and Tail Block**43+**Scenario 1: Tail Block Equal Split**
44 44 
45- Input: `x` and `y` each contain [1, 260096] `half` elements.45- Input: `x` and `y` each contain [1, 260096] `half` elements.
46- Output: `z` contains [1, 260096] `half` elements.46- Output: `z` contains [1, 260096] `half` elements.
47- Tiling split: Each of the 8 cores processes 32512 elements. Each core contains 10 main tiles and a tail block with 512 elements.47- Tiling split: Each of the 8 cores processes 32512 elements. Each core contains 10 main tiles and a tail block with 512 elements.
48- Description: All cores process the same data volume. Each core has an equal-length tail block after the main tiles.48- Description: All cores process the same data volume. Each core has an equal-length tail block after the main tiles.
49 49 
50-**Scenario 2: Main Tiles and Tail Core**50+**Scenario 2: Tail Core Split**
51 51 
52- Input: `x` and `y` each contain [1, 256064] `half` elements.52- Input: `x` and `y` each contain [1, 256064] `half` elements.
53- Output: `z` contains [1, 256064] `half` elements.53- Output: `z` contains [1, 256064] `half` elements.
54- Tiling split: The first 4 former cores each process 32016 elements, and the remaining 4 tail cores each process 32000 elements. Each former core contains 10 main tiles and a tail block with 16 elements. Each tail core contains 10 main tiles.54- Tiling split: The first 4 former cores each process 32016 elements, and the remaining 4 tail cores each process 32000 elements. Each former core contains 10 main tiles and a tail block with 16 elements. Each tail core contains 10 main tiles.
55- Description: The first 4 former cores each process one more DataBlock than the tail cores. The tail cores do not have tail blocks.55- Description: The first 4 former cores each process one more DataBlock than the tail cores. The tail cores do not have tail blocks.
56 56 
57-**Scenario 3: Tail Block and Tail Core**57+**Scenario 3: Tail Core Split with Tail Blocks**
58 58 
59- Input: `x` and `y` each contain [1, 258112] `half` elements.59- Input: `x` and `y` each contain [1, 258112] `half` elements.
60- Output: `z` contains [1, 258112] `half` elements.60- Output: `z` contains [1, 258112] `half` elements.
@@ -113,42 +113,58 @@ The Tiling calculation process of this sample is as follows. The host side recei
113 totalLengthAligned = AlignUp(totalLength, alignNum);113 totalLengthAligned = AlignUp(totalLength, alignNum);
114 ```114 ```
115 115 
116-2. Allocate the same number of main tiles, `MAIN_TILE_LENGTH`, to all cores.116+2. Allocate the same number of main tiles, `mainTileLength`, to all cores.
117 117 
118 ```cpp118 ```cpp
119- mainTileNum = totalLengthAligned / (numBlocks * MAIN_TILE_LENGTH);119+ mainTileNum = totalLengthAligned / (numBlocks * mainTileLength);
120- mainTileRemainder = totalLengthAligned % (numBlocks * MAIN_TILE_LENGTH);120+ mainTileRemainder = totalLengthAligned % (numBlocks * mainTileLength);
121 ```121 ```
122 122 
123- At this point, each core processes at least `mainTileNum * MAIN_TILE_LENGTH` elements. `mainTileRemainder` indicates the remaining data volume after main tile allocation.123+ At this point, each core processes at least `mainTileNum * mainTileLength` elements. `mainTileRemainder` indicates the remaining data volume after main tile allocation. If `mainTileRemainder` is 0, select the main-tiles-only scenario and finish the Tiling calculation.
124- 
125-3. Allocate `mainTileRemainder` to all cores as equal-length DataBlock tail blocks.
126 124 
127 ```cpp125 ```cpp
128- tailBlockNumEachCore = mainTileRemainder / (numBlocks * alignNum);126+ if (mainTileRemainder == 0) {
129- formerCoreRemainder = mainTileRemainder % (numBlocks * alignNum);127+ // Use MainTileOnlyTiling.
130- baseLength = mainTileNum * MAIN_TILE_LENGTH + tailBlockNumEachCore * alignNum;128+ return;
131- ```
132- 
133- `baseLength` indicates the base data volume processed by each core. `formerCoreRemainder` indicates the remaining data volume that must be allocated to the first several cores after each core receives equal-length DataBlock tail blocks.
134- 
135-4. Allocate the remaining DataBlocks to the first several former cores.
136- 
137- ```cpp
138- if (formerCoreRemainder == 0) {
139- formerNum = numBlocks;
140- formerLength = baseLength;
141- tailNum = 0;
142- tailLength = 0;
143- } else {
144- formerNum = formerCoreRemainder / alignNum;
145- formerLength = baseLength + alignNum;
146- tailNum = numBlocks - formerNum;
147- tailLength = baseLength;
148 }129 }
149 ```130 ```
150 131 
151- If `formerCoreRemainder == 0`, no tail core exists, and all cores process `baseLength` elements. If `formerCoreRemainder != 0`, `formerNum` indicates the number of former cores. The first `formerNum` cores process `formerLength` elements. `tailNum` indicates the number of tail cores, and the remaining cores process `tailLength` elements.132+3. If `mainTileRemainder` is not 0, allocate it to all cores as equal-length DataBlock tail blocks.
133+ 
134+ ```cpp
135+ tailBlockNumEachCore = mainTileRemainder / (numBlocks * alignNum);
136+ remainingTailLength = mainTileRemainder % (numBlocks * alignNum);
137+ baseLength = mainTileNum * mainTileLength + tailBlockNumEachCore * alignNum;
138+ ```
139+ 
140+ `baseLength` indicates the base data volume processed by each core. `remainingTailLength` indicates the number of elements that remain unallocated after each core receives equal-length DataBlock tail blocks. Its value is an integer multiple of `alignNum`.
141+ 
142+4. Check for the tail block equal split scenario. If `remainingTailLength` is 0, all cores receive equal-length tail blocks, and this scenario is selected.
143+ 
144+ ```cpp
145+ if (remainingTailLength == 0) {
146+ // Use MainTileWithTailBlockTiling.
147+ return;
148+ }
149+ ```
150+ 
151+5. If `remainingTailLength` is not 0, allocate the remaining DataBlocks to the first several former cores and calculate the data lengths of former and tail cores.
152+ 
153+ ```cpp
154+ formerNum = remainingTailLength / alignNum;
155+ formerLength = baseLength + alignNum;
156+ tailLength = baseLength;
157+ ```
158+ 
159+6. Check for the tail core split scenario. If `tailBlockNumEachCore` is 0, tail cores contain only main tiles, and this scenario is selected. Otherwise, select the tail core split with tail blocks scenario.
160+ 
161+ ```cpp
162+ if (tailBlockNumEachCore == 0) {
163+ // Use MainTileWithTailCoreTiling.
164+ } else {
165+ // Use TailBlockAndTailCoreTiling.
166+ }
167+ ```
152 168 
153## Build and Run169## Build and Run
154 170 
@@ -193,7 +209,7 @@ In the sample root directory, perform the following steps to build and run the s
193 |------|--------|------|209 |------|--------|------|
194 | `CMAKE_ASC_RUN_MODE` | `npu` (default), `cpu`, `sim` | Run mode: NPU execution, CPU debugging, and NPU simulation |210 | `CMAKE_ASC_RUN_MODE` | `npu` (default), `cpu`, `sim` | Run mode: NPU execution, CPU debugging, and NPU simulation |
195 | `CMAKE_ASC_ARCHITECTURES` | `dav-2201` (default), `dav-3510` | NPU architecture: `dav-2201` maps to Atlas A2 training series products, Atlas A2 inference series products, Atlas A3 training series products, and Atlas A3 inference series products. `dav-3510` maps to Ascend 950PR and Ascend 950DT |211 | `CMAKE_ASC_ARCHITECTURES` | `dav-2201` (default), `dav-3510` | NPU architecture: `dav-2201` maps to Atlas A2 training series products, Atlas A2 inference series products, Atlas A3 training series products, and Atlas A3 inference series products. `dav-3510` maps to Ascend 950PR and Ascend 950DT |
196- | `SCENARIO_NUM` | `0` (default), `1`, `2`, `3` | Scenario number: 0 (main tiles only), 1 (main tiles and tail block), 2 (main tiles and tail core), and 3 (tail block and tail core) |212+ | `SCENARIO_NUM` | `0` (default), `1`, `2`, `3` | Scenario number: 0 (main tile equal split), 1 (tail block equal split), 2 (tail core split), and 3 (tail core split with tail blocks) |
197 213 
198- Execution result.214- Execution result.
199 215 
Mexamples/01_simd_cpp_api/02_features/02_tiling_selector/vector_tiling_strategy/vector_tiling_strategy.asc+65-70
@@ -12,7 +12,7 @@
12 * \file vector_tiling_strategy.asc12 * \file vector_tiling_strategy.asc
13 * \brief13 * \brief
14 * 以两个一维Tensor相加为例,演示如何根据数据总量与核数的关系计算Tiling参数。14 * 以两个一维Tensor相加为例,演示如何根据数据总量与核数的关系计算Tiling参数。
15- * 样例覆盖四种典型场景:主块、主块+尾块、主块+尾核、尾块+尾核。15+ * 样例覆盖四种典型场景:主块均分、尾块均分、尾核切分、尾核尾块切分
16 */16 */
17 17 
18#include <cstdint>18#include <cstdint>
@@ -27,57 +27,44 @@
27#endif27#endif
28 28 
29constexpr uint32_t scenarioNum = SCENARIO_NUM;29constexpr uint32_t scenarioNum = SCENARIO_NUM;
30-constexpr uint32_t NUM_BLOCKS = 8;30+constexpr uint32_t numBlocks = 8; // 参与计算的逻辑核数
31-constexpr uint32_t BLOCK_SIZE = 32;31+constexpr uint32_t dataBlockSize = 32; // 一个DataBlock的字节数
32-constexpr uint32_t HALF_SIZE = 2;32+constexpr uint32_t halfSize = 2; // 一个half元素的字节数
33-constexpr uint32_t ALIGN_NUM = BLOCK_SIZE / HALF_SIZE;33+constexpr uint32_t alignNum = dataBlockSize / halfSize; // 一个DataBlock包含的half元素数量
34-constexpr uint32_t MAIN_TILE_LENGTH = 3200; // 主块数34+constexpr uint32_t mainTileLength = 3200; // 一个主块包含的half元素数量
35 35 
36struct MainTileOnlyTiling {36struct MainTileOnlyTiling {
37- uint32_t blockLength; // 每个核处理的数37+ uint32_t blockLength; // 每个核处理的元素数量
38 uint32_t tileNum; // 主块数量38 uint32_t tileNum; // 主块数量
39};39};
40 40 
41struct MainTileWithTailBlockTiling {41struct MainTileWithTailBlockTiling {
42- uint32_t blockLength; // 每个核处理的数42+ uint32_t blockLength; // 每个核处理的元素数量
43 uint32_t tileNum; // 主块数量43 uint32_t tileNum; // 主块数量
44- uint32_t lastTileLength; // 尾块数44+ uint32_t lastTileLength; // 尾块元素数量
45};45};
46 46 
47struct MainTileWithTailCoreTiling {47struct MainTileWithTailCoreTiling {
48 uint32_t formerNum; // 整核数量48 uint32_t formerNum; // 整核数量
49- uint32_t formerLength; // 整核处理的数49+ uint32_t formerLength; // 整核处理的元素数量
50 uint32_t formerTileNum; // 整核中主块的数量50 uint32_t formerTileNum; // 整核中主块的数量
51- uint32_t formerLastTileLength; // 整核中尾块的数51+ uint32_t formerLastTileLength; // 整核中尾块的元素数量
52- uint32_t tailLength; // 尾核处理的数52+ uint32_t tailLength; // 尾核处理的元素数量
53 uint32_t tailTileNum; // 尾核中主块的数量53 uint32_t tailTileNum; // 尾核中主块的数量
54};54};
55 55 
56struct TailBlockAndTailCoreTiling {56struct TailBlockAndTailCoreTiling {
57 uint32_t formerNum; // 整核数量57 uint32_t formerNum; // 整核数量
58- uint32_t formerLength; // 整核处理的数58+ uint32_t formerLength; // 整核处理的元素数量
59 uint32_t formerTileNum; // 整核中主块的数量59 uint32_t formerTileNum; // 整核中主块的数量
60- uint32_t formerLastTileLength; // 整核中尾块的数60+ uint32_t formerLastTileLength; // 整核中尾块的元素数量
61- uint32_t tailLength; // 尾核处理的数61+ uint32_t tailLength; // 尾核处理的元素数量
62 uint32_t tailTileNum; // 尾核中主块的数量62 uint32_t tailTileNum; // 尾核中主块的数量
63- uint32_t tailLastTileLength; // 尾核中尾块的数63+ uint32_t tailLastTileLength; // 尾核中尾块的元素数量
64-};
65- 
66-struct TileInfo {
67- uint32_t tileNum;
68- uint32_t lastTileLength;
69};64};
70 65 
71uint32_t AlignUp(uint32_t value, uint32_t align) { return ((value + align - 1U) / align) * align; }66uint32_t AlignUp(uint32_t value, uint32_t align) { return ((value + align - 1U) / align) * align; }
72 67 
73-TileInfo CalcTileInfo(uint32_t length)
74-{
75- TileInfo info;
76- info.tileNum = length / MAIN_TILE_LENGTH;
77- info.lastTileLength = length - info.tileNum * MAIN_TILE_LENGTH;
78- return info;
79-}
80- 
81class KernelAdd {68class KernelAdd {
82public:69public:
83 __aicore__ inline KernelAdd() = default;70 __aicore__ inline KernelAdd() = default;
@@ -137,14 +124,14 @@ public:
137 124 
138 __aicore__ inline void Process()125 __aicore__ inline void Process()
139 {126 {
140- AscendC::LocalTensor<half> xLocal(AscendC::TPosition::VECCALC, xAddr, MAIN_TILE_LENGTH);127+ AscendC::LocalTensor<half> xLocal(AscendC::TPosition::VECCALC, xAddr, mainTileLength);
141- AscendC::LocalTensor<half> yLocal(AscendC::TPosition::VECCALC, yAddr, MAIN_TILE_LENGTH);128+ AscendC::LocalTensor<half> yLocal(AscendC::TPosition::VECCALC, yAddr, mainTileLength);
142- AscendC::LocalTensor<half> zLocal(AscendC::TPosition::VECCALC, zAddr, MAIN_TILE_LENGTH);129+ AscendC::LocalTensor<half> zLocal(AscendC::TPosition::VECCALC, zAddr, mainTileLength);
143 130 
144 uint32_t totalLoop = this->tileNum + (this->lastTileLength > 0U ? 1U : 0U);131 uint32_t totalLoop = this->tileNum + (this->lastTileLength > 0U ? 1U : 0U);
145 for (uint32_t loopIdx = 0; loopIdx < totalLoop; loopIdx++) {132 for (uint32_t loopIdx = 0; loopIdx < totalLoop; loopIdx++) {
146- uint32_t curLen = (loopIdx < this->tileNum) ? MAIN_TILE_LENGTH : this->lastTileLength;133+ uint32_t curLen = (loopIdx < this->tileNum) ? mainTileLength : this->lastTileLength;
147- uint32_t startElement = loopIdx * MAIN_TILE_LENGTH;134+ uint32_t startElement = loopIdx * mainTileLength;
148 135 
149 if (loopIdx != 0U) {136 if (loopIdx != 0U) {
150 AscendC::WaitFlag<AscendC::HardEvent::V_MTE2>(EVENT_ID0);137 AscendC::WaitFlag<AscendC::HardEvent::V_MTE2>(EVENT_ID0);
@@ -187,8 +174,8 @@ private:
187 174 
188private:175private:
189 static constexpr uint32_t xAddr = 0;176 static constexpr uint32_t xAddr = 0;
190- static constexpr uint32_t yAddr = MAIN_TILE_LENGTH * HALF_SIZE;177+ static constexpr uint32_t yAddr = mainTileLength * halfSize;
191- static constexpr uint32_t zAddr = yAddr + MAIN_TILE_LENGTH * HALF_SIZE;178+ static constexpr uint32_t zAddr = yAddr + mainTileLength * halfSize;
192 179 
193 AscendC::GlobalTensor<half> xGm;180 AscendC::GlobalTensor<half> xGm;
194 AscendC::GlobalTensor<half> yGm;181 AscendC::GlobalTensor<half> yGm;
@@ -211,51 +198,59 @@ void LaunchAddByTiling(uint8_t* xDevice, uint8_t* yDevice, uint8_t* zDevice, uin
211{198{
212 // Host侧Tiling逐级计算主块数量、尾块数量、尾核数量。199 // Host侧Tiling逐级计算主块数量、尾块数量、尾核数量。
213 // 第零步,将数据量按32字节对齐200 // 第零步,将数据量按32字节对齐
214- uint32_t totalLengthAligned = AlignUp(totalLength, ALIGN_NUM);201+ uint32_t totalLengthAligned = AlignUp(totalLength, alignNum);
215 202 
216 // 第一步,所有核均分相同数量的主块。203 // 第一步,所有核均分相同数量的主块。
217- uint32_t mainTileNum = totalLengthAligned / (NUM_BLOCKS * MAIN_TILE_LENGTH);204+ uint32_t mainTileNum = totalLengthAligned / (numBlocks * mainTileLength);
218- uint32_t mainTileRemainder = totalLengthAligned % (NUM_BLOCKS * MAIN_TILE_LENGTH);205+ uint32_t mainTileRemainder = totalLengthAligned % (numBlocks * mainTileLength);
206+ uint32_t baseLength = mainTileNum * mainTileLength;
207+ uint32_t baseTileNum = mainTileNum;
208+ 
209+ if (mainTileRemainder == 0U) {
210+ MainTileOnlyTiling tiling{baseLength, baseTileNum};
211+ printf("Tiling mode: main_tile_only\n");
212+ add_custom<MainTileOnlyTiling><<<numBlocks, 0, stream>>>(xDevice, yDevice, zDevice, tiling);
213+ return;
214+ }
219 215 
220 // 第二步,mainTileRemainder按DataBlock给每个核补齐等长尾块。216 // 第二步,mainTileRemainder按DataBlock给每个核补齐等长尾块。
221- uint32_t tailBlockNumEachCore = mainTileRemainder / (NUM_BLOCKS * ALIGN_NUM);217+ uint32_t tailBlockNumEachCore = mainTileRemainder / (numBlocks * alignNum);
222- uint32_t formerCoreRemainder = mainTileRemainder % (NUM_BLOCKS * ALIGN_NUM);218+ uint32_t remainingTailLength = mainTileRemainder % (numBlocks * alignNum);
223- uint32_t baseLength = mainTileNum * MAIN_TILE_LENGTH + tailBlockNumEachCore * ALIGN_NUM;219+ uint32_t baseLastTileLength = tailBlockNumEachCore * alignNum;
224- TileInfo baseInfo = CalcTileInfo(baseLength);220+ baseLength += baseLastTileLength;
225 221 
226- // 如果没有剩余DataBlock,所有核都处理baseLength,只需要区分是否存在尾块。222+ // 如果没有剩余DataBlock,所有核都处理baseLength,每个核都有等长尾块。
227- if (formerCoreRemainder == 0U) {223+ if (remainingTailLength == 0U) {
228- if (baseInfo.lastTileLength == 0U) {224+ MainTileWithTailBlockTiling tiling{baseLength, baseTileNum, baseLastTileLength};
229- MainTileOnlyTiling tiling{baseLength, baseInfo.tileNum};225+ printf("Tiling mode: main_tile_with_tail_block\n");
230- printf("Tiling mode: main_tile_only\n");226+ add_custom<MainTileWithTailBlockTiling><<<numBlocks, 0, stream>>>(xDevice, yDevice, zDevice, tiling);
231- add_custom<MainTileOnlyTiling><<<NUM_BLOCKS, 0, stream>>>(xDevice, yDevice, zDevice, tiling);
232- } else {
233- MainTileWithTailBlockTiling tiling{baseLength, baseInfo.tileNum, baseInfo.lastTileLength};
234- printf("Tiling mode: main_tile_with_tail_block\n");
235- add_custom<MainTileWithTailBlockTiling><<<NUM_BLOCKS, 0, stream>>>(xDevice, yDevice, zDevice, tiling);
236- }
237 return;227 return;
238 }228 }
239 229 
240 // 第三步,最后剩余的DataBlock分给前formerNum个整核。230 // 第三步,最后剩余的DataBlock分给前formerNum个整核。
241- uint32_t formerNum = formerCoreRemainder / ALIGN_NUM;231+ uint32_t formerNum = remainingTailLength / alignNum;
242- uint32_t formerLength = baseLength + ALIGN_NUM; // 前formerNum个核都多处理一个DataBlock的数据232+ uint32_t formerLength = baseLength + alignNum; // 前formerNum个核都多处理一个DataBlock的数据
243- uint32_t tailLength = baseLength;233+ uint32_t tailLength = baseLength; // 尾核处理的数据量
244- TileInfo formerInfo = CalcTileInfo(formerLength);234+ uint32_t formerTileNum = baseTileNum;
245- TileInfo tailInfo = baseInfo;235+ uint32_t tailTileNum = baseTileNum;
246- bool formerOnlyOneDataBlockTail = (formerInfo.lastTileLength == ALIGN_NUM);236+ // 未分配基础尾块时进入尾核切分场景,否则进入尾核尾块切分场景。
247- bool tailHasNoTailTile = (tailInfo.lastTileLength == 0U);237+ if (tailBlockNumEachCore == 0U) {
248- if (formerOnlyOneDataBlockTail && tailHasNoTailTile) {238+ uint32_t formerLastTileLength = alignNum;
249- MainTileWithTailCoreTiling tiling{formerNum, formerLength, formerInfo.tileNum, formerInfo.lastTileLength,239+ MainTileWithTailCoreTiling tiling{formerNum, formerLength, formerTileNum, formerLastTileLength,
250- tailLength, tailInfo.tileNum};240+ tailLength, tailTileNum};
251 printf("Tiling mode: main_tile_with_tail_core\n");241 printf("Tiling mode: main_tile_with_tail_core\n");
252- add_custom<MainTileWithTailCoreTiling><<<NUM_BLOCKS, 0, stream>>>(xDevice, yDevice, zDevice, tiling);242+ add_custom<MainTileWithTailCoreTiling><<<numBlocks, 0, stream>>>(xDevice, yDevice, zDevice, tiling);
253 } else {243 } else {
254- TailBlockAndTailCoreTiling tiling{244+ uint32_t formerLastTileLength = baseLastTileLength + alignNum;
255- formerNum, formerLength, formerInfo.tileNum, formerInfo.lastTileLength,245+ if (formerLastTileLength == mainTileLength) {
256- tailLength, tailInfo.tileNum, tailInfo.lastTileLength};246+ formerTileNum += 1U;
247+ formerLastTileLength = 0U;
248+ }
249+ uint32_t tailLastTileLength = baseLastTileLength;
250+ TailBlockAndTailCoreTiling tiling{formerNum, formerLength, formerTileNum, formerLastTileLength,
251+ tailLength, tailTileNum, tailLastTileLength};
257 printf("Tiling mode: tail_block_and_tail_core\n");252 printf("Tiling mode: tail_block_and_tail_core\n");
258- add_custom<TailBlockAndTailCoreTiling><<<NUM_BLOCKS, 0, stream>>>(xDevice, yDevice, zDevice, tiling);253+ add_custom<TailBlockAndTailCoreTiling><<<numBlocks, 0, stream>>>(xDevice, yDevice, zDevice, tiling);
259 }254 }
260}255}
261 256 
@@ -265,7 +260,7 @@ int32_t main(int32_t argc, char* argv[])
265 (scenarioNum == 1) ? 260096 :260 (scenarioNum == 1) ? 260096 :
266 (scenarioNum == 2) ? 256064 :261 (scenarioNum == 2) ? 256064 :
267 258112;262 258112;
268- size_t dataByteSize = static_cast<size_t>(dataLen) * HALF_SIZE;263+ size_t dataByteSize = static_cast<size_t>(dataLen) * halfSize;
269 264 
270 printf("Usage: %s (compile-time SCENARIO_NUM=%u)\n", argv[0], scenarioNum);265 printf("Usage: %s (compile-time SCENARIO_NUM=%u)\n", argv[0], scenarioNum);
271 printf("Running scenario %u, totalLength=%u\n", scenarioNum, dataLen);266 printf("Running scenario %u, totalLength=%u\n", scenarioNum, dataLen);