The programming paradigm describes the fixed process of operator kernel function implementation. Programming based on the paradigm can quickly set up the code framework for operator implementation.
According to Abstract Hardware Architecture, the execution units inside the AI Core asynchronously and concurrently execute the received instructions. The execution units cooperate with each other to complete the entire operator execution process in a pipeline manner.
The Ascend C programming paradigm is a pipeline programming paradigm. It divides the processing program in the operator core into multiple pipeline tasks, completes inter-task communication and synchronization through queues (TQue), and manages resources such as memories and events through a unified resource management module (TPipe).
The following sections describe this TPipe- and TQue-based programming paradigm in detail from the perspective of three typical operator types.

As shown in the above figure, the vector programming paradigm divides the operator implementation process into three basic tasks: CopyIn, Compute, and CopyOut.
VECIN and VECOUT mentioned above are the concepts of TPosition. When managing physical memories at different levels, Ascend C uses an abstract logical position (TPosition) to express storage at each level, replacing on-chip physical storage and hiding the hardware architecture. In addition to VECIN and VECOUT, VECCALC is also used in vector programming. Generally, this position is used when temporary variables are defined. For details about the mapping between TPosition and the physical memory, see Table 1.
The following figure illustrates the process (see the pseudo-code below) from the perspective of programming.

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 |
AscendC::TPipe pipe; // Create a global resource management object. AscendC::TQue<AscendC::TPosition::VecIn, 1> queIn; // Create a CopyIn queue. AscendC::TQue<AscendC::TPosition::VecOut, 1> queOut;// Create a CopyOut queue. // Initialization phase pipe.InitBuffer(queIn, 2, 1024); // Enable DoubleBuffer to divide the data to be processed into two parts for pipeline parallelism. pipe.InitBuffer(queOut, 2, 1024); for-loop { // CopyIn phase { auto tensor = queIn.AllocTensor<half>(); // Allocate resources from the queue, with the length of 1024 bytes. AscendC::DataCopy(tensor, gm, 1024); // Copy data from the global memory to VECIN. queIn.EnQue(tensor); } // Compute phase { auto tensor = queIn.DeQue<half>(); auto tensorOut = queOut.AllocTensor<half>(); AscendC::Abs(tensorOut, tensor, 1024); // Compute queIn.FreeTensor(tensor); queOut.EnQue(tensorOut); } // CopyOut phase { auto tensor = queOut.DeQue<half>(); AscendC::DataCopy(gmOut, tensor, 1024); // Copy data from VECOUT to the global memory. queOut.FreeTensor(tensor); // Release resources. } } |
Resources such as memory and events used for data copy between tasks are managed by the Pipe module. As shown in the following figure, TPipe provides the queue memory initialization function through InitBuffer. You can call this API to allocate memory to a specified queue.
After the queue memory initialization, call AllocTensor to allocate memory to the LocalTensor when the memory is required. When the created LocalTensor is not required after computation, call FreeTensor to reclaim the LocalTensor memory.
The temporary variable memory used during programming is also managed by Pipe. Temporary variables can use the TBuf data structure to allocate memory space on a specified TPosition. The memory space allocated by using TBuf can only be used for computation and cannot be used for data enqueuing or dequeuing. For details about the API usage, see GetSFFValue.
Parallel processing of data on a single core can be implemented by programming based on this paradigm. The data to be processed is divided into n slices. Each parallel task needs to process n data slices in sequence. Arrows between tasks indicate the dependency between data. For example, Compute can process the first data slice only after CopyIn has processed it.
The following figure shows the running of the pipeline task. As shown in the diagram, for the same data slice, CopyIn, Compute, and CopyOut processes depend on each other and need to be processed in sequence. Different data slices can be processed by multiple tasks in parallel at the same time, achieving task parallelism and performance improvement.
The following figure shows the typical data flow of cube compute.

Similar to the vector programming paradigm, the logical position (TPosition) is also used to express data streams. The logical position used in the cube programming paradigm is defined as follows:
For details about the mapping between TPosition and the physical memory, see Table 1.
The cube compute process also contains the CopyIn, Compute, and CopyOut phases. Considering the complexity of the process, encapsulated Matmul high-level APIs are provided to simplify the programming paradigm.

As shown in the preceding figure, the CopyIn phase corresponds to the SetTensorA, SetTensorB, and SetBias APIs, the Compute phase corresponds to the Iterate API, and the CopyOut phase corresponds to the GetTensorC API. For details, see the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// During Matmul object creation, input the type information of parameters A, B, C, and Bias. The type information is defined by MatmulType, including the logical memory location, data format, and data type. typedef MatmulType<TPosition::GM, CubeFormat::ND, half> aType; typedef MatmulType<TPosition::GM, CubeFormat::ND, half> bType; typedef MatmulType<TPosition::GM, CubeFormat::ND, float> cType; typedef MatmulType<TPosition::GM, CubeFormat::ND, float> biasType; Matmul<aType, bType, cType, biasType> mm; REGIST_MATMUL_OBJ(&pipe, GetSysWorkSpacePtr(), mm, &tiling); // Initialization // CopyIn stage: Move data from the global memory to the local memory. mm.SetTensorA(gm_a); // Set the left cube A. mm.SetTensorB(gm_b); // Set the right cube B. mm.SetBias(gm_bias); // Set the bias. // Compute phase: Complete cube multiplication computation. while (mm.Iterate()) { // CopyOut phase: Copy data from the local memory to the global memory. mm.GetTensorC(gm_c); } // End the cube multiplication operation. mm.End(); |
An operator that supports hybrid compute of vectors and cubes is called a fused operator. Ascend C provides fused operator programming paradigm so that you can express the data flow of the fused operator based on the paradigm and quickly implement your own fused operator.
The fused operator data flow refers to the flow direction of the input and output of the fused operator between storage locations. Take a typical fused operator for the cube and vector computation as an example. The following figure shows the data flows between logical locations. (To simplify the description, bias is not listed.)

The sample code (pseudocode) of the entire process is as follows:
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 |
template<typename aType, typename bType, typename cType, typename biasType> __aicore__ inline void MatmulLeakyKernel<aType, bType, cType, biasType>::Process() { // Step 1: Initialize a Matmul object and copy the input data from the global memory to the Cube Core. uint32_t computeRound = 0; REGIST_MATMUL_OBJ(&pipe, GetSysWorkSpacePtr(), matmulObj); matmulObj.Init(&tiling); matmulObj.SetTensorA(aGlobal); matmulObj.SetTensorB(bGlobal); matmulObj.SetBias(biasGlobal); while (matmulObj.template Iterate<true>()) { // Step 2: Perform the internal Matmul compute. // Step 3: Move the Matmul compute result to the Vector Core. reluOutLocal = reluOutQueue_.AllocTensor<cType>(); matmulObj.template GetTensorC<true>(reluOutLocal, false, true); // Step 4: Perform the vector compute. AscendC::LeakyRelu(reluOutLocal, reluOutLocal, (cType)alpha, tiling.baseM * tiling.baseN); reluOutQueue_.EnQue(reluOutLocal); // Step 5: Move the output to the global memory. reluOutQueue_.DeQue<cType>(); ... AscendC::DataCopy(cGlobal[startOffset], reluOutLocal, copyParam); reluOutQueue_.FreeTensor(reluOutLocal); computeRound++; } matmulObj.End(); } |