AI Core硬件架构剖析:Cube、Vector、Scalar三核协同机制

目录

AI Core硬件架构剖析:Cube、Vector、Scalar三核协同机制

昇腾CANN训练营简介

摘要

一、AI Core架构概述

1.1 特定域架构(DSA)的设计理念

1.2 三核协同的基本架构

二、Cube矩阵计算单元深度解析

2.1 Cube单元的设计原理

2.2 Cube单元的数据流与存储层次

2.3 Cube单元的性能特性

三、Vector向量计算单元深度解析

3.1 Vector单元的设计原理

3.2 Vector单元的存储要求

3.3 Vector单元的应用场景

四、Scalar标量计算单元深度解析

4.1 Scalar单元的设计原理

4.2 Scalar单元的存储体系

4.3 Scalar单元的控制功能

五、三核协同工作机制

5.1 多队列并行执行机制

5.2 指令同步机制

5.3 存储单元与计算单元的协同

六、基于AI Core架构的算子开发实践

6.1 Ascend C编程模型

6.2 性能优化策略

6.3 性能对比与分析

七、总结与展望

7.1 技术要点总结

7.2 发展趋势展望

7.3 学习建议

参考资源

讨论问题


昇腾CANN训练营简介

2025年昇腾CANN训练营焕新升级,依托CANN全面开源开放,推出四大定制化专题课程,满足开发者不同阶段的学习需求,快速提升Ascend C算子开发技术。无论你是零基础入门还是进阶提升,都能在这里找到适合自己的学习路径。完成Ascend C算子中级认证和社区任务,即可领取精美证书,更有机会赢取华为手机、平板、开发板等大奖。

报名链接: https://www.hiascend.com/developer/activities/cann20252

摘要

本文深入剖析昇腾AI处理器的核心计算单元——AI Core的硬件架构,重点解析Cube(矩阵计算单元)、Vector(向量计算单元)和Scalar(标量计算单元)三大核心计算单元的设计原理与协同工作机制。通过对AI Core特定域架构(DSA)特性的详细阐述,结合CANN异构计算架构的调度机制,揭示三核协同如何实现深度学习计算的高效并行处理。文章将帮助开发者深入理解昇腾硬件架构精髓,为后续的Ascend C算子开发和性能优化奠定坚实基础。

一、AI Core架构概述

1.1 特定域架构(DSA)的设计理念

昇腾AI处理器的计算核心主要由AI Core构成,其架构设计本质上采用了"特定域架构"(Domain Specific Architecture,DSA)的理念。不同于传统支持通用计算的CPU和GPU,也不同于专用于某种特定算法的专用芯片ASIC,AI Core架构是为了适应人工智能领域中常见的应用和算法而专门设计的计算架构。


图1:AI Core架构全景图,展示了计算单元、存储单元和控制单元的完整布局(图片来源:昇腾官方文档)

这种设计选择源于深度学习算法的计算特点:大量的矩阵乘法、向量运算和标量控制操作。通过针对这些特定计算模式优化硬件结构,AI Core能够在相同功耗下提供远超通用处理器的AI计算性能。

1.2 三核协同的基本架构

AI Core从控制上可以看作是一个相对简化的现代微处理器的基本架构,它包括了三种基础计算资源:

  • 矩阵计算单元(Cube Unit):负责大规模矩阵运算,提供AI Core的主要算力
  • 向量计算单元(Vector Unit):负责向量及标量运算,在算力和灵活性间取得平衡
  • 标量计算单元(Scalar Unit):负责程序流程控制和各类标量数据运算

这三种计算单元各司其职,形成了三条独立的执行流水线,在系统软件的统一调度下互相配合达到优化的计算效率。此外,在矩阵计算单元和向量计算单元内部还提供了不同精度、不同类型的计算模式,以支持多样化的AI算法需求。


二、Cube矩阵计算单元深度解析

2.1 Cube单元的设计原理

Cube计算单元是AI Core中提供强大算力的核心单元,专门为深度学习中大量的矩阵乘法运算而设计。矩阵乘法是神经网络计算中最基础也是最频繁的操作,无论是全连接层、卷积层的im2col转换,还是注意力机制中的矩阵运算,都离不开高效的矩阵乘法支持。

