SIMD and SIMT Programming

Abstract Hardware Architecture

Hybrid programming of SIMD and SIMT on the AI Core combines the parallel compute capability of SIMD with the discrete memory access advantage of SIMT, achieving efficient collaboration between vector-level parallelism and thread-level parallelism. Currently, this technology is supported only by Atlas 350 Accelerator Card.

The entire execution process uses the Vector Function (VF) as the basic scheduling unit. A VF is a basic function block. SIMD and SIMT programming supports flexible switching between SIMD and SIMT execution modes within the same operator. Two different types of VFs can be quickly switched. Each VF represents an independent compute task segment, which usually corresponds to a segment of logic that can be processed in parallel within the operator. This achieves a better balance between performance, energy efficiency, and development efficiency. In SIMD and SIMT programming:

In SIMD and SIMT programming, SIMT simplifies the development of complex operators and irregular control flows. SIMD, on the other hand, implements efficient data parallel processing based on vector registers and instructions, allowing a single instruction to process multiple pieces of data and improving the throughput per cycle. SIMD and SIMT programming allows you to perform refined mapping based on operator characteristics. Regular element-wise operations achieve high bandwidth and compute power utilization through SIMD, while irregular or branch-containing computations are handled by SIMT to mitigate divergence and control complexity. At the system level, this helps improve hardware utilization and energy efficiency. It also facilitates optimizations such as operator fusion and data reuse. An operator can contain both continuous regular computations that are well-suited to SIMD and discrete access tasks that are better handled by SIMT, thereby leveraging the advantages of both SIMD and SIMT within the same operator.

As shown in Figure 1, the internal execution processes of SIMD and SIMT are as follows:

Figure 1 SIMD and SIMT programming hardware architecture

The following table describes the differences between SIMD and SIMT programming.

Table 1 Core differences between SIMD and SIMT

Dimension

SIMD

SIMT

Programming model

Single instruction, multiple data (SIMD), which is based on vector registers and vector instructions.

Single instruction, multiple threads (SIMT), which is executed in parallel by thread.

Data movement mode

Data is explicitly moved from the Unified Buffer to the vector register through Load/Store.

Data cannot be directly moved from the Global Memory to the SIMD vector register.

Data in the Global Memory or Unified Buffer can be directly read and written.

Application scenario

Regular and continuous element-wise operations, such as convolution, matrix multiplication, and vector operations.

Complex logic like irregular, branch-containing, and dynamic access logic, such as attention mechanism and sparse operations

Although SIMD and SIMT differ significantly in their programming models and execution mechanisms, they share the following key resources at the hardware level:

Memory Hierarchy

In the SIMD and SIMT programming scenario, multiple types of memory spaces can be accessed. The following table summarizes the scopes and lifecycles of common memory types.

Memory Type

Scope

Lifecycle

Physical Location

Characteristic

Global memory

All kernel functions

Application

Device

Large capacity and low bandwidth

Shared memory

Single-core kernel function

Kernel function

Vector Core

Small capacity and high bandwidth

SIMT register

Thread

SIMT VF function

Device

Extremely small capacity and extremely high bandwidth

SIMD register

Register file

SIMD VF function

Vector Core

Extremely small capacity and extremely high bandwidth

The following figure shows the overall memory architecture.

Figure 2 SIMD and SIMT programming memory model diagram

In SIMT mode, the working process of each memory is as follows:

In SIMD mode, the working process of each memory is as follows:

UB Allocation

The total memory space of the UB is 256 KB. As shown in Figure 3, the UB is divided into four main areas based on functions: static memory, dynamic memory, reserved space, and data cache, in ascending order of addresses. The structure is as follows:

  1. Static memory: The static memory is a segment of memory space (with a specified size) allocated from the start address of the memory. The size of the static memory is determined during compilation and cannot be dynamically modified.
    1
    2
    // The static memory is allocated through arrays. Example:
    __ubuf__ char staticBuf[1024];
    
  2. Dynamic memory (will be supported in later versions): The dynamic memory is located after the static memory. The dynamic memory size is specified by the dynUBufSize parameter in <<<>>>. You can allocate the dynamic memory in the following ways:
    • Allocate the memory by calling relevant TPipe APIs.
    • Allocate the memory by calling the Alloc API of LocalMemAllocator.
    • Allocate the memory using dynamic arrays.
      // The dynamic memory is allocated through dynamic arrays. Example:
      extern __ubuf__ char dynamicBuf[];

    All the preceding three methods allocate memory starting from the end position of the static memory. Address space overlapping will occur if these methods are used at the same time, causing undefined behavior. Therefore, you can use only one of the methods to allocate the dynamic memory.

  3. Reserved space: space reserved for the compiler and Ascend C. The size is fixed at 8 KB.
  4. Data cache: The data cache is dedicated for SIMT. Its size must be greater than or equal to 32 KB.

