fix itemlist and title reference for ch06 (#47)

Co-authored-by: Corleone <liuchao195@huawei.com>
This commit is contained in:
Corleone
2022-03-09 22:55:09 +08:00
committed by GitHub
parent d0e1ee5dfa
commit 51117def0e
4 changed files with 64 additions and 3712 deletions

View File

@@ -7,31 +7,22 @@
现代GPU在十分有限的面积上实现了极强的计算能力和极高的储存器以及IO带宽。一块高端的GPU中晶体管数量已经达到主流CPU的两倍而且显存已经达到了16GB以上,工作频率也达到了1GHz。GPU的体系架构由两部分组成分别是流处理阵列和存储器系统两部分通过一个片上互联网络连接。流处理器阵列和存储器系统都可以单独扩展规格可以根据产品的市场定位单独裁剪。如GV100的组成如 :numref:`gv100`所示:
![Volta GV100 [@2017NVIDIA] []](../img/ch06/V100.svg)
![Volta GV100](../img/ch06/V100.svg)
:width:`800px`
:label:`gv100`
- 6个GPU处理集群GPU Processing ClusterGPC, 每个GPC含有
- 7个纹理处理集群Texture Processing Cluster, TPC (每个TPC含有两个流多处理器Streaming Multiprocessor, SM)
- 14个SM
- 7个纹理处理集群Texture Processing Cluster, TPC (每个TPC含有两个流多处理器Streaming Multiprocessor, SM)
- 14个SM
- 84个SM, 每个流多处理器含有:
- 64个32位浮点运算单元
- 64个32位整数运算单元
- 32个64位浮点运算单元
- 8个张量核
- 4个纹理单元
- 64个32位浮点运算单元
- 64个32位整数运算单元
- 32个64位浮点运算单元
- 8个张量核
- 4个纹理单元
- 8个512-bit内存控制器
一个完整的GV100 GPU含有84个SM5376个32位浮点运算单元5376个32位整型运算单元2688个64位浮点运算单元672个张量运算单元和336个纹理单元。一对内存控制器控制一个HBM2 DRAM堆栈。 :numref:`gv100`中展示的为带有84个SM的GV100 GPU(不同的厂商可以使用不同的配置)Tesla V100则含有80个SM。
一个完整的GV100 GPU含有84个SM5376个32位浮点运算单元5376个32位整型运算单元2688个64位浮点运算单元672个张量运算单元和336个纹理单元。一对内存控制器控制一个HBM2 DRAM堆栈。 :numref:`gv100`中展示的为带有84个SM的GV100 GPU(不同的厂商可以使用不同的配置)Tesla V100则含有80个SM。
### 硬件加速器的存储单元
@@ -50,8 +41,9 @@
由于寄存器的高速读取特性,因此每次计算都离不开寄存器的参与。接着是一级缓存和共享内存,然后是常量内存、纹理内存、全局内存,最后则是主机端内存。根据不同存储器之间的存储速度的数量级的变化规律,选用适当类型的内存以及最大化地利用它们,从而发挥硬件的最大算力,减少计算时间。
### 硬件加速器的计算单元
:label:`compute-unit-title`
为了支持不同的神经网络模型,加速器会提供以下几种计算单元,不同的网络层可以根据需要选择使用对应的计算单元。如 :numref:`compute-unit`所示
为了支持不同的神经网络模型,加速器会提供以下几种计算单元,不同的网络层可以根据需要选择使用对应的计算单元。如 :numref:`compute-unit`所示
- **标量计算单元**与标准的精简指令运算集Reduced Instruction Set ComputerRISC相似一次计算一个标量元素。
@@ -61,27 +53,27 @@
- **三维向量计算单元**:一次完成一个矩阵的乘法,专为神经网络应用设计的计算单元,更充分利用数据重复特性,隐藏数据通信带宽与数据计算的差距。
![多种计算单元[]](../img/ch06/compute_unit.svg)
![多种计算单元](../img/ch06/compute_unit.svg)
:width:`800px`
:label:`compute-unit`
GPU计算单元主要由标量计算单元和三维向量计算单元组成。如 :numref:`SM`所示,对于每个SM其中64个32位浮点运算单元、64个32位整数运算单元、32个64位浮点运算单元均为标量计算单元。而8个张量核则是专为神经网络应用设计的三维向量计算单元。
GPU计算单元主要由标量计算单元和三维向量计算单元组成。如 :numref:`SM`所示,对于每个SM其中64个32位浮点运算单元、64个32位整数运算单元、32个64位浮点运算单元均为标量计算单元。而8个张量核则是专为神经网络应用设计的三维向量计算单元。
![Volta GV100 流多处理器SM [@2017NVIDIA] []](../img/ch06/SM.svg)
![Volta GV100 流多处理器SM](../img/ch06/SM.svg)
:width:`800px`
:label:`SM`
张量核Tensor Core每个时钟周期完成一次4x4的矩阵乘累加计算,如 :numref:`tensorcore`
张量核Tensor Core每个时钟周期完成一次$4\times4$的矩阵乘累加计算,如 :numref:`tensorcore`
```
```cpp
D = A * B + C
```
![Tensor Core 4x4矩阵乘累加计算[]](../img/ch06/tensor_core.svg)
![Tensor Core $4\times4$矩阵乘累加计算](../img/ch06/tensor_core.svg)
:width:`800px`
:label:`tensorcore`
其中A,B,C和D都是4x4的矩阵矩阵乘累加的输入矩阵A和B是FP16的矩阵累加矩阵C和D可以是FP16也可以是FP32。 V100的张量核是可编程的矩阵乘法和累加计算单元可以提供多达125 Tensor TFLOPS(Tera Floating-point Operations Per Second)的训练和推理应用。相比于普通的FP32计算单元可以提速10倍以上。
其中A,B,C和D都是$4\times4$的矩阵矩阵乘累加的输入矩阵A和B是FP16的矩阵累加矩阵C和D可以是FP16也可以是FP32。 V100的张量核是可编程的矩阵乘法和累加计算单元可以提供多达125 Tensor TFLOPS(Tera Floating-point Operations Per Second)的训练和推理应用。相比于普通的FP32计算单元可以提速10倍以上。
### DSA芯片架构
@@ -91,4 +83,4 @@ GPU计算单元主要由标量计算单元和三维向量计算单元组成。
:width:`800px`
:label:`davinci_architecture`
昇腾AI芯片的计算核心主要由AI Core构成负责执行标量、向量和张量相关的计算密集型算子。AI Core采用了达芬奇架构基本结构如 :numref:`davinci_architecture`所示从控制上可以看成是一个相对简化的现代微处理器基本架构。它包括了三种基础计算单元矩阵计算单元Cube Unit、向量计算单元Vector Unit和标量计算单元Scalar Unit。这三种计算单元分别对应了张量、向量和标量三种常见的计算模式在实际的计算过程中各司其职形成了三条独立的执行流水线在系统软件的统一调度下互相配合达到优化计算效率的目的。 同GPU类似在矩阵乘加速设计上在AICore中也提供了矩阵计算单元作为昇腾AI芯片的核心计算模块意图高效解决矩阵计算的瓶颈问题。矩阵计算单元提供强大的并行乘加计算能力可以用一条指令完成两个16\*16矩阵的相乘运算等同于在极短时间内进行了16\*16\*16=4096个乘加运算并且可以实现FP16的运算精度。
昇腾AI芯片的计算核心主要由AI Core构成负责执行标量、向量和张量相关的计算密集型算子。AI Core采用了达芬奇架构基本结构如 :numref:`davinci_architecture`所示从控制上可以看成是一个相对简化的现代微处理器基本架构。它包括了三种基础计算单元矩阵计算单元Cube Unit、向量计算单元Vector Unit和标量计算单元Scalar Unit。这三种计算单元分别对应了张量、向量和标量三种常见的计算模式在实际的计算过程中各司其职形成了三条独立的执行流水线在系统软件的统一调度下互相配合达到优化计算效率的目的。 同GPU类似在矩阵乘加速设计上在AICore中也提供了矩阵计算单元作为昇腾AI芯片的核心计算模块意图高效解决矩阵计算的瓶颈问题。矩阵计算单元提供强大的并行乘加计算能力可以用一条指令完成两个$16\times16$矩阵的相乘运算,等同于在极短时间内进行了$16\times16\times16=4096$个乘加运算并且可以实现FP16的运算精度。

View File

@@ -8,6 +8,7 @@
虽然GPU在面向向量、矩阵以及张量的计算上引入许多新颖的优化设计但由于GPU需要支持的计算类型复杂芯片规模大、能耗高人们开始将更多的精力转移到深度学习硬件加速器的设计上来。和传统CPU和GPU芯片相比新型深度学习加速器会有更高的性能以及更低的能耗。未来随着人们真正进入智能时代智能应用的普及会越来越广泛到那时每台服务器、每台智能手机、每个智能摄像头都需要使用加速器。
### 硬件加速器设计的思路
:label:`accelerator-design-title`
近些年来,计算机体系结构的研究热点之一就是深度学习硬件加速器的设计。在体系结构的研究中,能效和通用性是两个重要的衡量指标。能效关注单位能耗下基本计算的次数,通用性主要指芯片能够覆盖的任务种类。

View File

@@ -1,11 +1,13 @@
加速器基本编程原理
------------------
:label:`accelerator-program-title`
本章前两节主要介绍了这些硬件加速器设计的意义、思路以及基本组成原理。软硬件协同优化作为构建高效AI系统的一个重要指导思想需要软件算法/软件栈和硬件架构在神经网络应用中互相影响、紧密耦合。为了最大限度地发挥加速器的优势,要求能够基于硬件系统架构设计出一套较为匹配的指令或编程(操纵)方法。因此,本节将以[1.2.3](#硬件加速器的计算单元){reference-type="ref" reference="硬件加速器的计算单元"}中介绍的Tensor Core为例着重介绍加速器的可编程性以及如何通过编程使能加速器提升神经网络算子的计算效率。
本章前两节主要介绍了这些硬件加速器设计的意义、思路以及基本组成原理。软硬件协同优化作为构建高效AI系统的一个重要指导思想需要软件算法/软件栈和硬件架构在神经网络应用中互相影响、紧密耦合。为了最大限度地发挥加速器的优势,要求能够基于硬件系统架构设计出一套较为匹配的指令或编程(操纵)方法。因此,本节将以 :numref:`compute-unit-title`中介绍的Tensor Core为例着重介绍加速器的可编程性以及如何通过编程使能加速器提升神经网络算子的计算效率。
### 硬件加速器的可编程性
:label:`accelerator-programable-title`
[1.1.2](#硬件加速器设计的思路){reference-type="ref" reference="硬件加速器设计的思路"}节中列出的硬件加速器均具有一定的可编程性程序员可以通过软件编程有效的使能上述加速器进行计算加速。但出于计算效率和易用性等方面考虑将编程使能方式分为不同等级一般包括算子库层级编程原语层级以及指令层级。为了更具象的解释上述层级的区别仍以Volta架构的Tensor Core加速器为例由高层至底层对比介绍这三种不同编程方式
:numref:`accelerator-design-title`节中列出的硬件加速器均具有一定的可编程性程序员可以通过软件编程有效的使能上述加速器进行计算加速。但出于计算效率和易用性等方面考虑将编程使能方式分为不同等级一般包括算子库层级编程原语层级以及指令层级。为了更具象的解释上述层级的区别仍以Volta架构的Tensor Core加速器为例由高层至底层对比介绍这三种不同编程方式
- **算子库层级**如cuBLAS基本矩阵与向量运算库cuDNN深度学习加速库均通过Host端调用算子库提供的核函数使能TensorCore
@@ -22,16 +24,17 @@ $D[i][j] \gets C[i][j]$ 
```
### 硬件加速器的多样化编程方法
:label:`diversified-programming-title`
#### 算子库使能加速器
在上述三种层级的编程方式中直接调用算子加速库使能加速器无疑是最快捷高效的方式。NVIDIA提供了cuBLAS/cuDNN两类算子计算库cuBLAS提供了使能Tensor Core单元的接口用以加速矩阵乘法(GEMM)运算cuDNN提供了对应接口加速卷积(CONV)运算等。
[1.3.1](#硬件加速器的可编程性){reference-type="ref" reference="硬件加速器的可编程性"}小节的GEMM运算为例与常规CUDA调用cuBLAS算子库相似通过cuBLAS加速库使能Tensor Core步骤包括
:numref:`accelerator-programable-title`小节的GEMM运算为例与常规CUDA调用cuBLAS算子库相似通过cuBLAS加速库使能Tensor Core步骤包括
1. 创建cuBLAS对象句柄且设置对应数学计算模式
```
```cpp
cublasHandle_t handle;
cublasStatus_t cublasStat = cublasCreate(&handle);
cublasStat = cublasSetMathMode(handle, CUBLAS_TENSOR_OP_MATH);
@@ -39,7 +42,7 @@ cublasStat = cublasSetMathMode(handle, CUBLAS_TENSOR_OP_MATH);
2. 分配和初始化矩阵内存空间及内容元素
```
```cpp
size_t matrixSizeA = (size_t)M * K;
cublasStat = cudaMalloc(&devPtrA[0], matrixSizeA * sizeof(devPtrA[0][0]));
cublasStat = cublasSetMatrix(M, K, sizeof(A[0]), A, M, devPtrA[i], M);
@@ -47,7 +50,7 @@ cublasStat = cublasSetMatrix(M, K, sizeof(A[0]), A, M, devPtrA[i], M);
3. 调用对应计算函数接口
```
```cpp
cublasStat = cublasGemmEx(handle, transa, transb, m, n, k, alpha,
A, CUDA_R_16F, lda,
B, CUDA_R_16F, ldb,
@@ -56,28 +59,28 @@ cublasStat = cublasGemmEx(handle, transa, transb, m, n, k, alpha,
4. 传回结果数据
```
```cpp
cublasStat = cublasGetMatrix(M, N, sizeof(D[0]), devPtrD[i], M, D, M);
```
5. 释放内存和对象句柄
```
```cpp
cudaFree(devPtrA);
cudaDestroy(handle);
```
当然由于加速器一般会有矩阵形状、数据类型、排布方式等限制因此在调用句柄和函数接口时要多加注意。如本例中cuBLAS计算模式必须设置为$CUBLAS\_TENSOR\_OP\_MATH$步长必须设置为8的倍数输入数据类型必须为$CUDA\_R\_16F$等。按照如上方式即可通过cuBLAS算子库对[1.3.1](#硬件加速器的可编程性){reference-type="ref" reference="硬件加速器的可编程性"}实例使能Tensor Core加速器通过NVIDIA官方数据可知该方式对于不同矩阵乘法计算规模平均有410倍的提升且矩阵规模越大加速器提升效果越明显。
当然由于加速器一般会有矩阵形状、数据类型、排布方式等限制因此在调用句柄和函数接口时要多加注意。如本例中cuBLAS计算模式必须设置为$CUBLAS\_TENSOR\_OP\_MATH$步长必须设置为8的倍数输入数据类型必须为$CUDA\_R\_16F$等。按照如上方式即可通过cuBLAS算子库对 :numref:`accelerator-programable-title`实例使能Tensor Core加速器通过NVIDIA官方数据可知该方式对于不同矩阵乘法计算规模平均有410倍的提升且矩阵规模越大加速器提升效果越明显。
该方式由于能够隐藏体系结构细节,易用性较好,且一般官方提供的算子库吞吐量较高。但与此同时,这种算子颗粒度的库也存在一些问题,如不足以应对复杂多变的网络模型导致的算子长尾问题(虽然常规形式算子占据绝大多数样本,但仍有源源不断的新增算子,因其出现机会较少,算子库未对其进行有效优化。),以及错失了较多神经网络框架优化(如算子融合)的机会。
#### 编程原语使能加速器
第二种加速器编程方式为编程原语使能加速器如通过在Device端调用CUDA WMMA (Warp Matrix Multiply Accumulate) API接口。以线程束即Warp是调度的基本单位为操纵对象使能多个Tensor Core单元。该方式在CUDA 9.0中被公开程序员可通过添加API头文件的引用和命名空间定义来使用上述API接口。基于软硬件协同设计的基本思想该层级编程API的设计多与架构绑定如WMMA操纵的总是$16x16$大小的矩阵块并且操作一次跨两个TensorCore进行处理本质是与TensorCore如何集成进SM中强相关的。针对Float16输入数据类型NVIDIA官方提供了三种不同矩阵规模的WMMA乘累加计算接口分别为$16x16x16$$32x8x16$$8x32x16$。
第二种加速器编程方式为编程原语使能加速器如通过在Device端调用CUDA WMMA (Warp Matrix Multiply Accumulate) API接口。以线程束即Warp是调度的基本单位为操纵对象使能多个Tensor Core单元。该方式在CUDA 9.0中被公开程序员可通过添加API头文件的引用和命名空间定义来使用上述API接口。基于软硬件协同设计的基本思想该层级编程API的设计多与架构绑定如WMMA操纵的总是$16\times16$大小的矩阵块并且操作一次跨两个TensorCore进行处理本质是与TensorCore如何集成进SM中强相关的。针对Float16输入数据类型NVIDIA官方提供了三种不同矩阵规模的WMMA乘累加计算接口分别为$16\times16\times16$$32\times8\times16$$8\times32\times16$。
该API接口操纵的基本单位为Fragment是一种指明了矩阵含义乘法器/累加器)、矩阵形状($WMMA\_M, WMMA\_N, WMMA\_K$、数据类型Half/ Float、排布方式$row\_major/ col\_major$)等信息的模板类型,包括如下:
```
```cpp
wmma::fragment<wmma::matrix_a, WMMA_M, WMMA_N, WMMA_K, half, wmma::row_major> a_frag;
wmma::fragment<wmma::matrix_b, WMMA_M, WMMA_N, WMMA_K, half, wmma::col_major> b_frag;
wmma::fragment<wmma::accumulator, WMMA_M, WMMA_N, WMMA_K, float> acc_frag;
@@ -88,7 +91,7 @@ wmma::fragment<wmma::accumulator, WMMA_M, WMMA_N, WMMA_K, float> c_frag;
#### 指令集编程使能加速器
在NVIDIA PTX ISA (Instruction Set Architecture)中提供了另一个编程接口如Volta架构中的$mma.sync.m8n8k4$指令,它使用$M=8, N=8, K=4$的形状配置执行乘累加操作。具体地它由线程组黑色椭圆表示或octet执行[@2018Modeling],如 :numref:`PTX`显示了线程和数据的映射关系。每个线程组由四个连续的线程组成使用不同颜色的圆圈表示。图中还指出了一个octet里面的线程在线程束内的分布Float16乘法器A或B的四个连续元素使用具有相同颜色的块表示以及Float32累加器C或D的八个分散元素同样使用相同颜色的块表示。彩色块上的数字代表对应的线程ID。
在NVIDIA PTX ISA (Instruction Set Architecture)中提供了另一个编程接口如Volta架构中的$mma.sync.m8n8k4$指令,它使用$M=8, N=8, K=4$的形状配置执行乘累加操作。具体地它由线程组黑色椭圆表示或octet执行如 :numref:`PTX`显示了线程和数据的映射关系。每个线程组由四个连续的线程组成使用不同颜色的圆圈表示。图中还指出了一个octet里面的线程在线程束内的分布Float16乘法器A或B的四个连续元素使用具有相同颜色的块表示以及Float32累加器C或D的八个分散元素同样使用相同颜色的块表示。彩色块上的数字代表对应的线程ID。
![mma指令之线程与矩阵元素映射关系](../img/ch06/ptx.svg)
:width:`800px`
@@ -118,7 +121,7 @@ res = te.lang.cce.matmul(tensor_a, tensor_b, False, False, False, dst_dtype=dst_
### 硬件加速器高性能编程实例
本节[1.3](#加速器基本编程原理){reference-type="ref" reference="加速器基本编程原理"}前几个小节主要介绍了硬件加速器的不同层级的多样化编程方法。调用计算库的方式留给程序员的优化空间较少,合理利用硬件加速器不同层级的编程,可以实现更好的性能优化。 为了更好的让读者理解硬件加速器的使用,本节会继续[1.3.1](#硬件加速器的可编程性){reference-type="ref" reference="硬件加速器的可编程性"}节中的GEMM运算仍以WMMA API使能Tensor Core加速单元为例介绍如何通过矩阵分块、资源映射等方式更高效的利用硬件加速器。
本节 :numref:`accelerator-program-title`前几个小节主要介绍了硬件加速器的不同层级的多样化编程方法。调用计算库的方式留给程序员的优化空间较少,合理利用硬件加速器不同层级的编程,可以实现更好的性能优化。 为了更好的让读者理解硬件加速器的使用,本节会继续 :numref:`accelerator-programable-title`节中的GEMM运算仍以WMMA API使能Tensor Core加速单元为例介绍如何通过矩阵分块、资源映射等方式更高效的利用硬件加速器。
[\[alg:TensorCore\]]{#alg:TensorCore label="alg:TensorCore"}
@@ -136,7 +139,7 @@ $Syncthreads()$  $wmma.store\_matrix\_sync(D, D_{Fragment})$ 
若要得到高性能CUDA程序提高并行性、增大吞吐量、优化指令执行是至关重要的三个优化目标。针对该实例具体地实现和优化方案列出如下对应到具体实例伪代码如算法2所示
1. **优化内存结构------增大吞吐量**:将原始大规模矩阵根据不同阈值切分成不同层级的子矩阵块,使得子矩阵块能被如共享内存、寄存器等高性能体系结构存储下来,以此提高吞吐量。设置切分参数为$BlockTile[Ms, Ns, Ks]$和$WarpTile[Mw, Nw, Kw]$对应的将BlockTile下的矩阵由全局内存搬移至共享内存以提高全局内存合并访问和数据局部性 :numref:`GEMM-BlockTile`所示再将内层WarpTile下的矩阵由共享内存搬移至寄存器中 :numref:`GEMM-WarpTile`所示以备Tensor Core加速器数据存取。
1. **优化内存结构------增大吞吐量**:将原始大规模矩阵根据不同阈值切分成不同层级的子矩阵块,使得子矩阵块能被如共享内存、寄存器等高性能体系结构存储下来,以此提高吞吐量。设置切分参数为$BlockTile[Ms, Ns, Ks]$和$WarpTile[Mw, Nw, Kw]$对应的将BlockTile下的矩阵由全局内存搬移至共享内存以提高全局内存合并访问和数据局部性如 :numref:`GEMM-BlockTile`所示再将内层WarpTile下的矩阵由共享内存搬移至寄存器中如 :numref:`GEMM-WarpTile`所示以备Tensor Core加速器数据存取。
![全局内存与共享内存数据交互](../img/ch06/G2S.svg)
:width:`800px`
@@ -148,7 +151,7 @@ $Syncthreads()$  $wmma.store\_matrix\_sync(D, D_{Fragment})$ 
2. **并行资源映射------提高并行性**将多层级的并行资源Block、Warp、Thread与对应需要计算/搬移的数据建立映射关系,提高程序并行性。将可并行的计算/数据搬移操作映射到并行资源上对于GEMM实例M/N轴即为可并行轴将数据搬移操作中的循环指令映射分配到Block层级即算法3中的2-4行$For$循环将内层循环指令映射分配到Warp层级即算法3中的8-10行$For$循环前文介绍线程束Warp作为调度的基本单位且是WMMA API操纵的基本层级因此对Warp层级进行数据映射比Thread层级映射更为合适
3. **Warp统一的Tensor Core数据交互------增大吞吐量**:根据[1.3.2](#硬件加速器的多样化编程方法){reference-type="ref" reference="硬件加速器的多样化编程方法"}节中介绍的编程方法除调用算子库外均需要使用或将指令封装成WMMA接口形式统一进行Warp层级的数据存取和计算。如 :numref:`GEMM-TensorCore`所示Tensor Core加速器需要从局部内存/寄存器中读取数据存于虚拟Fragment数据结构中对应使用$wmma.load\_matrix\_sync()$接口将累加Fragment $C$ 通过$wmma.fill\_fragment()$接口进行初始化后,使用$wmma.mma\_sync()$使能加速器进行乘累加运算后将结果Fragment $D$通过调用$wmma.store\_matrix\_sync()$接口拷贝至目标内存地址。
3. **Warp统一的Tensor Core数据交互------增大吞吐量**:根据 :numref:`diversified-programming-title`节中介绍的编程方法除调用算子库外均需要使用或将指令封装成WMMA接口形式统一进行Warp层级的数据存取和计算。如 :numref:`GEMM-TensorCore`所示Tensor Core加速器需要从局部内存/寄存器中读取数据存于虚拟Fragment数据结构中对应使用$wmma.load\_matrix\_sync()$接口将累加Fragment $C$ 通过$wmma.fill\_fragment()$接口进行初始化后,使用$wmma.mma\_sync()$使能加速器进行乘累加运算后将结果Fragment $D$通过调用$wmma.store\_matrix\_sync()$接口拷贝至目标内存地址。
![寄存器与硬件加速器交互](../img/ch06/R2TC.svg)
:width:`800px`

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 277 KiB

After

Width:  |  Height:  |  Size: 277 KiB