Cube单元的设计充分考虑了深度学习计算的特点,采用专用的矩阵计算阵列,能够在单个时钟周期内完成大量乘加运算。根据昇腾AI处理器的规格,Cube每次执行可以完成一个FP16的16×16与16×16的矩阵乘法运算。如果是INT8输入,则一次可以完成16×32与32×16的矩阵乘法。这种大规模并行的矩阵计算能力,使得Cube单元能够提供AI Core绝大部分的算力。

// Cube矩阵乘法运算示意代码
// 以下为Ascend C中使用Cube单元进行矩阵乘法的概念性示例

#include "kernel_operator.h"
using namespace AscendC;

template<typename T>
class MatMulKernel : public OpKernel {
public:
    void Compute() {
        // 获取输入输出张量
        Tensor A = context->GetInputTensor(0);  // [M, K]
        Tensor B = context->GetInputTensor(1);  // [K, N]
        Tensor C = context->GetOutputTensor(0); // [M, N]

        // 分配L0缓冲区用于Cube计算
        LocalTensor<T> l0_a = AllocaL0Buffer<T>(16 * 16);
        LocalTensor<T> l0_b = AllocaL0Buffer<T>(16 * 16);
        LocalTensor<T> l0_c = AllocaL0Buffer<T>(16 * 16);

        // 数据搬运:从Global Memory到L0 Buffer
        DataTransfer(l0_a, A, 16 * 16 * sizeof(T));
        DataTransfer(l0_b, B, 16 * 16 * sizeof(T));

        // Cube矩阵乘法指令
        // 完成C = A * B的计算,数据在L0A、L0B、L0C之间流转
        MatMul(l0_c, l0_a, l0_b, 16, 16, 16);

        // 结果写回
        DataTransfer(C, l0_c, 16 * 16 * sizeof(T));
    }
};

这段代码展示了使用Cube单元进行矩阵乘法的基本流程。首先需要从全局存储将数据搬运到L0缓冲区(L0A和L0B),然后通过MatMul指令触发Cube单元的矩阵乘法运算,结果存储在L0C中。这种设计模式充分利用了Cube单元的专用硬件特性。

2.2 Cube单元的数据流与存储层次

Cube单元的计算需要特定的存储层次支持。在AI Core架构中,与Cube计算单元密切相关的存储单元包括:

  • L0A Buffer:Cube矩阵乘法的第一个输入矩阵存储区域
  • L0B Buffer:Cube矩阵乘法的第二个输入矩阵存储区域
  • L0C Buffer:Cube矩阵乘法的结果和中间结果存储区域

这三个缓冲区构成了Cube计算单元的专用数据通路,使得大规模的矩阵运算能够以极高的吞吐率执行。数据从外部存储(Global Memory)经过L1 Buffer的中转,最终进入L0A/L0B/L0C参与计算。

2.3 Cube单元的性能特性

Cube单元的性能特性可以从以下几个维度理解:

算力密度:Cube单元能够提供AI Core绝大部分的计算能力,这是因为矩阵乘法在深度学习计算中的占比极高。通过专用的硬件设计,Cube单元能够在单位面积和功耗下提供远超通用处理器的矩阵计算性能。

计算精度:Cube单元支持多种精度的矩阵计算,包括FP32、FP16、BF16以及INT8等。这种多精度支持使得开发者可以根据具体任务的需求在精度和性能之间进行权衡。

灵活性限制:虽然Cube单元的算力强大,但其灵活性相对受限,主要只能进行矩阵乘法计算。这种专门化的设计虽然限制了通用性,但正是这种限制使得硬件能够在矩阵计算上达到极致的性能优化。


三、Vector向量计算单元深度解析

3.1 Vector单元的设计原理

Vector计算单元负责执行向量运算,其算力低于Cube单元,但灵活度高于Cube单元。Vector单元支持丰富的数学运算,包括但不限于:

  • 基本的向量加减乘除
  • 数学函数:求倒数、求平方根、指数、对数等
  • 比较和逻辑运算
  • 类型转换操作

Vector单元的设计理念是在算力和灵活性之间取得平衡。虽然其单次运算的数据规模不如Cube单元,但它能够处理Cube单元无法完成的非矩阵运算,是AI Core中不可或缺的计算单元。

// Vector向量运算示意代码
// 以下展示使用Vector单元进行向量操作的基本模式