The method of allocating the dynamic memory through dynamic arrays is under development and will be supported in later versions.

  • Data cache = Total UB size (256 KB) – Static memory – Dynamic memory – Reserved space (8 KB)
  • If the data cache is less than 32 KB, a verification error will occur.
  • In the SIMD and SIMT programming scenario, an operator cannot use all Unified Buffer space. In addition to the 8 KB reserved space, at least 32 KB of data cache space must be reserved for SIMT.
Figure 3 UB allocation diagram

Kernel Function Definition

Call Hierarchy

The following figure shows the supported call relationship.

Figure 4 Function call relationship

Programming Sample

For details about the complete code of the operator described in the sample, see the sample of implementing the gather&adds operator using hybrid programming of SIMD and SIMT. For more restrictions on SIMT function declaration, see the syntax restrictions.
 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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
__simt_vf__ __launch_bounds__(THREAD_COUNT) inline void simt_gather(
    __gm__ float* input,
    __gm__ uint32_t* index,
    __ubuf__ float* gather_output,
    uint32_t input_total_length,
    uint32_t index_total_length,
    uint32_t output_total_length)
{
    if (threadIdx.x >= output_total_length) {
        return;
    }
    // blockIdx will be supported later.
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx >= index_total_length) {
        return;
    }

    uint32_t gather_idx = index[idx];
    if (gather_idx >= input_total_length) {
        return;
    }

    gather_output[threadIdx.x] = input[gather_idx];
}

__simd_vf__ inline void simd_adds(__ubuf__ float* output, __ubuf__ float* input,
    uint32_t count, uint32_t one_repeat_size, uint16_t repeat_times)
{
    AscendC::Reg::RegTensor<float> src_reg0;
    AscendC::Reg::RegTensor<float> dst_reg0;
    AscendC::Reg::MaskReg mask_reg; 

    for (uint16_t i = 0; i < repeat_times; i++) {
        mask_reg = AscendC::Reg::UpdateMask<float>(count);
        AscendC::Reg::LoadAlign(src_reg0, input + i * one_repeat_size);
        AscendC::Reg::Adds(dst_reg0, src_reg0, ADDS_ADDEND, mask_reg);
        AscendC::Reg::StoreAlign(output + i * one_repeat_size, dst_reg0, mask_reg);
    }
}

__global__ __aicore__ void gather_and_adds_kernel(__gm__ float* input, __gm__ uint32_t* index, __gm__ float* output,
    uint32_t input_total_length, uint32_t index_total_length)
{
    KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY);
    AscendC::LocalMemAllocator<AscendC::Hardware::UB> ub_allocator;
    // 1. gather numbers from input.
    uint32_t index_total_length_per_block = index_total_length / AscendC::GetBlockNum();
    AscendC::LocalTensor<float> gather_output = ub_allocator.Alloc<float>(index_total_length_per_block);
    asc_vf_call<simt_gather>(dim3(THREAD_COUNT),input, index,
                             (__ubuf__ float *)gather_output.GetPhyAddr(),
                             input_total_length,
                             index_total_length,
                             index_total_length_per_block);

    // 2. use reg compute api to do addition.
    AscendC::LocalTensor<float> adds_output = ub_allocator.Alloc<float>(index_total_length_per_block);
    constexpr uint32_t one_repeat_size = AscendC::GetVecLen() / sizeof(float);
    uint16_t repeat_times = (index_total_length_per_block + one_repeat_size - 1) / one_repeat_size;
    asc_vf_call<simd_adds>((__ubuf__ float *)adds_output.GetPhyAddr(),
        (__ubuf__ float *)gather_output.GetPhyAddr(), index_total_length_per_block, one_repeat_size, repeat_times);

    AscendC::SetFlag<AscendC::HardEvent::V_MTE3>(0);
    AscendC::WaitFlag<AscendC::HardEvent::V_MTE3>(0);

    // 3. copy data to global memory.
    AscendC::GlobalTensor<float> output_global_tensor;
    output_global_tensor.SetGlobalBuffer(output + index_total_length_per_block * AscendC::GetBlockIdx());
    AscendC::DataCopy(output_global_tensor, adds_output, index_total_length_per_block);
}

int main(int argc, char *argv[])
{
     ...
     //numBlocks only supports one dimension currently.
     gather_and_adds_kernel<<<numBlocks, dynUBufSize, stream>>>(input_device, index_device, output_device, input_total_length, index_total_length);
     ...
}