昇腾社区首页
中文
注册

如何使用Tensor原地操作提升算子性能

Tensor原地操作(inplace接口)是一种优化技术,全局申请、保留LocalTensor内存,避免了频繁创建和销毁LocalTensor对象。AllocTensor、FreeTensor、EnQue、DeQue接口不产生新的LocalTensor,而是在该全局LocalTensor上反复申请、释放、入队、出队。其实现原理如下图所示:

图1 Tensor原地操作实现原理

Tensor原地操作的优势

  • 减少栈变换:相比构造新Tensor的方式,inplace接口减少了LocalTensor的栈变换,允许Tensor被反复使用。
  • 减少入队/出队操作:在调用EnQue、DeQue的过程中,TQue对象没有存储该Tensor对应的Buffer地址,实际没有真正入队、出队,减少了反复入队、出队的Scalar指令。

保留EnQue和DeQue的原因

既然Tensor原地操作没有执行真正的入队出队操作,为什么还需要保留EnQue和DeQue接口呢?

  • 编程兼容性:为了保持编程接口的一致性,inplace接口仍然需要调用EnQue和DeQue,确保代码结构的统一性和可维护性。
  • 内存同步功能:EnQue和DeQue操作中实现了内存读写同步功能,确保数据的一致性和正确性,即使没有实际的队列操作,这些同步机制仍然需要保留。

适用场景

适合计算循环次数多的场景:如图1所示,inplace接口虽然增加了TQue对象InitBuffer的初始化开销,但显著减少了每次循环中AllocTensor、EnQue、DeQue和FreeTensor内部对LocalTensor和事件的操作次数,特别适合需要多次循环来完成计算的场景。

使用方法

  • 配置TQue对象:在创建TQue对象时,设置深度(depth)为0,启用inplace操作模式。
  • 调用原地操作接口:使用inplace接口直接操作LocalTensor。
    • AllocTensor和DeQue区分non-inplace和inplace接口,详情请参考AllocTensorDeQue
    • FreeTensor和EnQue不区分non-inplace和inplace接口。

示例代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// ...
namespace AscendC {
class MyKernel {
public:
    __aicore__ inline MyKernel() {}
    __aicore__ inline void Init(__gm__ uint8_t* src0Gm, __gm__ uint8_t* src1Gm, __gm__ uint8_t* dstGm)
    {
        src0Global.SetGlobalBuffer((__gm__ half*)src0Gm);
        src1Global.SetGlobalBuffer((__gm__ half*)src1Gm);
        dstGlobal.SetGlobalBuffer((__gm__ half*)dstGm);
        pipe.InitBuffer(srcQue0, 1, BLOCK_SIZE * sizeof(half));
        pipe.InitBuffer(srcQue1, 1, BLOCK_SIZE * sizeof(half));
        pipe.InitBuffer(dstQue0, 1, BLOCK_SIZE * sizeof(half));
    }

    __aicore__ inline void Process()
    {
        for (int i = 0; i < REPTIMES; i++) {
            CopyIn(i);
            Compute(i);
            CopyOut(i);
        }
    }

private:
    __aicore__ inline void CopyIn(int32_t i)
    {
        srcQue0.AllocTensor<half>(src0Local);
        srcQue1.AllocTensor<half>(src1Local);
        DataCopy(src0Local, src0Global[i*BLOCK_SIZE], BLOCK_SIZE);
        DataCopy(src1Local, src1Global[i*BLOCK_SIZE], BLOCK_SIZE);
        srcQue0.EnQue(src0Local);
        srcQue1.EnQue(src1Local);
    }
    __aicore__ inline void Compute(int32_t i)
    {
        srcQue0.DeQue<half>(src0Local);
        srcQue1.DeQue<half>(src1Local);
        dstQue0.AllocTensor<half>(dstLocal);
        Add(dstLocal, src0Local, src1Local, BLOCK_SIZE);
        dstQue0.EnQue<half>(dstLocal);
        srcQue0.FreeTensor(src0Local);
        srcQue1.FreeTensor(src1Local);
    }
    __aicore__ inline void CopyOut(int32_t i)
    {
        dstQue0.DeQue<half>(dstLocal);
        DataCopy(dstGlobal[i*BLOCK_SIZE], dstLocal, BLOCK_SIZE);
        dstQue0.FreeTensor(dstLocal);
    }

private:
    TPipe pipe;
    TQue<QuePosition::VECIN, 0> srcQue0, srcQue1;
    TQue<QuePosition::VECOUT, 0> dstQue0;
    GlobalTensor<half> src0Global, src1Global, dstGlobal;
    LocalTensor<half> src0Local;
    LocalTensor<half> src1Local;
    LocalTensor<half> dstLocal;
};
} // namespace AscendC

// ...