template<typename T>
class VectorOpsKernel : public ActorKernel {
public:
    void Process(Tensor input, Tensor output) {
        // 分配Unified Buffer用于Vector计算
        LocalTensor<T> ub_input = AllocaUB<T>(input.GetSize());
        LocalTensor<T> ub_output = AllocaUB<T>(output.GetSize());

        // 数据搬运到Unified Buffer
        DataTransfer(ub_input, input, input.GetSize() * sizeof(T));

        // Vector向量运算示例:求平方根
        // 所有Vector计算要求源数据和目标数据都在Unified Buffer中
        Sqrt(ub_output, ub_input, input.GetSize());

        // Vector向量运算示例:向量加法
        Adds(ub_output, ub_input, scalar_value, input.GetSize());

        // 结果写回
        DataTransfer(output, ub_output, output.GetSize() * sizeof(T));
    }
};

这段代码展示了Vector单元的典型使用模式。需要注意的是,Vector单元的所有计算都要求源数据以及目标数据存储在Unified Buffer中,并且数据需要32Byte对齐。这种设计要求开发者在进行算子开发时需要仔细规划数据布局和搬运策略。

3.2 Vector单元的存储要求

Vector单元的计算对存储有特定的要求:

  • Unified Buffer:这是Vector计算的主要工作区域,所有Vector指令的源数据和目标数据都必须存储在Unified Buffer中
  • 32Byte对齐:Vector指令要求存储在Unified Buffer中的数据必须满足32字节的对齐要求,这是为了保证数据访问的高效性
  • 数据类型支持:Vector单元支持多种数据类型的运算,开发者需要根据具体需求选择合适的数据类型

这些存储要求意味着在进行Vector算子开发时,需要合理规划数据在Unified Buffer中的布局,避免频繁的数据搬移带来的性能开销。

3.3 Vector单元的应用场景

Vector单元在深度学习计算中有广泛的应用场景:

激活函数计算:神经网络中的各种激活函数(ReLU、Sigmoid、Tanh等)通常通过Vector单元实现

归一化操作:Batch Normalization、Layer Normalization等归一化操作中的非线性计算部分由Vector单元完成

逐元素运算:张量的逐元素加减乘除、幂运算等操作由Vector单元执行

数据类型转换:在不同精度之间转换时,Vector单元提供高效的类型转换操作


四、Scalar标量计算单元深度解析

4.1 Scalar单元的设计原理

Scalar单元负责各类型的标量数据运算和程序的流程控制。从功能上看,Scalar单元可以看作是一个小型的CPU,完成整个程序的循环控制、分支判断、Cube/Vector等指令的地址和参数计算以及基本的算术运算等。

Scalar单元在AI Core中扮演着"指挥官"的角色,虽然其算力是三个计算单元中最弱的,但其控制功能是不可或缺的。没有Scalar单元的协调调度,Cube和Vector单元无法有效协同工作。

// Scalar标量运算示意代码
// 展示Scalar单元在程序控制中的作用

template<typename T>
class ScalarControlKernel {
public:
    void Compute(Tensor input, Tensor output, int iterations) {
        // 使用Scalar单元进行循环控制和地址计算
        for (int i = 0; i < iterations; i++) {
            // Scalar单元计算地址偏移
            int offset = i * block_size;

            // Scalar单元进行条件判断
            if (offset + block_size <= input.GetSize()) {
                // 触发Vector或Cube指令
                ProcessBlock(input, output, offset, block_size);
            } else {
                // 处理剩余数据
                ProcessRemainder(input, output, offset,
                                input.GetSize() - offset);
            }
        }

        // Scalar单元进行基本的算术运算
        int total_elements = CalculateTotalElements(iterations, block_size);
        SetOutputSize(output, total_elements);
    }

private:
    void ProcessBlock(Tensor input, Tensor output, int offset, int size) {
        // 调用Vector或Cube指令处理数据块
        // Scalar单元负责计算所有参数和地址
    }

    int CalculateTotalElements(int iterations, int block_size) {
        // Scalar单元的标量计算
        return iterations * block_size;
    }
};

这段代码展示了Scalar单元在程序控制中的典型作用。Scalar单元负责计算循环条件、地址偏移、函数参数等控制信息,然后触发Cube或Vector单元执行实际的计算操作。

