Continuous Vector Computations Through UB Fusion
[Priority] High
[Description] If the operator implementation involves multiple vector computations, the output of the previous computation can be temporarily stored in the Unified Buffer (UB) and directly used as the input of the next computation. That is, the previous computation output does not need to be transferred from the UB to the GM and then from the GM to the UB. This UB fusion mode can reduce the number of CopyIn and CopyOut times, implementing continuous vector computations and improving memory usage. The following figure shows the comparison of data flows.

[Negative Example]
The compute logic of this operator is to perform Exp computation and then Abs computation. First, the source operand is transferred from the GM to the UB for Exp computation. After the Exp computation is complete, the Exp result is transferred from the UB to the GM. Then, the Exp result is transferred from the GM to the UB as the input for Abs computation. After Abs computation is complete, the target operand result is transferred from the UB to the GM. The entire process involves data CopyIn and CopyOut of the GM for four times. When vector computations need to be performed n times, the data CopyIn and CopyOut of the GM need to be performed 2n times.
class KernelSample {
public:
__aicore__ inline KernelSample() {}
__aicore__ inline void Init(__gm__ uint8_t* src0Gm, __gm__ uint8_t* dstGm)
{
src0Global.SetGlobalBuffer((__gm__ float*)src0Gm);
dstGlobal.SetGlobalBuffer((__gm__ float*)dstGm);
pipe.InitBuffer(inQueueSrc0, 1, 1024 * sizeof(float));
pipe.InitBuffer(outQueueDst, 1, 1024 * sizeof(float));
}
__aicore__ inline void Process()
{
CopyIn();
Compute();
CopyOut();
CopyIn1();
Compute1();
CopyOut1();
}
private:
__aicore__ inline void CopyIn()
{
LocalTensor<float> src0Local = inQueueSrc0.AllocTensor<float>();
DataCopy(src0Local, src0Global, 1024);
inQueueSrc0.EnQue(src0Local);
}
__aicore__ inline void Compute()
{
LocalTensor<float> src0Local = inQueueSrc0.DeQue<float>();
LocalTensor<float> dstLocal = outQueueDst.AllocTensor<float>();
Exp(dstLocal, src0Local, 1024);
outQueueDst.EnQue<float>(dstLocal);
inQueueSrc0.FreeTensor(src0Local);
}
__aicore__ inline void CopyOut()
{
LocalTensor<float> dstLocal = outQueueDst.DeQue<float>();
DataCopy(dstGlobal, dstLocal, 1024);
outQueueDst.FreeTensor(dstLocal);
}
__aicore__ inline void CopyIn1()
{
PipeBarrier<PIPE_ALL>();
LocalTensor<float> src0Local = inQueueSrc0.AllocTensor<float>();
DataCopy(src0Local, dstGlobal, 1024);
inQueueSrc0.EnQue(src0Local);
}
__aicore__ inline void Compute1()
{
LocalTensor<float> src0Local = inQueueSrc0.DeQue<float>();
LocalTensor<float> dstLocal = outQueueDst.AllocTensor<float>();
Abs(dstLocal, src0Local, 1024);
outQueueDst.EnQue<float>(dstLocal);
inQueueSrc0.FreeTensor(src0Local);
}
__aicore__ inline void CopyOut1()
{
LocalTensor<float> dstLocal = outQueueDst.DeQue<float>();
DataCopy(dstGlobal, dstLocal, 1024);
outQueueDst.FreeTensor(dstLocal);
}
private:
TPipe pipe;
TQue<QuePosition::VECIN, 1> inQueueSrc0;
TQue<QuePosition::VECOUT, 1> outQueueDst;
GlobalTensor<float> src0Global, dstGlobal;
};
[Positive Example]
With UB fusion, when continuous vector computations are performed on the UB, the previous result can be directly used as the input of the next computation, which continues on the UB without intermediate CopyIn and CopyOut. Simply, the source operand is transferred to the UB when the computation starts. After the computation is complete, the final result is transferred from the UB to the GM. That is, the data CopyIn and CopyOut need to be performed only twice in total.
class KernelSample {
public:
__aicore__ inline KernelSample() {}
__aicore__ inline void Init(__gm__ uint8_t* src0Gm, __gm__ uint8_t* dstGm)
{
src0Global.SetGlobalBuffer((__gm__ float*)src0Gm);
dstGlobal.SetGlobalBuffer((__gm__ float*)dstGm);
pipe.InitBuffer(inQueueSrc0, 1, 1024 * sizeof(float));
pipe.InitBuffer(outQueueDst, 1, 1024 * sizeof(float));
}
__aicore__ inline void Process()
{
CopyIn();
Compute();
CopyOut();
}
private:
__aicore__ inline void CopyIn()
{
LocalTensor<float> src0Local = inQueueSrc0.AllocTensor<float>();
DataCopy(src0Local, src0Global, 1024);
inQueueSrc0.EnQue(src0Local);
}
__aicore__ inline void Compute()
{
LocalTensor<float> src0Local = inQueueSrc0.DeQue<float>();
LocalTensor<float> dstLocal = outQueueDst.AllocTensor<float>();
Exp(dstLocal, src0Local, 1024);
Abs(dstLocal, dstLocal, 1024);
outQueueDst.EnQue<float>(dstLocal);
inQueueSrc0.FreeTensor(src0Local);
}
__aicore__ inline void CopyOut()
{
LocalTensor<float> dstLocal = outQueueDst.DeQue<float>();
DataCopy(dstGlobal, dstLocal, 1024);
outQueueDst.FreeTensor(dstLocal);
}
private:
TPipe pipe;
TQue<QuePosition::VECIN, 1> inQueueSrc0;
TQue<QuePosition::VECOUT, 1> outQueueDst;
GlobalTensor<float> src0Global, dstGlobal;
};