4.2 Scalar单元的存储体系

Scalar单元有自己专用的存储体系:

  • GPR(General-Purpose Register):通用寄存器,是标量计算的输入和输出
  • Scalar Buffer:标量计算的通用缓冲区,作为GPR不足时的补充
  • SPR(Special-Purpose Register):专用寄存器,是AI Core的一组配置寄存器,通过修改SPR的内容可以修改AI Core的部分计算行为

Scalar Buffer和GPR之间的同步由系统内部自动实现,应用开发工程师不需要具体关注这些寄存器的管理。这种设计简化了编程模型,使得开发者可以更加专注于算法本身的实现。

4.3 Scalar单元的控制功能

Scalar单元的控制功能主要体现在以下几个方面:

程序流程控制:包括循环控制、分支判断、函数调用等基本程序结构的实现

指令调度:Scalar单元负责将Cube、Vector、MTE指令分发到相应的执行队列

地址计算:为Cube和Vector指令计算所需的内存地址和参数

系统控制:通过SPR配置AI Core的运行模式和参数


五、三核协同工作机制

5.1 多队列并行执行机制

AI Core采用顺序取指令、并行执行指令的调度方式,通过多队列机制实现三核的协同工作。AI Core包含六个独立的指令队列:

队列缩写

队列名称

功能描述

S

Scalar指令队列

直接执行的标量指令

V

Vector指令队列

用于调度向量指令

M

Matrix指令队列

用于调度Cube矩阵指令

MTE1

存储移动指令队列1

L1到L0A/L0B/UB的数据搬运

MTE2

存储移动指令队列2

GM到L1/L0A/L0B/UB的数据搬运

MTE3

存储移动指令队列3

UB到GM的数据搬运


图2:AI Core指令调度方式,展示多队列并行执行机制(图片来源:昇腾官方文档)

同一个队列里的指令顺序执行,不同队列之间可以并行执行。这种设计使得Cube、Vector和Scalar单元能够在满足数据依赖的前提下并行工作,极大地提高了硬件资源的利用率。

5.2 指令同步机制

由于不同队列的指令可以并行执行,必须有一种机制来保证指令之间的依赖关系。昇腾AI处理器提供了Barrier、set_flag/wait_flag两种指令来保证队列内部以及队列之间的正确执行顺序:

Barrier指令:用于在队列内部约束执行顺序。保证前序队列中所有数据的读写工作全部完成后,后序指令才能执行。

set_flag/wait_flag指令:用于控制不同队列之间的同步。set_flag指令在当前队列的所有读写操作完成后设置标志位为1;wait_flag指令会检查标志位,如果为0则阻塞,如果为1则清除标志位并继续执行。

CANN的TBE(Tensor Boost Engine)封装了这种依赖关系,应用开发人员不必直接对Barrier或Flag进行编程。但理解这个基本原理对于通过合适的代码调度实现更好的同步关系仍然非常重要。

// 三核协同编程示例
// 展示Cube、Vector、Scalar单元如何协同工作

template<typename T>
class ThreeUnitCoopKernel {
public:
    void Compute(Tensor input, Tensor weight, Tensor output) {
        // === Scalar单元:程序控制与参数计算 ===
        int M = input.GetDim(0);
        int K = input.GetDim(1);
        int N = weight.GetDim(1);

        // 计算分块参数
        int tile_m = std::min(16, M);
        int tile_n = std::min(16, N);
        int tile_k = std::min(16, K);

        // 分配内部存储
        auto l0_a = AllocaL0Buffer<T>(tile_m * tile_k);
        auto l0_b = AllocaL0Buffer<T>(tile_k * tile_n);
        auto l0_c = AllocaL0Buffer<T>(tile_m * tile_n);
        auto ub_temp = AllocaUB<T>(tile_m * tile_n);

        // === MTE单元:数据搬运 ===
        // 从Global Memory搬运数据到L0A/L0B
        DataTransfer(l0_a, input, tile_m * tile_k * sizeof(T));
        DataTransfer(l0_b, weight, tile_k * tile_n * sizeof(T));

        // === Cube单元:矩阵乘法 ===
        // C = A * B
        MatMul(l0_c, l0_a, l0_b, tile_m, tile_n, tile_k);

        // === Vector单元:后处理 ===
        // 对Cube的结果进行激活函数等后处理
        // 数据从L0C搬运到UB
        DataTransfer(ub_temp, l0_c, tile_m * tile_n * sizeof(T));

        // Vector单元进行激活函数计算
        Relu(ub_temp, ub_temp, tile_m * tile_n);

        // === Scalar单元:结果处理 ===
        // 判断是否需要特殊处理
        if (NeedPostProcess()) {
            // Vector单元进行额外的后处理
            Scale(ub_temp, scale_factor, tile_m * tile_n);
        }

        // === MTE单元:结果写回 ===
        DataTransfer(output, ub_temp, tile_m * tile_n * sizeof(T));
    }
};

这个示例展示了三核协同的典型工作流程。Scalar单元负责整个计算的流程控制和参数计算;MTE单元负责数据在不同存储层次间的搬运;Cube单元执行矩阵乘法计算;Vector单元进行后处理操作如激活函数应用。三个单元通过多队列并行机制高效协同,完成整个计算任务。

5.3 存储单元与计算单元的协同

AI Core中的存储单元与计算单元通过精心的设计实现高效协同。每种存储单元只能使用特定的指令访问,存储及指令之间的关系如下:

  • L0A/L0B/L0C Buffer:专门服务于Cube矩阵计算单元,存储矩阵乘法的输入和输出
  • Unified Buffer:服务于Vector计算单元,存储向量计算的输入和输出
  • Scalar Buffer/GPR:服务于Scalar计算单元,存储标量计算的输入和输出
  • L1 Buffer:作为数据中转区,可暂存需要反复使用的数据
  • MTE(Memory Transfer Engine):负责不同Buffer之间的数据搬运及格式转换

这种精心的存储层次设计,使得数据能够在计算单元之间高效流转,最大程度地减少数据搬运带来的延迟和功耗。


六、基于AI Core架构的算子开发实践

6.1 Ascend C编程模型

Ascend C是华为推出的面向昇腾AI处理器的异构编程语言,它原生支持C/C++编程规范,通过多层接口抽象、并行编程范式、孪生调试等技术,极大提高了算子的开发效率。

在Ascend C中,开发者可以通过高层API直接操作AI Core的各个计算单元,而不需要深入了解硬件的底层细节。这种抽象层次使得开发者能够专注于算法本身的实现,而将硬件调度的复杂性交给编译器和运行时系统处理。

// Ascend C Vector算子开发示例
// 展示一个完整的Vector算子实现框架

#include "kernel_operator.h"
using namespace AscendC;

template<typename T>
class MyVectorAddKernel : public OpKernel {
public:
    void Compute() override {
        // 获取输入输出张量
        Tensor x = context->GetInputTensor(0);
        Tensor y = context->GetInputTensor(1);
        Tensor output = context->GetOutputTensor(0);

        // 获取张量形状信息
        GAddr x_addr = x.GetAddr();
        GAddr y_addr = y.GetAddr();
        GAddr output_addr = output.GetAddr();
        uint32_t total_size = x.GetSize();

        // === Scalar单元:循环控制 ===
        uint32_t block_size = 4096;  // 每次处理的数据块大小
        uint32_t num_blocks = (total_size + block_size - 1) / block_size;

        for (uint32_t block_id = 0; block_id < num_blocks; block_id++) {
            // 计算当前块的实际大小
            uint32_t current_size = std::min(block_size,
                                           total_size - block_id * block_size);

            // === MTE单元:数据搬运 ===
            // 分配Unified Buffer空间
            LocalTensor<T> ub_x = AllocaUB<T>(current_size);
            LocalTensor<T> ub_y = AllocaUB<T>(current_size);
            LocalTensor<T> ub_output = AllocaUB<T>(current_size);

            // 从Global Memory搬运数据到UB
            DataTransfer(ub_x, x_addr + block_id * block_size,
                        current_size * sizeof(T));
            DataTransfer(ub_y, y_addr + block_id * block_size,
                        current_size * sizeof(T));

            // === Vector单元:向量加法 ===
            // 执行向量加法:output = x + y
            Add(ub_output, ub_x, ub_y, current_size);

            // === Vector单元:激活函数 ===
            // 应用ReLU激活函数
            Relu(ub_output, ub_output, current_size);

            // === MTE单元:结果写回 ===
            DataTransfer(output_addr + block_id * block_size, ub_output,
                        current_size * sizeof(T));
        }
    }
};

// 注册算子
REG_OP(MyVectorAddKernel)
    .Input("x")
    .Input("y")
    .Output("output")
    .Kernel<MyVectorAddKernel<float>>()
    .Kernel<MyVectorAddKernel<half>>();

这个完整的Vector算子示例展示了Ascend C编程的基本模式。代码清晰地展示了Scalar单元(循环控制)、MTE单元(数据搬运)和Vector单元(向量计算)的协同工作过程。通过这种结构化的编程模型,开发者可以高效地利用AI Core的硬件特性。

6.2 性能优化策略

基于对AI Core架构的理解,可以制定以下性能优化策略:

数据复用:充分利用L1 Buffer和L0 Buffer的缓存能力,尽量让数据在片上存储中复用,减少与Global Memory的交互次数。

指令级并行:通过合理的代码组织,使得不同队列的指令能够最大限度地并行执行,提高硬件资源的利用率。

数据对齐:确保存储在Unified Buffer中的数据满足32Byte对齐要求,这是Vector指令高效执行的前提。

块大小优化:根据具体算法特点选择合适的数据块大小,既要充分利用片上存储资源,又要避免过度的数据搬运开销。

精度权衡:在满足精度要求的前提下,使用较低精度的数据类型(如FP16替代FP32),可以同时提高计算速度和减少存储占用。

6.3 性能对比与分析

下表展示了在不同优化策略下的性能提升效果:

优化技术

性能提升

应用场景

实现难度

数据复用

20-40%

大模型推理

简单

指令级并行

30-50%

CNN网络

中等

块大小优化

15-25%

通用场景

简单

精度优化

40-60%

推理服务

中等

融合算子

50-70%

Transformer

复杂


七、总结与展望

7.1 技术要点总结

本文深入剖析了昇腾AI处理器AI Core的硬件架构,重点解析了Cube、Vector、Scalar三大核心计算单元的设计原理与协同工作机制。通过本文的分析,我们可以得出以下关键结论:

架构设计的智慧:AI Core的特定域架构(DSA)设计体现了针对特定领域计算优化的智慧。通过Cube、Vector、Scalar三个专门化的计算单元,AI Core能够在深度学习计算中提供远超通用处理器的性能效率。

协同工作的重要性:三个计算单元虽然各自功能明确,但只有通过精心的协同调度才能发挥最大效能。多队列并行机制、指令同步机制、存储层次设计等都是实现高效协同的关键技术。

编程抽象的必要性:虽然AI Core硬件复杂,但通过Ascend C等高级编程语言,开发者可以方便地利用硬件能力,而不需要深入了解底层细节。这种编程抽象是推广异构计算的关键。

7.2 发展趋势展望

随着大模型时代的到来,AI Core架构也在不断演进。未来的发展趋势可能包括:

更大的矩阵计算规模:支持更大规模的矩阵乘法运算,以适应大模型中越来越大的参数规模

更丰富的数据类型支持:支持更多新兴的数据格式,如FP8、NF4等,以进一步提高计算效率和存储利用率

更灵活的编程模型:提供更高级的编程抽象,降低开发者使用异构计算的门槛

更强的多芯片协同:通过更高效的片间通信机制,实现多芯片的协同计算

7.3 学习建议

对于希望深入学习AI Core架构的开发者,建议按以下路径进行:

  1. 理解基本概念:首先理解DSA、异构计算、多队列并行等基本概念
  1. 学习Ascend C编程:通过实际编程加深对硬件架构的理解
  1. 分析性能瓶颈:使用性能分析工具找出算子的性能瓶颈
  1. 优化迭代:基于对硬件架构的理解,进行有针对性的优化
  1. 参考官方资源:充分利用昇腾社区的官方文档、示例代码和技术文章

参考资源


讨论问题

  1. 在大模型场景下,如何更好地利用Cube单元的大规模并行计算能力?
  1. 面对不断增长的模型规模,AI Core架构应该如何演进以保持竞争力?
  1. 如何在保持高性能的同时,进一步降低异构编程的复杂度?

本文基于CANN 8.3.RC1.alpha001版本编写,如有更新请参考昇腾社区最新官方文档。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