diff --git a/00Others/Install.md b/00Others/Install.md index 968bb859..8be0b9b6 100644 --- a/00Others/Install.md +++ b/00Others/Install.md @@ -2,7 +2,7 @@ ## Sphinx 环境安装 -AI 系统项目部署在 Github 是依赖于 sphinx 工具实现的。因此我们首先要安装 sphinx。在MacOS中,可以使用 Homebrew 、 MacPorts 或者 Anaconda 之类的Python发行版安装Sphinx。 +AI 系统项目部署在 Github 是依赖于 sphinx 工具实现的。因此我们首先要安装 sphinx。在 MacOS 中,可以使用 Homebrew 、 MacPorts 或者 Anaconda 之类的 Python 发行版安装 Sphinx。 ```bash brew install sphinx-doc @@ -76,7 +76,7 @@ import os from urllib.request import urlopen from pathlib import Path -project = "AISystem & AIInfra (AI系统原理)" +project = "AISystem & AIInfra (AI 系统原理)" language = "cn" # For testing language translations master_doc = "index" diff --git a/00Others/inference.md b/00Others/inference.md index 22b62b2c..de446c40 100644 --- a/00Others/inference.md +++ b/00Others/inference.md @@ -39,7 +39,7 @@ 15. [Martín Abadi, Paul Barham, Jianmin Chen, Zhifeng Chen, Andy Davis, Jeffrey Dean, Matthieu Devin, Sanjay Ghemawat, Geoffrey Irving, Michael Isard, Manjunath Kudlur, Josh Levenberg, Rajat Monga, Sherry Moore, Derek G. Murray, Benoit Steiner, Paul Tucker, Vijay Vasudevan, Pete Warden, Martin Wicke, Yuan Yu, and Xiaoqiang Zheng. 2016. TensorFlow: a system for large-scale machine learning. In Proceedings of the 12th USENIX conference on Operating Systems Design and Implementation (OSDI'16). USENIX Association, USA, 265–283.](https://dl.acm.org/doi/10.5555/3026877.3026899) -## 二. AI 硬件体系结构 +## 二. AI 硬件体系结构 1. https://www.knime.com/blog/a-friendly-introduction-to-deep-neural-networks 2. https://machine-learning.paperspace.com/wiki/activation-function diff --git a/01Introduction/02Develop.md b/01Introduction/02Develop.md index 5e90e0a0..4e0714fe 100644 --- a/01Introduction/02Develop.md +++ b/01Introduction/02Develop.md @@ -146,7 +146,7 @@ AI 起源于上世纪五十年代,经历了几次繁荣与低谷,直到 2016 ![](images/02Develop10.png) -为了更高的性能,近年来 AI 芯片也大放光彩。其中一个代表就是谷歌 TPU(Tensor Processing Unit),通过对神经网络模型中的算子进行抽象,转换为矩阵乘法或非线性变换,根据专用负载特点进一步定制流水线化执行的脉动阵列(Systolic Array),进一步减少访存提升计算密度,提高了 AI 模型的执行性能。华为昇腾 NPU(神经网络处理器)针对矩阵运算专门优化设计,可解决传统芯片在神经网络运算时效率低下的问题。此外,华为达芬奇架构面向 AI 计算设计,通过独创 3D Cube 设计,每时钟周期可进行 4096 次 MAC 运算,为 AI 提供强大算力支持。 +为了更高的性能,近年来 AI 芯片也大放光彩。其中一个代表就是谷歌 TPU(Tensor Processing Unit),通过对神经网络模型中的算子进行抽象,转换为矩阵乘法或非线性变换,根据专用负载特点进一步定制流水线化执行的脉动阵列(Systolic Array),进一步减少访存提升计算密度,提高了 AI 模型的执行性能。华为昇腾 NPU(神经网络处理器)针对矩阵运算专门优化设计,可解决传统芯片在神经网络运算时效率低下的问题。此外,华为达芬奇架构面向 AI 计算设计,通过独创 3D Cube 设计,每时钟周期可进行 4096 次 MAC 运算,为 AI 提供强大算力支持。 除了算子层面驱动的定制,AI 层面的计算负载本身在算法层常常应用的稀疏性和量化等加速手段也逐渐被硬件厂商,根据通用算子定制到专用加速器中(例如,英伟达推出的 Transformer Engine),在专用计算领域进一步协同优化加速。通过定制化硬件,厂商又将处理器性能提升了大约 $10^5$ 量级。 diff --git a/01Introduction/03architecture.md b/01Introduction/03architecture.md index 05ee762b..e9873893 100644 --- a/01Introduction/03architecture.md +++ b/01Introduction/03architecture.md @@ -120,7 +120,7 @@ AI 框架不仅仅是指如 PyTorch 等训练框架,还包括推理框架。 - **优化器**:运行时即时(Just-in-Time)优化,内省(Introspective)优化等。运行时根据硬件,隐藏的软件栈信息,数据分布等只能运行时所获取的信息,进一步对模型进行优化。 -- **调度与执行**:调度优算子并行与调度,执行有单线程和多线程执行等。调度方面根据NPU提供的软件栈和硬件调度策略,以及模型的算子间并行机会,进行类装箱的并行调度。另外再算子执行过程中,如果特定NPU没有做过多的运行时调度与干预,框架可以设计高效的运行时算子内的线程调度策略。 +- **调度与执行**:调度优算子并行与调度,执行有单线程和多线程执行等。调度方面根据 NPU 提供的软件栈和硬件调度策略,以及模型的算子间并行机会,进行类装箱的并行调度。另外再算子执行过程中,如果特定 NPU 没有做过多的运行时调度与干预,框架可以设计高效的运行时算子内的线程调度策略。 - **硬件接口抽象**:GPU、NPU、TPU、CPU、FPGA 和 ASIC 等硬件的接口抽象。统一的硬件接口抽象可以复用编译优化策略,让优化方案与具体底层的 AI 硬件设备和 AI 体系结构适当解耦。 diff --git a/01Introduction/04sample.md b/01Introduction/04sample.md index e7b2b3ff..3178d04b 100644 --- a/01Introduction/04sample.md +++ b/01Introduction/04sample.md @@ -202,7 +202,7 @@ for n in range(batch_size): 在实际 Kernel 的计算过程中有很多有趣的问题: -- **硬件加速**: 通用矩阵乘是计算机视觉和自然语言处理模型中的主要的计算方式,同时 NPU/GPU,如 TPU 脉动阵列的矩阵乘单元等其他专用人工智能芯片 ASIC 是否会针对矩阵乘作为底层支持?(第二章 AI 芯片体系结构相关内容) +- **硬件加速**:通用矩阵乘是计算机视觉和自然语言处理模型中的主要的计算方式,同时 NPU/GPU,如 TPU 脉动阵列的矩阵乘单元等其他专用人工智能芯片 ASIC 是否会针对矩阵乘作为底层支持?(第二章 AI 芯片体系结构相关内容) - **片上内存**:其中参与计算的输入、权重和输出张量能否完全放入 NPU/GPU 缓存(L1、L2、Cache)?如果不能放入则需要通过循环块(Loop Tile)编译优化进行切片。(第二章 AI 芯片体系结构相关内容) @@ -313,8 +313,8 @@ class LeNet(nn.Module): 2. 提供调用基本算子实现,大幅降低开发代码量; 2. 自动化内存管理、不暴露指针和内存管理给用户; 3. 实现自动微分功能,自动构建反向传播计算图; -4. 调用或生成运行时优化代码,调度算子在指定NPU的执行; -6. 并在运行期应用并行算子,提升NPU利用率等优化(动态优化)。 +4. 调用或生成运行时优化代码,调度算子在指定 NPU 的执行; +6. 并在运行期应用并行算子,提升 NPU 利用率等优化(动态优化)。 AI 框架帮助开发者解决了很多 AI System 底层问题,隐藏了很多工程的实现细节,但是这些细节和底层实现又是 AI System 工程师比较关注的点。 diff --git a/01Introduction/05Foundation.md b/01Introduction/05Foundation.md index 16f19623..7e570452 100644 --- a/01Introduction/05Foundation.md +++ b/01Introduction/05Foundation.md @@ -34,7 +34,7 @@ AI 大模型发展历经三个阶段,分别是萌芽期、探索期和爆发 以 CNN 为代表的传统神经网络模型阶段。1956 年,从计算机专家约翰·麦卡锡提出“人工智能”概念开始,AI 发展由最开始基于小规模专家知识逐步发展为基于机器学习。1980 年,卷积神经网络的雏形 CNN 诞生。1998 年,现代卷积神经网络 CNN 的基本结构 LeNet-5 诞生,机器学习方法由早期基于浅层机器学习的模型,变为了基于深度学习的模型。 -在萌芽期阶段,小模型的研究为自然语言生成、计算机视觉等领域的深入研究奠定了基础,对后续深度学习框架的迭代及大模型发展具有开创性的意义。此时在自然语言处理 NLP 的模型研究都是在研究基于给定的数据集,在特定的下游任务,如何设计网络模型结构、调整超参、提升训练技巧可以达到更高的任务分数,因此出现了 Word2vec、RNN、LSTM、GRU 等各种 NLP 模型结构。 +在萌芽期阶段,小模型的研究为自然语言生成、计算机视觉等领域的深入研究奠定了基础,对后续深度学习框架的迭代及大模型发展具有开创性的意义。此时在自然语言处理 NLP 的模型研究都是在研究基于给定的数据集,在特定的下游任务,如何设计网络模型结构、调整超参、提升训练技巧可以达到更高的任务分数,因此出现了 Word2vec、RNN、LSTM、GRU 等各种 NLP 模型结构。 - **探索期(2006-2019)** diff --git a/01Introduction/README.md b/01Introduction/README.md index 1607f499..6ebb203c 100644 --- a/01Introduction/README.md +++ b/01Introduction/README.md @@ -12,7 +12,7 @@ 课程主要包括以下六大模块: -第一部分《AI 系统概述》:AI 基础知识和 AI 系统的全栈概述的AI 系统概述,以及人工智能系统的系统性设计和方法论,主要是整体了解 AI 训练和推理全栈的体系结构内容。 +第一部分《AI 系统概述》:AI 基础知识和 AI 系统的全栈概述的 AI 系统概述,以及人工智能系统的系统性设计和方法论,主要是整体了解 AI 训练和推理全栈的体系结构内容。 第二部分《AI 硬件体系结构》:硬核篇介绍 AI 芯片概况,从芯片基础到 AI 芯片的范围都会涉及,芯片设计需要考虑上层 AI 框架的前端、后端编译、以及针对 AI 算法的实现角度等相关技术综合设计符合 AI 范式的芯片架构。 @@ -20,17 +20,17 @@ 第四部分《AI 推理系统与引擎》:实际应用推理系统与推理引擎,AI 系统领域众多,技术点也非常的多,但本质还是得回归到业务本质,让行业、企业能够真正应用起来,而推理系统涉及一些核心算法是真正在部署与推理端,帮助 AI 业务进行落地。 -第五部分《AI 框架核心模块》:介绍AI 框架核心技术,首先介绍任何一个 AI 框架都离不开的自动微分技术,通过自动微分功能后就会产生表示神经网络的图和算子,然后介绍 AI 框架前端的优化,还有最近大模型分布式训练在 AI 框架中的关键技术。 +第五部分《AI 框架核心模块》:介绍 AI 框架核心技术,首先介绍任何一个 AI 框架都离不开的自动微分技术,通过自动微分功能后就会产生表示神经网络的图和算子,然后介绍 AI 框架前端的优化,还有最近大模型分布式训练在 AI 框架中的关键技术。 ## 课程前言 -我在2019年来到了华为技术有限公司的 2012 实验室,后来加入了计算产品线从事昇腾产业,是国内为数不多的 AI(Artificial Intelligence, 人工智能)基础研究的发源地之一,很多学生和 AI 基础软硬件研究者慕名而来从事 AI 产业相关技术。这里拥有许多出色的 AI 的算法研究(如自然语言处理、计算机视觉、计算神经学等),同时也拥有一系列关于计算机系统的扎实研究(如操作系统、编程语言、编译器、计算机体系架构、AI 芯片设计等)。 +我在 2019 年来到了华为技术有限公司的 2012 实验室,后来加入了计算产品线从事昇腾产业,是国内为数不多的 AI(Artificial Intelligence, 人工智能)基础研究的发源地之一,很多学生和 AI 基础软硬件研究者慕名而来从事 AI 产业相关技术。这里拥有许多出色的 AI 的算法研究(如自然语言处理、计算机视觉、计算神经学等),同时也拥有一系列关于计算机系统的扎实研究(如操作系统、编程语言、编译器、计算机体系架构、AI 芯片设计等)。 当我在公司内部主要负责 AI 训练框架 MindSpore 和推理引擎 MindSpore Lite 的开发,深刻地了解到了:人工智能算法是如何利用计算机结构体系实现计算加速和部署。反观国内外鲜有相关的材料和课程介绍,大部分都是聚焦在如何开发一个 AI 算法,有更加深入的内容则是解读不同 AI 框架里面的某个注册机制,例如如何注册自定义算子等。再一次对外的交流过程中,我发现很多学生和从业者对人工智能算法,如何利用计算机结构体系实现计算加速和部署尤为兴趣,而且会投来疑惑的眼神。这促使我思考能否在中国这一片 AI 应用落地的摇篮之地,乃至于在国内最顶尖的学校(清华大学、浙江大学、哈尔滨工业大学等知名学府)教学大纲中,是不是缺一门衔接人工智能和计算机结构体系的课程。 我的第一反应是基于目前在华为昇腾从事的 AI 相关的工作来进行拓展。那时,加州大学伯克利分校的《AI Systems(人工智能系统)》课程较为知名,这门课描述了机器学习系统的不同研究方向,内容以研读论文为主;可惜的是,许多论文已经无法经受住时间的检验;微软开放的《AI Systems》课程因为课缺乏对于知识的整体梳理,未能形成完整的 AI 系统知识体系架构;学习完这门课程,学生未能对于从头搭建机器学习系统有明确的思路。华盛顿大学曾短期开过《Deep Learning Systems(深度学习系统)》课程,这门课程讲述了机器学习程序的编译过程。而由于这门课程以讲述 Apache TVM 深度学习编译器为主要目的,对于机器学习系统缺乏完整的教学;另外,斯坦福大学的课程《Machine Learning Systems Design(机器学习系统设计)》因为课程设计人的研究领域以数据库为主,因此该课程专注于数据清洗、数据管理、数据标注等主题;MindSpore 的首席架构师金雪峰老师合作与英国爱丁堡大学的麦络老师开发的《机器学习系统:设计和实现》,由于其内容更加专注于 AI 框架的实现,从而引入了很多跟 AI 系统不相关的强化学习、联邦学习等最新的 AI 算法介绍,但是缺乏从计算机结构体系的整个软硬件系统层视角来系统性论述人工智能系统所涉及的基础软硬件内容。 -回首2022年年底,我们已经拥有了优秀的操作系统、数据库、分布式系统等基础性教材。同时,在人工智能相关算法方面也有了一系列教材。然而,无论是国内外,很难找到一本系统性讲述人工智能系统的教材。随着国内的 AI 芯片的公司和大模型基础设施的公司如如后春笋般涌现,但是另外一方面,许多公司和高校实验室不得不花费大量的人力和物力从头培养学生和工程师,使他们加强对于人工智能底层基础设施的认识。这类教材的缺乏已经制约了高校的人才培养,制约了企业人才的培养,不利于高校培养出符合业界学界和时代发展的人才了。因此,开始思考:我们是不是应该推出一本人工智能系统的教科书了呢? +回首 2022 年年底,我们已经拥有了优秀的操作系统、数据库、分布式系统等基础性教材。同时,在人工智能相关算法方面也有了一系列教材。然而,无论是国内外,很难找到一本系统性讲述人工智能系统的教材。随着国内的 AI 芯片的公司和大模型基础设施的公司如如后春笋般涌现,但是另外一方面,许多公司和高校实验室不得不花费大量的人力和物力从头培养学生和工程师,使他们加强对于人工智能底层基础设施的认识。这类教材的缺乏已经制约了高校的人才培养,制约了企业人才的培养,不利于高校培养出符合业界学界和时代发展的人才了。因此,开始思考:我们是不是应该推出一本人工智能系统的教科书了呢? ### 课程开始 @@ -38,37 +38,37 @@ 而我和同行交流时也发现:他们更愿意改进市面上已经有的教科书,即做有迹可循的事情,而不是摸着石头过河,做从无到有的事情。特别是对于人工智能系统这个快速发展,频繁试错的领域,能不能写出经受时间检验的书也是一个未知数。 -传统的图书写作,往往依赖于一、两个教授将学科十余年的发展慢慢总结整理成书。这种模式类似于传统软件开发的瀑布流方式。可是,在科技的世界,软件的发展从传统的瀑布流进化到如今的开源敏捷开发。而图书的写作为什么还要停留在传统方式呢?于是我开始趁着在苏州给C9高校为期三天的人工智能 AI 框架的核心原理的培训这一契机,制作了一系列围绕 AI 框架核心模块 -- 自动微分的原理实现,从胶片到实操代码。并收到了来自浙江大学、上海交通大学、华东师范大学、南京大学等学生的高度好评,于是相隔了2个月之后,在22年的10月份开始尝试制作第一个AI框架原理的视频,并在全网上线,从书籍的撰写到围绕书籍内容进行视频的制作。一个人,没有找到伙伴,凭着一腔热爱技术分享,说干就干! +传统的图书写作,往往依赖于一、两个教授将学科十余年的发展慢慢总结整理成书。这种模式类似于传统软件开发的瀑布流方式。可是,在科技的世界,软件的发展从传统的瀑布流进化到如今的开源敏捷开发。而图书的写作为什么还要停留在传统方式呢?于是我开始趁着在苏州给 C9 高校为期三天的人工智能 AI 框架的核心原理的培训这一契机,制作了一系列围绕 AI 框架核心模块 -- 自动微分的原理实现,从胶片到实操代码。并收到了来自浙江大学、上海交通大学、华东师范大学、南京大学等学生的高度好评,于是相隔了 2 个月之后,在 22 年的 10 月份开始尝试制作第一个 AI 框架原理的视频,并在全网上线,从书籍的撰写到围绕书籍内容进行视频的制作。一个人,没有找到伙伴,凭着一腔热爱技术分享,说干就干! -上线了多期视频后,经过了几个月的视频分享,内容一期又一期,深度来越深,刚开始的时候,因为没有流量,关注的人只有少数的几百人。随着《AI 系统》最初的设计,分享从AI框架慢慢扩展到推理引擎、AI编译器等,我们将项目或者课程名定为《AI 系统》。希望通过分享人工智能系统设计原理,同时也为学生和 AI 行业从业者提供大量人工智能系统实现的实际项目经验分享,让他们在将来工作和科研中遇到实际问题知道该如何分析和解决。 +上线了多期视频后,经过了几个月的视频分享,内容一期又一期,深度来越深,刚开始的时候,因为没有流量,关注的人只有少数的几百人。随着《AI 系统》最初的设计,分享从 AI 框架慢慢扩展到推理引擎、AI 编译器等,我们将项目或者课程名定为《AI 系统》。希望通过分享人工智能系统设计原理,同时也为学生和 AI 行业从业者提供大量人工智能系统实现的实际项目经验分享,让他们在将来工作和科研中遇到实际问题知道该如何分析和解决。 ### 社区构建 -其实从22年10月份开始围绕《AI 系统》的第一个视频开始,并没有在社区引起多大的关注量,做的视频很用心行也没有几个人观看,不过当时候开发《AI 系统》课程的目标很强,因为已经梳理出来一个非常明确的大纲(跟最后的目录几乎接近一致),于是为了自己做技术积累也好,为了让更多的人关注到AI系统这个领域也好,为了计算产业培养更多优秀的人才也好。于是咬咬牙继续坚持了4个月,完成了围绕《AI框架核心模块》的30多个视频。 +其实从 22 年 10 月份开始围绕《AI 系统》的第一个视频开始,并没有在社区引起多大的关注量,做的视频很用心行也没有几个人观看,不过当时候开发《AI 系统》课程的目标很强,因为已经梳理出来一个非常明确的大纲(跟最后的目录几乎接近一致),于是为了自己做技术积累也好,为了让更多的人关注到 AI 系统这个领域也好,为了计算产业培养更多优秀的人才也好。于是咬咬牙继续坚持了 4 个月,完成了围绕《AI 框架核心模块》的 30 多个视频。 -此时已经到了23年1月份,在23年2月份开始录制AI框架的底层编译器,从传统编译器开始,刚好到了23年2到3月份的时候,国内很多本科和硕士开展编译器的课程,于是我的视频变成了学生们课外的视频读物,粉丝量开始上千,我也收到很多小伙伴的新的反馈,更加坚定自己做视频的动力。 +此时已经到了 23 年 1 月份,在 23 年 2 月份开始录制 AI 框架的底层编译器,从传统编译器开始,刚好到了 23 年 2 到 3 月份的时候,国内很多本科和硕士开展编译器的课程,于是我的视频变成了学生们课外的视频读物,粉丝量开始上千,我也收到很多小伙伴的新的反馈,更加坚定自己做视频的动力。 -在不断坚持的过程中,也非常感谢身边和家人的无私支持,因为在工作之余持续做技术分享,受限于大部分知识在网络上其知识点非常的少、非常的偏门以外,需要对知识点进行高度的提炼和总结,把自己知道和不知道的深入领域去挖掘、去学习、去深入、去洞察、去梳理、去总结。除此知识的梳理占了大部分时间意外,还要录课,剪视频。每晚到了凌晨2/3点,坚持了1年半左右。 +在不断坚持的过程中,也非常感谢身边和家人的无私支持,因为在工作之余持续做技术分享,受限于大部分知识在网络上其知识点非常的少、非常的偏门以外,需要对知识点进行高度的提炼和总结,把自己知道和不知道的深入领域去挖掘、去学习、去深入、去洞察、去梳理、去总结。除此知识的梳理占了大部分时间意外,还要录课,剪视频。每晚到了凌晨 2/3 点,坚持了 1 年半左右。 -收到分享的视频和PPT在美团、字节、蚂蚁、百度、小红书等公司和清华、北大、中科院、中科大、国防科大等高校的邀请分享以外,他们表示会给到小组内做技术分享,因此决定开源整个视频和内容的制作过程,包括视频里面的每一张图片都尽可能自己进行重绘,让读者更加容易理解和学习。 +收到分享的视频和 PPT 在美团、字节、蚂蚁、百度、小红书等公司和清华、北大、中科院、中科大、国防科大等高校的邀请分享以外,他们表示会给到小组内做技术分享,因此决定开源整个视频和内容的制作过程,包括视频里面的每一张图片都尽可能自己进行重绘,让读者更加容易理解和学习。 ### 众智之力 -充分发动众筹、众智的力量后,项目的内容得以持续高质量地添加。自从开源了项目以后,图书的受众快速增长,GitHub上关注度的增长让我们受宠若惊。在社区的推动下,参与进来进行内容审核、视频字幕补充、内容细化的小伙伴越来越多,图书的每一个细节内容快速地补充,所有细节和更加深刻的知识点都已经开始全面推进。这么多年来,我第一次意识到我在人工智能系统中学习到的和积累到的知识,在解决现实复杂问题的时候是如此的有用,能够帮助到这么多人! +充分发动众筹、众智的力量后,项目的内容得以持续高质量地添加。自从开源了项目以后,图书的受众快速增长,GitHub 上关注度的增长让我们受宠若惊。在社区的推动下,参与进来进行内容审核、视频字幕补充、内容细化的小伙伴越来越多,图书的每一个细节内容快速地补充,所有细节和更加深刻的知识点都已经开始全面推进。这么多年来,我第一次意识到我在人工智能系统中学习到的和积累到的知识,在解决现实复杂问题的时候是如此的有用,能够帮助到这么多人! 很多时候,当我们面对未知而巨大的困难时,个人的力量真的很渺小,即使再渺小的力量下,坚持下去一定会有所收获的。而和朋友、社区一起就变成了强大的力量,让我们鼓起勇气,走出了最关键的一步!希望我们在开源社区的《AI 系统》的内容,能给整个计算产业带来更多的思考和产业落地的借鉴思路。 -截止到书稿快接近尾声,本课程已经汇聚超过50个小伙伴的贡献,特别在这里要感谢华为郝志宏、李兆雅同事的牵线,让项目的内容作为浙江大学吴飞教授领衔负责的教育部关键领域硕博核心课程《人工智能系统架构》配套教材,在哈尔滨工业大学软件学院副院长苏统华教授的支持下,已经在哈尔滨工业大学2024年春季开课试行,大受欢迎。 +截止到书稿快接近尾声,本课程已经汇聚超过 50 个小伙伴的贡献,特别在这里要感谢华为郝志宏、李兆雅同事的牵线,让项目的内容作为浙江大学吴飞教授领衔负责的教育部关键领域硕博核心课程《人工智能系统架构》配套教材,在哈尔滨工业大学软件学院副院长苏统华教授的支持下,已经在哈尔滨工业大学 2024 年春季开课试行,大受欢迎。 -除此以外,需要特别感谢负责AI 硬件体系结构的刘军、张晓天、李明慧、张圣航、刘纬恒、李宗泽、赵文千;感谢负责 AI 编译原理的史家兴、宋一帆、韩昊知、李行、武震卿、张浩、陈庚天;负责 AI 推理引擎的高鑫淼、何伟、刘旭斌、杨璇、徐宏博、栾少、谢迎、刘欣楠、李行、江典恒,感谢负责 AI 框架核心的金傲群。 +除此以外,需要特别感谢负责 AI 硬件体系结构的刘军、张晓天、李明慧、张圣航、刘纬恒、李宗泽、赵文千;感谢负责 AI 编译原理的史家兴、宋一帆、韩昊知、李行、武震卿、张浩、陈庚天;负责 AI 推理引擎的高鑫淼、何伟、刘旭斌、杨璇、徐宏博、栾少、谢迎、刘欣楠、李行、江典恒,感谢负责 AI 框架核心的金傲群。 最后,还要感谢参与字幕和内容校对的张泽斌、魏铭康、王远航、郝嘉伟、陈志宇、刘旭斌、赵含霖、乔凯、孙仲琦、李敏涛、管一铭、谢鑫鑫、邓实诚、杨绎等同学。 -最后,我们非常欢迎新成员的加入以提升书籍质量,扩展内容。感兴趣的读者可以通过书籍的 AI 系统社区联系我们。我们非常期待和大家一起努力,编写出一本推动业界发展的AI系统图书! +最后,我们非常欢迎新成员的加入以提升书籍质量,扩展内容。感兴趣的读者可以通过书籍的 AI 系统社区联系我们。我们非常期待和大家一起努力,编写出一本推动业界发展的 AI 系统图书! ZOMI 酱 -2024年6月20日 +2024 年 6 月 20 日 ## 备注 diff --git a/02Hardware/01Foundation/01Introduction.md b/02Hardware/01Foundation/01Introduction.md index 95ca44d3..4cd5cd65 100644 --- a/02Hardware/01Foundation/01Introduction.md +++ b/02Hardware/01Foundation/01Introduction.md @@ -28,7 +28,7 @@ AI 芯片的广泛定义是指那些面向人工智能应用的芯片。按照 CPU、GPU、FPGA、ASIC 是目前 AI 计算过程中最主流的四种芯片类型,CPU、GPU、FPGA 是前期较为成熟的芯片架构,属于通用性芯片,ASIC 是为 AI 特定场景定制的芯片。他们的主要区别体现在计算效率、能耗和灵活性上面,对 AI 算法具有不同的支持程度。 -- **CPU**:CPU 是冯诺依曼架构下的处理器,遵循“Fetch (取指) -Decode (译码) - Execute (执行) - Memory Access (访存) -Write Back (写回)”的处理流程。作为计算机的核心硬件单元,CPU 具有大量缓存和复杂的逻辑控制单元,非常擅长逻辑控制、串行的运算,不擅长复杂算法运算和处理并行重复的操作。CPU 能够支持所有的 AI 模型算法。 +- **CPU**:CPU 是冯诺依曼架构下的处理器,遵循“Fetch (取指)-Decode (译码)- Execute (执行)- Memory Access (访存)-Write Back (写回)”的处理流程。作为计算机的核心硬件单元,CPU 具有大量缓存和复杂的逻辑控制单元,非常擅长逻辑控制、串行的运算,不擅长复杂算法运算和处理并行重复的操作。CPU 能够支持所有的 AI 模型算法。 - **GPU**:图形处理器,最早应用于图像处理领域,与 CPU 相比,减少了大量数据预取和决策模块,增加了计算单元 ALU 的占比,从而在并行化计算效率上有较大优势。但 GPU 无法单独工作,必须由 CPU 进行控制调用才能工作,而且功耗比较高。随着技术的进步,以及越来越多 AI 算法通过 GPU 用来加速训练,GPU 的功能越来越强大,人们开始将 GPU 的能力扩展到一些计算密集的领域,这种扩展后设计的处理器称为 GPGPU(General-Purpose compution on Graphics Processing Unit) diff --git a/02Hardware/01Foundation/02ArchSlim.md b/02Hardware/01Foundation/02ArchSlim.md index f96fe62a..10371186 100644 --- a/02Hardware/01Foundation/02ArchSlim.md +++ b/02Hardware/01Foundation/02ArchSlim.md @@ -199,13 +199,13 @@ class MultiHeadSelfAttention(nn.Module): 1. **降低内存**:低比特量化将模型的权重和激活值转换为较低位宽度的整数或定点数,从而大幅减少了模型的存储需求,使得模型可以更轻松地部署在资源受限的设备上。 -2. **降低成本**: 低比特量化降低了神经网络中乘法和加法操作的精度要求,从而减少了计算量,加速了推理过程,提高了模型的效率和速度。 +2. **降低成本**:低比特量化降低了神经网络中乘法和加法操作的精度要求,从而减少了计算量,加速了推理过程,提高了模型的效率和速度。 -3. **降低能耗**: 低比特量化减少了模型的计算需求,因此可以降低模型在移动设备和嵌入式系统上的能耗,延长设备的电池寿命。 +3. **降低能耗**:低比特量化减少了模型的计算需求,因此可以降低模型在移动设备和嵌入式系统上的能耗,延长设备的电池寿命。 -4. **提升速度**: 虽然低比特量化会引入一定程度的信息丢失,但在合理选择量化参数的情况下,可以最大程度地保持模型的性能和准确度,同时获得存储和计算上的优势。 +4. **提升速度**:虽然低比特量化会引入一定程度的信息丢失,但在合理选择量化参数的情况下,可以最大程度地保持模型的性能和准确度,同时获得存储和计算上的优势。 -5. **丰富模型的部署场景**: 低比特量化压缩了模型参数,可以使得模型更容易在移动设备、边缘设备和嵌入式系统等资源受限的环境中部署和运行,为各种应用场景提供更大的灵活性和可行性。 +5. **丰富模型的部署场景**:低比特量化压缩了模型参数,可以使得模型更容易在移动设备、边缘设备和嵌入式系统等资源受限的环境中部署和运行,为各种应用场景提供更大的灵活性和可行性。 #### 量化基本概念 @@ -241,7 +241,7 @@ $$ #### 量化相关研究热点 -模型量化本质上就是将神经网络模型中权重和激活值从高比特精度(FP32)转换为低比特精度(如 INT4/INT8 等)的技术,用来加速模型的部署和集成,降低硬件成本,为实际应用带来更多可能性, 所以模型量化依旧是未来 AI 落地应用的一个重要的研究方向。围绕着模型量化的研究热点可以分为如下几个方面: +模型量化本质上就是将神经网络模型中权重和激活值从高比特精度(FP32)转换为低比特精度(如 INT4/INT8 等)的技术,用来加速模型的部署和集成,降低硬件成本,为实际应用带来更多可能性,所以模型量化依旧是未来 AI 落地应用的一个重要的研究方向。围绕着模型量化的研究热点可以分为如下几个方面: 1. **量化方法**:根据要量化的原始数据分布范围,可以将量化方法分为线性量化(如 MinMax)和非线性量化(如 KL 散度,Log-Net 算法)。 diff --git a/02Hardware/01Foundation/03MobileParallel.md b/02Hardware/01Foundation/03MobileParallel.md index 7ba646bc..e89b93aa 100644 --- a/02Hardware/01Foundation/03MobileParallel.md +++ b/02Hardware/01Foundation/03MobileParallel.md @@ -34,7 +34,7 @@ MobileNet 系列的网络设计中,提出了深度可分离卷积的设计策 ![](images/03MobileParallel02.png) -比如一个 $3x3$ 卷积核大小的卷积层,输入通道是 16, 输出通道是 32,正常的卷积模型参数是 $3x3x16x32=4608$,而将其模型替代设计为一个 $3x3$ 卷积核的 Depthwise 卷积,和 $1x1$ 卷积核的 Pointwise 卷积,模型参数为 $3x3x16+1x1x16x32=656$,可以看出模型参数量得到了很大的减少。 +比如一个 $3x3$ 卷积核大小的卷积层,输入通道是 16,输出通道是 32,正常的卷积模型参数是 $3x3x16x32=4608$,而将其模型替代设计为一个 $3x3$ 卷积核的 Depthwise 卷积,和 $1x1$ 卷积核的 Pointwise 卷积,模型参数为 $3x3x16+1x1x16x32=656$,可以看出模型参数量得到了很大的减少。 3. **减少卷积核个数的设计** @@ -79,15 +79,15 @@ MobileNet 系列的网络设计中,提出了深度可分离卷积的设计策 - All-reduce:所有节点上的数据都会被收集起来,然后进行某种操作(通常是求和或求平均),然后将结果广播回每个节点。这个操作在并行计算中常用于全局梯度更新。 -- All-gather: 每个节点上的数据都被广播到其他所有节点上。每个节点最终都会收到来自所有其他节点的数据集合。这个操作在并行计算中用于收集各个节点的局部数据,以进行全局聚合或分析。 +- All-gather:每个节点上的数据都被广播到其他所有节点上。每个节点最终都会收到来自所有其他节点的数据集合。这个操作在并行计算中用于收集各个节点的局部数据,以进行全局聚合或分析。 - Broadcast:一台节点上的数据被广播到其他所有节点上。通常用于将模型参数或其他全局数据分发到所有节点。 -- Reduce: 将所有节点上的数据进行某种操作(如求和、求平均、取最大值等)后,将结果发送回指定节点。这个操作常用于在并行计算中进行局部聚合。 +- Reduce:将所有节点上的数据进行某种操作(如求和、求平均、取最大值等)后,将结果发送回指定节点。这个操作常用于在并行计算中进行局部聚合。 -- Scatter: 从一个节点的数据集合中将数据分发到其他节点上。通常用于将一个较大的数据集合分割成多个部分,然后分发到不同节点上进行并行处理。 +- Scatter:从一个节点的数据集合中将数据分发到其他节点上。通常用于将一个较大的数据集合分割成多个部分,然后分发到不同节点上进行并行处理。 -- Gather: 将各个节点上的数据收集到一个节点上。通常用于将多个节点上的局部数据收集到一个节点上进行汇总或分析。 +- Gather:将各个节点上的数据收集到一个节点上。通常用于将多个节点上的局部数据收集到一个节点上进行汇总或分析。 ### 数据并行技术 diff --git a/02Hardware/01Foundation/04Metrics.md b/02Hardware/01Foundation/04Metrics.md index fcbe06bb..7f22f164 100644 --- a/02Hardware/01Foundation/04Metrics.md +++ b/02Hardware/01Foundation/04Metrics.md @@ -12,13 +12,13 @@ **OPS** -OPS,Operations Per Second, 每秒操作数。 1 TOPS 代表处理器每秒进行一万亿次($10^12$)计算。 +OPS,Operations Per Second, 每秒操作数。1 TOPS 代表处理器每秒进行一万亿次($10^12$)计算。 OPS/W:每瓦特运算性能。TOPS/W 评价处理器在 1W 功耗下运算能力的性能指标。 **MACs** -Multiply-Accumulate Operations,乘加累计操作。 1 MACs 包含一个乘法操作与一个加法操作,通常 $1MACs = 2FLOPs$。 +Multiply-Accumulate Operations,乘加累计操作。1 MACs 包含一个乘法操作与一个加法操作,通常 $1MACs = 2FLOPs$。 **FLOPs** @@ -78,7 +78,7 @@ AI 芯片的能耗取决于多个因素,包括芯片架构、制造工艺、 ### 易用性 Flexibility -一个好的 AI 芯片产品应该提供完善的软硬件支持、丰富的文档和教程、灵活的编程语言和框架支持,以及便捷的硬件接口和集成支持,从而满足开发者在不同应用场景下的需求,提高开发效率和用户体验。 AI 芯片的易用性的具体理解为: +一个好的 AI 芯片产品应该提供完善的软硬件支持、丰富的文档和教程、灵活的编程语言和框架支持,以及便捷的硬件接口和集成支持,从而满足开发者在不同应用场景下的需求,提高开发效率和用户体验。AI 芯片的易用性的具体理解为: - **文档和教程**:良好的文档和教程能够帮助用户更好地了解 AI 芯片的特性、功能和使用方法,降低学习成本,提高开发效率。 @@ -136,7 +136,7 @@ $$ 根据算术强度和操作字节比的概念,我们很容易评估出一个 AI 模型在指定 AI 芯片上的理论性能情况。下面展示一个具体的示例。 -以 V100 GPU 执行 GEMM 为例,V100 的 FP16 峰值计算性能是 125TFLOPS,片外存储带宽约为 900GB/s, 片上 L2 带宽为 3.1TB/s。 +以 V100 GPU 执行 GEMM 为例,V100 的 FP16 峰值计算性能是 125TFLOPS,片外存储带宽约为 900GB/s,片上 L2 带宽为 3.1TB/s。 - 如果输入数据来自片外存储器,操作字节比约为 125/0.9≈138.9 - 如果输入数据来自片上存储器,操作字节比约为 125/3.1≈40 diff --git a/02Hardware/01Foundation/05Matrix.md b/02Hardware/01Foundation/05Matrix.md index 7077398e..6e62f66c 100644 --- a/02Hardware/01Foundation/05Matrix.md +++ b/02Hardware/01Foundation/05Matrix.md @@ -20,7 +20,7 @@ $$ \end{align} $$ -更具体的,假设卷积核的维度(2, 2), 输入特征图维度(3, 3),输入和输出通道都是 1,对一个无 padding,stride=1 的卷积操作,输出特征图是(2, 2),所以输入卷积核转换为矩阵乘排布后的行数是 $2 * 2 = 4$, 列数为 $2 * 2 * 1= 4$。下图是对应的卷积到矩阵乘的转换示意,输入、输出特征图和卷积核都用不同的颜色表示,图中数字表示位置标记。 +更具体的,假设卷积核的维度(2, 2),输入特征图维度(3, 3),输入和输出通道都是 1,对一个无 padding,stride=1 的卷积操作,输出特征图是(2, 2),所以输入卷积核转换为矩阵乘排布后的行数是 $2 * 2 = 4$,列数为 $2 * 2 * 1= 4$。下图是对应的卷积到矩阵乘的转换示意,输入、输出特征图和卷积核都用不同的颜色表示,图中数字表示位置标记。 ![](images/05Matrix02.png) @@ -38,7 +38,7 @@ $$ 上面介绍了卷积到矩阵乘的转换过程,我们可以发现,转换后的矩阵乘的维度非常大,而芯片里的内存空间往往是有限的(成本高),表现为越靠近计算单元,带宽越快,内存越小。为了平衡计算和内存加载的时间,让算力利用率最大化,AI 芯片往往会进行由远到近,多级内存层级的设计方式,达到数据复用和空间换时间的效果。根据这样的设计,矩阵乘实际的数据加载和计算过程将进行分块 Tiling 处理。 -假设用 CHW 表示上面转换公式中的 $KH * KW * IC$ 的值,M 表示 OC, N 表示 $OH * OW $,矩阵乘的输入特征图维度是 (CHW, N),矩阵乘的卷积核维度是(M, CHW),输出矩阵维度是(M, N),可以同时在 M,N,CHW 三个维度进行 Tiling,每次计算过程分别加载一小块的特征图和卷积核数据计算,比如在 M,N,CHW 三个维度各分了 2 小块,得到完成的输出特征图需要进行 8 次的数据加载和计算。下图中的 Step1, Step2 展示了两次数据加载可以完成一个输出 Tile 块的计算过程。 +假设用 CHW 表示上面转换公式中的 $KH * KW * IC$ 的值,M 表示 OC,N 表示 $OH * OW $,矩阵乘的输入特征图维度是 (CHW, N),矩阵乘的卷积核维度是(M, CHW),输出矩阵维度是(M, N),可以同时在 M,N,CHW 三个维度进行 Tiling,每次计算过程分别加载一小块的特征图和卷积核数据计算,比如在 M,N,CHW 三个维度各分了 2 小块,得到完成的输出特征图需要进行 8 次的数据加载和计算。下图中的 Step1, Step2 展示了两次数据加载可以完成一个输出 Tile 块的计算过程。 ![](images/05Matrix03.png) @@ -79,7 +79,7 @@ $$ 这些优化算法通常根据硬件平台、数据规模和计算需求选择不同的策略,以提高矩阵乘法运算的效率。在具体的 AI 芯片或其它专用芯片里面,对矩阵乘的优化实现主要就是减少指令开销,可以表现为两个方面: -1. **让每个指令执行更多的 MACs 计算。**比如 CPU 上的 SIMD/Vector 指令, GPU 上的 SIMT/Tensor 指令,NPU 上 SIMD/Tensor,Vector 指令的设计。 +1. **让每个指令执行更多的 MACs 计算。**比如 CPU 上的 SIMD/Vector 指令,GPU 上的 SIMT/Tensor 指令,NPU 上 SIMD/Tensor,Vector 指令的设计。 2. **在不增加内存带宽的前提下,单时钟周期内执行更多的 MACs。**比如 NVIDIA 的 Tensor Core 中支持低比特计算的设计,对每个 cycle 执行 512bit 数据的带宽前提下,可以执行 64 个 8bit 的 MACs,大于执行 16 个 32bit 的 MACs。 diff --git a/02Hardware/01Foundation/06BitWidth.md b/02Hardware/01Foundation/06BitWidth.md index 3c500449..e453b079 100644 --- a/02Hardware/01Foundation/06BitWidth.md +++ b/02Hardware/01Foundation/06BitWidth.md @@ -54,8 +54,8 @@ IEEE 754 标准中浮点数根据指数的值会分为规格化,非规格化 $$ \begin{align} -&规格化浮点数: (-1 )^S \times 2^{E-B} \times (1+M)\\ -&非规格化浮点数: (-1 )^S \times 2^{1-B} \times M\\ +&规格化浮点数:(-1 )^S \times 2^{E-B} \times (1+M)\\ +&非规格化浮点数:(-1 )^S \times 2^{1-B} \times M\\ &B=2^{E_{bw}-1}-1 \end{align} $$ @@ -117,7 +117,7 @@ AI 模型在业界长期依赖于 FP16 和 FP32 数据类型的训练,后来 B 1. 降低 MAC 的输入和输出数据位宽,能够有效减少数据的搬运和存储开销。更小的内存搬移带来更低的功耗开销。 -2. 减少 MAC 计算的开销和代价,比如,两个 int8 数据类型的相乘,累加和使用 16bit 位宽的寄存器即可,而 FP16 数据类型的相乘,累加和需要设计 32 位宽的寄存器。 8bit 和 16bit 计算对硬件电路设计的复杂度影响也很大。 +2. 减少 MAC 计算的开销和代价,比如,两个 int8 数据类型的相乘,累加和使用 16bit 位宽的寄存器即可,而 FP16 数据类型的相乘,累加和需要设计 32 位宽的寄存器。8bit 和 16bit 计算对硬件电路设计的复杂度影响也很大。 ![](images/06BitWidth06.png) @@ -131,7 +131,7 @@ AI 模型在业界长期依赖于 FP16 和 FP32 数据类型的训练,后来 B 结合 AI 计算模式,通过上面低比特位宽数据的一些理解,可以引发如下方面对 AI 芯片设计的思考。 -1. 当降低位宽的时候,主要对尾数 M 和动态范围指数 E 两个值进行调整,这两个值具有不同的影响方向。尾数 M 的调整: 对数据的精度产生影响。指数 E 的调整: 指数的调整会影响数据的表达范围。 +1. 当降低位宽的时候,主要对尾数 M 和动态范围指数 E 两个值进行调整,这两个值具有不同的影响方向。尾数 M 的调整:对数据的精度产生影响。指数 E 的调整:指数的调整会影响数据的表达范围。 2. 不同比特位宽的选择时候,需要考虑对模型精度的影响,不同的数据集和不同的任务会有不一样的性能表现。比如 NLP 和 CV 的数据集,分类和检测的任务类型,需要进行全面的评测。 diff --git a/02Hardware/02ChipBase/01CPUBase.md b/02Hardware/02ChipBase/01CPUBase.md index 5e0349c2..30d455d9 100644 --- a/02Hardware/02ChipBase/01CPUBase.md +++ b/02Hardware/02ChipBase/01CPUBase.md @@ -4,7 +4,7 @@ CPU 是 Central Processing Unit(中央处理器)的简称,它负责执行指令和计算,控制着计算机的所有组件。CPU 从无到有,从弱小到强大,经历了漫长发展过程,其间发生了无数的故事。 -在本节将着重介绍 CPU 基础内容,从 CPU 的发展历史入手,看看世界上第一块 CPU 是怎么诞生的,再到当代 CPU 的组成,为什么 CPU 能为我们的电脑处理那么多的事情? +在本节将着重介绍 CPU 基础内容,从 CPU 的发展历史入手,看看世界上第一块 CPU 是怎么诞生的,再到当代 CPU 的组成,为什么 CPU 能为电脑处理那么多的事情? ## CPU 发展历史和组成 @@ -66,7 +66,7 @@ CPU 的主要功能就是运算,这正是通过算术逻辑单元(ALU,Arit 算术单元负责对二进制数执行加减等数学运算,而逻辑单元执行与、或、非等逻辑运算,以及对两个操作数进行比较等。另外 ALU 还具备位移功能,将输入的操作数向左或向右移动从而得到新的操作数.不只是 CPU,其他如图形处理器 GPU 等几乎所有的微处理器中,ALU 都是最基本的组件。 -除了执行与加法和减法相关的计算外,ALU 还可以处理两个整数的乘法,因为它们旨在执行整数计算;因此,它的结果也是一个整数。但是,除法运算通常不能由 ALU 执行,因为除法运算可能会产生浮点数的结果。相反,浮点单元 (FPU,floating-point unit) 通常处理除法运算;FPU 也可以执行其他非整数计算。 +除了执行与加法和减法相关的计算外,ALU 还可以处理两个整数的乘法,因为它们旨在执行整数计算;因此,它的结果也是一个整数。但是,除法运算通常不能由 ALU 执行,因为除法运算可能会产生浮点数的结果。相反,浮点单元 (FPU,floating-point unit)通常处理除法运算;FPU 也可以执行其他非整数计算。 > 浮点数的由来:用科学计数法的方式表示小数时,小数点的位置就变得「漂浮不定」了,这就是相对于定点数,浮点数名字的由来。 @@ -188,7 +188,7 @@ CU 接收这三个外部参数后,就能够发出控制信号——微命令 4. **写回**:结果写回阶段,作为最后一个阶段,结果写回(Write Back,WB)阶段把执行指令阶段的运行结果数据写回到 CPU 的内部寄存器中,以便被后续的指令快速地存取; -结合下图简单解释,第一步就是从内存里面去读取一些指令,给到我们的控制单元 CU,而控制单元就会对我们的刚才读取的一些指令来进行解码,变成正式的一些 command 命令,然后 ALU 就会去执行这些 command,这些命令执行完之后就会存储回来我们的内存进行汇总也就是写回。 +结合下图简单解释,第一步就是从内存里面去读取一些指令,给到控制单元 CU,而控制单元就会对刚才读取的一些指令来进行解码,变成正式的一些 command 命令,然后 ALU 就会去执行这些 command,这些命令执行完之后就会存储回来内存进行汇总也就是写回。 ![CPU 工作流](images/01CPUBase13.png) @@ -214,7 +214,7 @@ CU 接收这三个外部参数后,就能够发出控制信号——微命令 回到 CPU 的的架构,我们需要了解的是 CPU 三大组成的各自分工,控制器和寄存器负责的工作最多、要存的数据最多的两部分。 -下图是 CPU 的一个简要架构图,从下往上是 DRAM(Dynamic Random Access Memory,动态随机存取存储器)以及 Cache 这些其实都可以当作是我们的内存,然后有控制器,真正的执行单元就是我们的 ALU,我们可以看到真正执行单元的 ALU 占的面积是非常的小的,图中假设有 4 个 ALU 或者计算盒,而在整体电路里面占了绝大部分面积的是内存还有控制器,而并非计算,所以说 CPU 是非常适合擅长处理我们的逻辑控制,而并非计算。 +下图是 CPU 的一个简要架构图,从下往上是 DRAM(Dynamic Random Access Memory,动态随机存取存储器)以及 Cache 这些其实都可以当作是内存,然后有控制器,真正的执行单元就是 ALU,我们可以看到真正执行单元的 ALU 占的面积是非常的小的,图中假设有 4 个 ALU 或者计算盒,而在整体电路里面占了绝大部分面积的是内存还有控制器,而并非计算,所以说 CPU 是非常适合擅长处理逻辑控制,而并非计算。 ![CPU 架构图](images/01CPUBase14.png) @@ -224,7 +224,7 @@ CU 接收这三个外部参数后,就能够发出控制信号——微命令 实质上 ALU 模块(逻辑运算单元)是用来完成数据计算,其他各个模块的存在都是为了保证指令能够一条接一条的有序执行。这种通用性结构对于传统的编程计算模式非常适合,同时可以通过提升 CPU 主频(提升单位时间内执行指令的条数)来提升计算速度。  -但是,依照冯·诺依曼架构针对指令的“顺序执行”的原则,CPU 只能执行完一条指令再来下一条,计算能力进一步受限。当然了,我们会存在多核的情况,一次或可以执行多条指令,因为大原则受限于顺序执行,所以我们的计算能力的提升是受到限制的。于是我们引入了第二个内容,也就是本节的第二章,CPU 并行处理架构。 +但是,依照冯·诺依曼架构针对指令的“顺序执行”的原则,CPU 只能执行完一条指令再来下一条,计算能力进一步受限。当然了,我们会存在多核的情况,一次或可以执行多条指令,因为大原则受限于顺序执行,所以计算能力的提升是受到限制的。于是我们引入了第二个内容,也就是本节的第二章,CPU 并行处理架构。 ## 小结与思考 diff --git a/02Hardware/02ChipBase/02CPUISA.md b/02Hardware/02ChipBase/02CPUISA.md index 23bf6231..511d0869 100644 --- a/02Hardware/02ChipBase/02CPUISA.md +++ b/02Hardware/02ChipBase/02CPUISA.md @@ -30,7 +30,7 @@ ISA 是处理器支持的所有指令的语义,包括指令本身及其操作 CPU 在硬件电路上支持的这些指令的集合就是指令集。指令集是一个标准,定义了指令的种类、格式,需要的配套的寄存器等。CPU 在设计之前,就需要先设计一套指令集,或者说使用现成的指令集(如 ARM、X86 指令集),在硬件电路上实现这些指令。 -CPU 设计好后,还需要配套的编译器,编译器也需要参考这个指令集标准,将我们编写的 C 程序、C++程序编译成 CPU 硬件电路支持的加减乘除、与或非等指令,我们的程序才能在 CPU 上运行。 +CPU 设计好后,还需要配套的编译器,编译器也需要参考这个指令集标准,将我们编写的 C 程序、C++程序编译成 CPU 硬件电路支持的加减乘除、与或非等指令,程序才能在 CPU 上运行。 指令集也可以说是一个标准,这个标准并不是一成不变的,会随着需求不断添加新的指令。比如随着多媒体技术的发展,需要对各种音频、视频等大量的数据做计算。一个简单的数组加法 $a[100]+b[100]$,就需要做 100 次运算,自从指令集添加了 SIMD 指令,一次运算就可以搞定了。 @@ -60,7 +60,7 @@ MIPS32 的指令字长是 32 位的定长格式,也就是由 32 个 0 或者 1 ![ISA](images/02CPUISA02.png) -右边三个部分对应着的是我们的操作对象,其中一共有三个参数分别是:目的操作数 Addr1、原操作数 Addr2 以及立即数 imediate Value。其中目的操作数 Addr1、原操作数 Addr2 都是 5 位所代表的是我们寄存器的地址,Addr1 用于存储计算后的结果,Addr2 地址中存储着要进行加法计算的数值。imediate Value 是一个立即数共 16 位,它是一个有符号的常量值,用于与 Addr2 寄存器中的值相加。 +右边三个部分对应着的是操作对象,其中一共有三个参数分别是:目的操作数 Addr1、原操作数 Addr2 以及立即数 imediate Value。其中目的操作数 Addr1、原操作数 Addr2 都是 5 位所代表的是我们寄存器的地址,Addr1 用于存储计算后的结果,Addr2 地址中存储着要进行加法计算的数值。imediate Value 是一个立即数共 16 位,它是一个有符号的常量值,用于与 Addr2 寄存器中的值相加。 图中的立即数转化为十进制就是 350,这条指令会将寄存器 Addr2 中的值与立即数 355 相加,并将结果存储在 Addr1 寄存器中。 @@ -70,7 +70,7 @@ MIPS32 的指令字长是 32 位的定长格式,也就是由 32 个 0 或者 1 ### ISA 基本分类 -一般来说,指令都是从数据池里面取出数据,然后对数据处理,最后再将数据放回,这三部曲是指令的基本内容。寻址方式提供了从数据池读取或写入数据的地址,算是解决了这三部曲的关键问题。 在计算机体系结构中,CPU 的运算指令、控制指令和数据移动指令是构成指令集的基本元素。 +一般来说,指令都是从数据池里面取出数据,然后对数据处理,最后再将数据放回,这三部曲是指令的基本内容。寻址方式提供了从数据池读取或写入数据的地址,算是解决了这三部曲的关键问题。在计算机体系结构中,CPU 的运算指令、控制指令和数据移动指令是构成指令集的基本元素。 1. 运算指令:在 ALU 中执行的计算操作 diff --git a/02Hardware/02ChipBase/03CPUData.md b/02Hardware/02ChipBase/03CPUData.md index b7a8ae66..0ddc6cd0 100644 --- a/02Hardware/02ChipBase/03CPUData.md +++ b/02Hardware/02ChipBase/03CPUData.md @@ -1,41 +1,41 @@ # CPU(中央处理器) -在现代计算机科学领域,中央处理器(CPU)是计算机系统的核心部件,承担着执行指令和处理数据的关键任务。CPU的设计和性能直接影响到整个系统的效率和能力。在这一节我们将详细探讨CPU的主要组成部分及其功能,解释其工作原理,并深入分析算力如何通过各种因素如核心数量、时钟频率和内存带宽来衡量和优化。我们还将通过算力敏感度的分析,探讨在高性能计算中操作强度、处理元素和带宽的平衡关系。最后,会展示当今CPU和相关技术在不同领域的性能趋势,包括逻辑电路技术、服务器性能、GPU集群性能以及超级计算中心的演变。 +在现代计算机科学领域,中央处理器(CPU)是计算机系统的核心部件,承担着执行指令和处理数据的关键任务。CPU 的设计和性能直接影响到整个系统的效率和能力。在这一节我们将详细探讨 CPU 的主要组成部分及其功能,解释其工作原理,并深入分析算力如何通过各种因素如核心数量、时钟频率和内存带宽来衡量和优化。我们还将通过算力敏感度的分析,探讨在高性能计算中操作强度、处理元素和带宽的平衡关系。最后,会展示当今 CPU 和相关技术在不同领域的性能趋势,包括逻辑电路技术、服务器性能、GPU 集群性能以及超级计算中心的演变。 下面是 CPU 的主要组成部分及其功能: ## CPU 的组成 -CPU是计算机的核心处理单元,由算术逻辑单元(ALU)、控制单元(CU)、存储单元、总线接口与时钟和其他关键组件组成,负责解释和执行指令来处理数据并协调系统操作。下图展示了计算机中央处理单元(CPU)的结构和主要组件 +CPU 是计算机的核心处理单元,由算术逻辑单元(ALU)、控制单元(CU)、存储单元、总线接口与时钟和其他关键组件组成,负责解释和执行指令来处理数据并协调系统操作。下图展示了计算机中央处理单元(CPU)的结构和主要组件 ![CPU](images/03CPUdata08.png) ### 算术逻辑单元(ALU) -ALU全称Arithmetic logic unit,中文名为算术逻辑单元,是 CPU 中负责执行算术和逻辑运算的部分。它可以进行加法、减法、乘法、除法等算术运算,还可以执行与、或、非、异或等逻辑运算。 +ALU 全称 Arithmetic logic unit,中文名为算术逻辑单元,是 CPU 中负责执行算术和逻辑运算的部分。它可以进行加法、减法、乘法、除法等算术运算,还可以执行与、或、非、异或等逻辑运算。 -下图展示的是一个算术逻辑单元(ALU)的基本工作原理,图中的各个部分对应ALU的不同功能和输入/输出 +下图展示的是一个算术逻辑单元(ALU)的基本工作原理,图中的各个部分对应 ALU 的不同功能和输入/输出 ![ALU](images/03CPUdata07.png) -图中的“操作数A”和“操作数B”代表的是输入到ALU中的两个数据。这些数据是ALU进行运算所需要的值,通常是从寄存器中提取的,在实际操作中,这些操作数可以是需要加、减、乘、除的数字,或是需要进行逻辑运算(如与、或、非)的位模式。 +图中的“操作数 A”和“操作数 B”代表的是输入到 ALU 中的两个数据。这些数据是 ALU 进行运算所需要的值,通常是从寄存器中提取的,在实际操作中,这些操作数可以是需要加、减、乘、除的数字,或是需要进行逻辑运算(如与、或、非)的位模式。 -图中的操作码则是指示ALU应该执行哪种操作的信号。它是从控制单元发送到ALU的,告诉ALU当前要执行的操作类型(如加法、减法、乘法、除法、逻辑与、或、非等)。操作码的选择决定了ALU如何处理输入的操作数 +图中的操作码则是指示 ALU 应该执行哪种操作的信号。它是从控制单元发送到 ALU 的,告诉 ALU 当前要执行的操作类型(如加法、减法、乘法、除法、逻辑与、或、非等)。操作码的选择决定了 ALU 如何处理输入的操作数 -图中的状态输入通常包含一些控制信号或标志位,这些信号可以影响ALU的操作方式。例如,状态输入可能指示ALU是否应该执行带符号的操作或处理溢出情况。 +图中的状态输入通常包含一些控制信号或标志位,这些信号可以影响 ALU 的操作方式。例如,状态输入可能指示 ALU 是否应该执行带符号的操作或处理溢出情况。 -图中的运算结果是指ALU输出的结果。经过ALU处理后的数据结果会输出到这里,通常被存储回寄存器或直接用于下一步操作。这个结果可以是算术计算的结果(如加法的和、减法的差等)或逻辑运算的结果(如与操作的结果)。 +图中的运算结果是指 ALU 输出的结果。经过 ALU 处理后的数据结果会输出到这里,通常被存储回寄存器或直接用于下一步操作。这个结果可以是算术计算的结果(如加法的和、减法的差等)或逻辑运算的结果(如与操作的结果)。 -图中的状态输出是ALU操作后生成的状态信息。这些信息可以包括零标志(表示结果为零)、进位标志(表示计算中发生了进位或借位)、溢出标志(表示结果超出了表示范围)等。状态输出通常反馈给控制单元,以便它可以决定后续的操作。例如,如果计算结果为零,控制单元可能会跳转到程序中的另一个部分。 +图中的状态输出是 ALU 操作后生成的状态信息。这些信息可以包括零标志(表示结果为零)、进位标志(表示计算中发生了进位或借位)、溢出标志(表示结果超出了表示范围)等。状态输出通常反馈给控制单元,以便它可以决定后续的操作。例如,如果计算结果为零,控制单元可能会跳转到程序中的另一个部分。 ### 控制单元(CU) -CU全称(Control Unit),中文名为控制单元,控制单元是中央处理器(CPU)的核心部分之一,负责协调和控制计算机执行程序的过程。CU的工作流程可以通过一系列的步骤来描述,这些步骤通常被称为取指(Fetch)、解码(Decode)和执行(Execute)周期。 +CU 全称(Control Unit),中文名为控制单元,控制单元是中央处理器(CPU)的核心部分之一,负责协调和控制计算机执行程序的过程。CU 的工作流程可以通过一系列的步骤来描述,这些步骤通常被称为取指(Fetch)、解码(Decode)和执行(Execute)周期。 -首先在取指阶段,程序计数器(PC)的值被用作地址,通过地址总线发送到内存,内存中的指令在指定地址被读取,并通过数据总线发送到CPU,读取的指令被存储在指令寄存器(IR)中,程序计数器(PC)**自动递增,以指向下一条指令的地址,准备下一次取指操作。 +首先在取指阶段,程序计数器(PC)的值被用作地址,通过地址总线发送到内存,内存中的指令在指定地址被读取,并通过数据总线发送到 CPU,读取的指令被存储在指令寄存器(IR)中,程序计数器(PC)**自动递增,以指向下一条指令的地址,准备下一次取指操作。 -然后在解码阶段,则将指令寄存器中的机器指令翻译成操作码和操作数。首先指令译码器(Instruction Decoder)读取指令寄存器中的内容。然后将指令拆分成操作码(Opcode)和操作数(Operands)。紧接着控制信号生成器根据操作码生成适当的控制信号,指示ALU(算术逻辑单元)、寄存器和其他组件执行具体的操作,确定需要哪些资源(如ALU、内存、寄存器)来执行指令。 +然后在解码阶段,则将指令寄存器中的机器指令翻译成操作码和操作数。首先指令译码器(Instruction Decoder)读取指令寄存器中的内容。然后将指令拆分成操作码(Opcode)和操作数(Operands)。紧接着控制信号生成器根据操作码生成适当的控制信号,指示 ALU(算术逻辑单元)、寄存器和其他组件执行具体的操作,确定需要哪些资源(如 ALU、内存、寄存器)来执行指令。 -最后在执行阶段,ALU将在算术和逻辑操作中接收控制信号并对操作数进行处理(如加减乘除、逻辑运算),如果指令涉及数据传输(如从内存读取或写入数据),控制单元会协调这些操作。如果是涉及结果存储,则处理结果将被存储在指定的寄存器或内存位置,然后及时更新状态寄存器(如零标志、进位标志等),反映指令执行的结果。 +最后在执行阶段,ALU 将在算术和逻辑操作中接收控制信号并对操作数进行处理(如加减乘除、逻辑运算),如果指令涉及数据传输(如从内存读取或写入数据),控制单元会协调这些操作。如果是涉及结果存储,则处理结果将被存储在指定的寄存器或内存位置,然后及时更新状态寄存器(如零标志、进位标志等),反映指令执行的结果。 ### 存储单元 @@ -66,26 +66,26 @@ CPU 时钟频率(Clock Frequency)是指 CPU 每秒可以执行的周期数 时钟周期(Clock Cycle)是 CPU 执行一个基本操作(如读、写、计算等)的时间单位,一个时钟周期的长度与时钟频率成反比。更高的时钟频率意味着更短的时钟周期,从而允许更快的操作。 ## CPU 的工作原理 -CPU 的工作可以分为取指(Fetch)、译码(Decode)、执行(Execute)、访存(Memory Access)和写回(Write Back)五个阶段。这些阶段有序且循环地运行,形成了完整的指令周期。每个阶段都有其独特的功能和作用,共同确保指令的正确执行和数据的精确处理。下图是CPU中央处理器(CPU)执行指令的基本过程。 +CPU 的工作可以分为取指(Fetch)、译码(Decode)、执行(Execute)、访存(Memory Access)和写回(Write Back)五个阶段。这些阶段有序且循环地运行,形成了完整的指令周期。每个阶段都有其独特的功能和作用,共同确保指令的正确执行和数据的精确处理。下图是 CPU 中央处理器(CPU)执行指令的基本过程。 ![CPU 工作图](images/03CPUdata09.png) ### 取指(Fetch) -在CPU执行指令的第一阶段,取指阶段主要由程序计数器和指令寄存器负责,程序计数器位于图的左侧,标注为“程序计数器”。程序计数器保存着下一条要执行的指令的内存地址,在取指阶段,程序计数器的值被输出到内存地址总线(图中总线箭头的方向指向内存),用于从内存读取指令。当内存中的指令地址被确定后,指令通过总线被读取并存储到“指令寄存器”中。 +在 CPU 执行指令的第一阶段,取指阶段主要由程序计数器和指令寄存器负责,程序计数器位于图的左侧,标注为“程序计数器”。程序计数器保存着下一条要执行的指令的内存地址,在取指阶段,程序计数器的值被输出到内存地址总线(图中总线箭头的方向指向内存),用于从内存读取指令。当内存中的指令地址被确定后,指令通过总线被读取并存储到“指令寄存器”中。 ### 译码(Decode) 在取指之后,指令进入译码阶段,这个阶段由指令解码器和寄存器堆进行处理,指令解码器位于图的中央区域,从指令寄存器读取的指令被送到解码器进行解释,以此确定操作类型以及操作数的位置。图中靠近解码器的“寄存器堆”模块,用于提供指令中所需的操作数,根据解码器的指令,从寄存器堆中读取所需的数据。 ### 执行(Execute) -在执行阶段,指令中的操作被实际执行,这一阶段主要由运算器完成,图的右侧区域标注为“运算器”,ALU接收从寄存器堆或解码器传递过来的操作数并执行指定的操作(如加法、减法、逻辑与或逻辑或等)。 +在执行阶段,指令中的操作被实际执行,这一阶段主要由运算器完成,图的右侧区域标注为“运算器”,ALU 接收从寄存器堆或解码器传递过来的操作数并执行指定的操作(如加法、减法、逻辑与或逻辑或等)。 -### 访存(Memory Access) -如果需要从内存中读取数据,CPU会在从内存读取这一阶段与内存交互,从内存中取出数据作为操作数。如果指令需要将结果写回到内存中(如存储指令),CPU会在写入内存这一阶段将计算结果存储到内存指定位置。 +### 访存(Memory Access) +如果需要从内存中读取数据,CPU 会在从内存读取这一阶段与内存交互,从内存中取出数据作为操作数。如果指令需要将结果写回到内存中(如存储指令),CPU 会在写入内存这一阶段将计算结果存储到内存指定位置。 ### 写回(Write Back) -最后,写回阶段将执行结果写入到寄存器或内存,如果指令的操作涉及寄存器,结果会被写回到指定的寄存器。如果操作结果需要存储在内存中,CPU会确保结果被正确存储到内存中。 +最后,写回阶段将执行结果写入到寄存器或内存,如果指令的操作涉及寄存器,结果会被写回到指定的寄存器。如果操作结果需要存储在内存中,CPU 会确保结果被正确存储到内存中。 ## 从数据看 CPU 计算 @@ -179,15 +179,15 @@ $$ ### 影响 CPU 算力因素 -1. 核心数量:核心数量是衡量CPU并行处理能力的重要指标之一。每个核心可以独立执行任务,更多的核心意味着CPU可以同时处理更多的任务,从而显著提升并行计算的能力。现代CPU通常设计为多核心架构,这使得它们在处理复杂的、多线程任务时具有明显的优势。 +1. 核心数量:核心数量是衡量 CPU 并行处理能力的重要指标之一。每个核心可以独立执行任务,更多的核心意味着 CPU 可以同时处理更多的任务,从而显著提升并行计算的能力。现代 CPU 通常设计为多核心架构,这使得它们在处理复杂的、多线程任务时具有明显的优势。 -2. 时钟频率:时钟频率指的是CPU每秒钟可以执行的周期数,通常以GHz(千兆赫兹)为单位。更高的时钟频率意味着CPU可以在更短的时间内完成更多的计算任务。 +2. 时钟频率:时钟频率指的是 CPU 每秒钟可以执行的周期数,通常以 GHz(千兆赫兹)为单位。更高的时钟频率意味着 CPU 可以在更短的时间内完成更多的计算任务。 -3. 每个时钟周期的浮点运算次数:现代CPU架构采用超标量设计和向量化技术来增加每个时钟周期内可以执行的浮点运算次数。浮点运算是处理复杂计算任务的关键,特别是在科学计算和图形处理领域。 +3. 每个时钟周期的浮点运算次数:现代 CPU 架构采用超标量设计和向量化技术来增加每个时钟周期内可以执行的浮点运算次数。浮点运算是处理复杂计算任务的关键,特别是在科学计算和图形处理领域。 -4. 缓存和内存带宽:缓存和内存带宽是影响CPU数据访问速度的关键因素。高效的缓存系统和足够的内存带宽可以显著减少数据传输的延迟,提高整体计算效率。 +4. 缓存和内存带宽:缓存和内存带宽是影响 CPU 数据访问速度的关键因素。高效的缓存系统和足够的内存带宽可以显著减少数据传输的延迟,提高整体计算效率。 -5. 指令集架构:指令集架构(ISA)是CPU如何执行指令的基础。不同的ISA(如x86、ARM、RISC-V)对浮点运算的支持和优化程度有所不同,直接影响CPU的算力表现。 +5. 指令集架构:指令集架构(ISA)是 CPU 如何执行指令的基础。不同的 ISA(如 x86、ARM、RISC-V)对浮点运算的支持和优化程度有所不同,直接影响 CPU 的算力表现。 ## 算力敏感度 @@ -199,7 +199,7 @@ $$ 1. **操作强度(Operational Intensity)**:操作强度常用 ops/byte(操作次数/字节)表示,是指每字节数据进行的操作次数。这一概念在计算机科学中至关重要,尤其在高性能计算领域。操作强度衡量的是计算与内存访问之间的关系。操作强度越高,意味着处理器在处理数据时进行更多计算操作,而不是频繁访问内存。这种情况下,处理器需要的数据带宽相对较低,因为大部分时间花费在计算上,而非在数据传输上。反之,操作强度较低时,处理器的计算操作较少,大部分时间可能花费在内存数据的读取和写入上,这时对数据带宽的需求较高。 -2. **处理元素(Processing Elements, PEs)**:处理元素是指计算系统中执行操作的基本单元。它们是计算的核心,负责实际的数据处理任务。在现代计算架构中,处理元素可以是一个独立的CPU核心、一个GPU流处理器,或是一个专用计算单元。系统中的处理元素数量和性能直接决定了系统的理论峰值性能。处理元素越多,或者它们的计算能力越强,系统能够在单位时间内完成的计算任务就越多,从而提升了系统的整体性能。现代高性能计算系统通常通过增加处理元素的数量或提升单个处理元素的效率来实现性能的提高。此外,处理元素的架构和设计也会影响系统的能源效率和热管理,进而影响到系统的实际应用场景和运行成本。 +2. **处理元素(Processing Elements, PEs)**:处理元素是指计算系统中执行操作的基本单元。它们是计算的核心,负责实际的数据处理任务。在现代计算架构中,处理元素可以是一个独立的 CPU 核心、一个 GPU 流处理器,或是一个专用计算单元。系统中的处理元素数量和性能直接决定了系统的理论峰值性能。处理元素越多,或者它们的计算能力越强,系统能够在单位时间内完成的计算任务就越多,从而提升了系统的整体性能。现代高性能计算系统通常通过增加处理元素的数量或提升单个处理元素的效率来实现性能的提高。此外,处理元素的架构和设计也会影响系统的能源效率和热管理,进而影响到系统的实际应用场景和运行成本。 3. **带宽(Bandwidth)**:带宽是指系统在单位时间内可以处理的数据量,通常以 GB/s(千兆字节每秒)或 TB/s(太字节每秒)为单位来表示。带宽是计算系统中的一个关键指标,直接影响数据传输的效率。带宽限制是影响高操作强度应用性能的主要因素之一。当系统的操作强度较高时,处理器对内存的访问需求降低,此时带宽的瓶颈影响较小。然而,对于那些操作强度较低的应用,处理器频繁访问内存,对带宽的需求极大,如果带宽不足,就会限制系统的整体性能表现。通过优化带宽和存储器架构,可以在一定程度上缓解这些瓶颈问题,从而提升系统的计算效率。 @@ -264,9 +264,9 @@ $$ ## 小结与思考 -本节我们深入探讨了中央处理器(CPU)的核心组件、工作原理以及其算力的衡量和优化。通过分析了CPU的算术逻辑单元(ALU)、控制单元(CU)、存储单元、总线接口与时钟的功能,和具体的算力计算示例,理解了如何评估和提升计算性能。 +本节我们深入探讨了中央处理器(CPU)的核心组件、工作原理以及其算力的衡量和优化。通过分析了 CPU 的算术逻辑单元(ALU)、控制单元(CU)、存储单元、总线接口与时钟的功能,和具体的算力计算示例,理解了如何评估和提升计算性能。 -通过算力敏感度分析,我们了解了操作强度、处理元素和带宽如何共同影响系统性能。以及展示了现代技术的趋势,发现服务器、GPU和超算中心的性能正在迅速提升,说明计算系统的性能正以惊人的速度提升。随着逻辑电路技术的进步,服务器和GPU的性能不断突破新高,超级计算中心的算力达到前所未有的水平。在这样的背景下,新的计算架构和优化方法将不断涌现,推动科技进步和应用创新。 +通过算力敏感度分析,我们了解了操作强度、处理元素和带宽如何共同影响系统性能。以及展示了现代技术的趋势,发现服务器、GPU 和超算中心的性能正在迅速提升,说明计算系统的性能正以惊人的速度提升。随着逻辑电路技术的进步,服务器和 GPU 的性能不断突破新高,超级计算中心的算力达到前所未有的水平。在这样的背景下,新的计算架构和优化方法将不断涌现,推动科技进步和应用创新。 最后,在这节内容中,我们深刻认识到计算能力和数据传输速率在提升系统性能中同等重要。未来的计算系统设计需要更好地平衡这两者,以避免出现计算资源的浪费或内存带宽的瓶颈。如何在不同应用场景中找到这个平衡点,是一个值得持续探索的课题。 ## 本节视频 diff --git a/02Hardware/02ChipBase/04CPULatency.md b/02Hardware/02ChipBase/04CPULatency.md index 723bb3fd..7bef777e 100644 --- a/02Hardware/02ChipBase/04CPULatency.md +++ b/02Hardware/02ChipBase/04CPULatency.md @@ -6,7 +6,7 @@ CPU(中央处理器)是计算机的核心组件,其性能对计算机系 ======== 主要强调,内存、带宽、时延之间的关系,一定要搞清楚三者之间的关系,深入深入深入理解。 -## CPU 计算时延 +## CPU 计算时延 ===== XXXXX,组织一下语言,下面的内容尽可能展开一下,不要都用大纲或者目录的方式。 @@ -20,7 +20,7 @@ CPU 计算时延主要由以下几个部分组成: - **执行时延(Execution Time)**:执行时延是指 CPU 实际执行指令所需的时间。这个时延取决于指令的类型和 CPU 的架构,例如流水线深度、并行度等。 -- **存储器访问时延(Memory Access Time)**: 存储器访问时延是指 CPU 访问主存储器或缓存所需的时间。这个时延受缓存层次结构(L1, L2, L3 缓存)和存储器带宽的影响。 +- **存储器访问时延(Memory Access Time)**:存储器访问时延是指 CPU 访问主存储器或缓存所需的时间。这个时延受缓存层次结构(L1, L2, L3 缓存)和存储器带宽的影响。 - **写回时延(Write-back Time)**:写回时延是指执行完指令后将结果写回寄存器或存储器的时间。这一过程也受缓存的影响。 @@ -32,7 +32,7 @@ CPU 计算时延主要由以下几个部分组成: - **并行处理(Parallel Processing)**:多核处理器和超线程技术允许多个指令同时执行,显著降低计算时延。并行处理的效率依赖于任务的可并行性。 -- **缓存命中率(Cache Hit Rate)**: 高缓存命中率可以显著减少存储器访问时延,提高整体性能。缓存失效(Cache Miss)会导致较高的存储器访问时延。 +- **缓存命中率(Cache Hit Rate)**:高缓存命中率可以显著减少存储器访问时延,提高整体性能。缓存失效(Cache Miss)会导致较高的存储器访问时延。 - **内存带宽(Memory Bandwidth)**:高内存带宽可以减少数据传输瓶颈,降低存储器访问时延,提升计算性能。 diff --git a/02Hardware/02ChipBase/05GPUBase.md b/02Hardware/02ChipBase/05GPUBase.md index 0aee4525..87f94768 100644 --- a/02Hardware/02ChipBase/05GPUBase.md +++ b/02Hardware/02ChipBase/05GPUBase.md @@ -54,11 +54,11 @@ GPU 即图形处理单元(Graphics Processing Unit),可以更高效地处 GPU 和 CPU 在架构方面的主要区别包括以下几点: -1. **并行处理能力**: CPU 拥有少量的强大计算单元(ALU),更适合处理顺序执行的任务,可以在很少的时钟周期内完成算术运算,时钟周期的频率很高,复杂的控制逻辑单元(Control)可以在程序有多个分支的情况下提供分支预测能力,因此 CPU 擅长逻辑控制和串行计算,流水线技术通过多个部件并行工作来缩短程序执行时间。GPU 控制单元可以把多个访问合并成,采用了数量众多的计算单元(ALU)和线程(Thread),大量的 ALU 可以实现非常大的计算吞吐量,超配的线程可以很好地平衡内存延时问题,因此可以同时处理多个任务,专注于大规模高度并行的计算任务。 +1. **并行处理能力**:CPU 拥有少量的强大计算单元(ALU),更适合处理顺序执行的任务,可以在很少的时钟周期内完成算术运算,时钟周期的频率很高,复杂的控制逻辑单元(Control)可以在程序有多个分支的情况下提供分支预测能力,因此 CPU 擅长逻辑控制和串行计算,流水线技术通过多个部件并行工作来缩短程序执行时间。GPU 控制单元可以把多个访问合并成,采用了数量众多的计算单元(ALU)和线程(Thread),大量的 ALU 可以实现非常大的计算吞吐量,超配的线程可以很好地平衡内存延时问题,因此可以同时处理多个任务,专注于大规模高度并行的计算任务。 -2. **内存架构**: CPU 被缓存 Cache 占据了大量空间,大量缓存可以保存之后可能需要访问的数据,可以降低延时; GPU 缓存很少且为线程(Thread)服务,如果很多线程需要访问一个相同的数据,缓存会合并这些访问之后再去访问 DRMA,获取数据之后由 Cache 分发到数据对应的线程。 GPU 更多的寄存器可以支持大量 Thread。 +2. **内存架构**:CPU 被缓存 Cache 占据了大量空间,大量缓存可以保存之后可能需要访问的数据,可以降低延时; GPU 缓存很少且为线程(Thread)服务,如果很多线程需要访问一个相同的数据,缓存会合并这些访问之后再去访问 DRMA,获取数据之后由 Cache 分发到数据对应的线程。GPU 更多的寄存器可以支持大量 Thread。 -3. **指令集**: CPU 的指令集更加通用,适合执行各种类型的任务; GPU 的指令集主要用于图形处理和通用计算,如 CUDA 和 OpenCL。 +3. **指令集**:CPU 的指令集更加通用,适合执行各种类型的任务; GPU 的指令集主要用于图形处理和通用计算,如 CUDA 和 OpenCL。 4. **功耗和散热**:CPU 的功耗相对较低,散热要求也相对较低;由于 GPU 的高度并行特性,其功耗通常较高,需要更好的散热系统来保持稳定运行。 diff --git a/02Hardware/02ChipBase/06NPUBase.md b/02Hardware/02ChipBase/06NPUBase.md index 8c38135f..19882f8f 100644 --- a/02Hardware/02ChipBase/06NPUBase.md +++ b/02Hardware/02ChipBase/06NPUBase.md @@ -6,7 +6,7 @@ ## 什么是 AI 芯片 -AI 芯片是专门为加速人工智能应用中的大量针对矩阵计算任务而设计的处理器或计算模块。与传统的通用芯片如中央处理器(CPU)不同,AI 芯片采用针对特定领域优化的体系结构(Domain-Specific Architecture, DSA),侧重于提升执行 AI 算法所需的专用计算性能。 +AI 芯片是专门为加速人工智能应用中的大量针对矩阵计算任务而设计的处理器或计算模块。与传统的通用芯片如中央处理器(CPU)不同,AI 芯片采用针对特定领域优化的体系结构(Domain-Specific Architecture,DSA),侧重于提升执行 AI 算法所需的专用计算性能。 如下图所示的就是一个典型的 AI 芯片架构,我们假设所有场景围绕应用,那么其周围的例如解码芯片(如图中黄色部分 RSU)、FPGA 芯片(如图中粉色部分)等都是属于针对特定领域优化的芯片结构。 diff --git a/02Hardware/02ChipBase/07Future.md b/02Hardware/02ChipBase/07Future.md index 9ea010c4..19ebfa0c 100644 --- a/02Hardware/02ChipBase/07Future.md +++ b/02Hardware/02ChipBase/07Future.md @@ -37,13 +37,13 @@ 异构计算的主要优势有: -性能飞跃: 异构架构将 CPU、GPU、FPGA 等计算单元有机结合,充分发挥各自优势,实现 1+1>2 的效果,显著提升计算性能。 +性能飞跃:异构架构将 CPU、GPU、FPGA 等计算单元有机结合,充分发挥各自优势,实现 1+1>2 的效果,显著提升计算性能。 -灵活定制: 针对不同计算任务,灵活选择合适的主张计算单元,实现资源的高效利用。 +灵活定制:针对不同计算任务,灵活选择合适的主张计算单元,实现资源的高效利用。 -降低成本: 相比于昂贵的专用计算单元,异构架构用更低的成本实现更高的性能,带来更佳的性价比。 +降低成本:相比于昂贵的专用计算单元,异构架构用更低的成本实现更高的性能,带来更佳的性价比。 -降低功耗: 异构架构能够根据任务需求动态调整资源分配,降低整体功耗,提升能源利用效率。 +降低功耗:异构架构能够根据任务需求动态调整资源分配,降低整体功耗,提升能源利用效率。 其应用场景也十分广泛,包括人工智能、高性能计算、大数据分析、图形处理等等 @@ -96,7 +96,7 @@ 3. **可以共享内存空间,消除冗余内存副本**:在此前的技术中,虽然 GPU 和 CPU 已整合到同一个芯片上,但是芯片在运算时要定位内存的位置仍然得经过繁杂的步骤,这是因为 CPU 和 GPU 的内存池仍然是独立运作。为了解决两者内存池独立的运算问题,当 CPU 程式需要在 GPU 上进行部分运算时,CPU 都必须从 CPU 的内存上复制所有的资料到 GPU 的内存上,而当 GPU 上的运算完成时,这些资料还得再复制回到 CPU 内存上。然而,将 CPU 与 GPU 放入同一架构,就能够消除冗余内存副本来改善问题,处理器不再需要将数据复制到自己的专用内存池来访问/更改该数据。统一内存池还意味着不需要第二个内存芯片池,即连接到 CPU 的 DRAM。 -除了 CPU 和 GPU 异构以外,AISC 在异构体系中也扮演着重要的角色(如下图),尤其是对于 AI 加速。其通过驱动程序和 CSR 和可配置表项交互,以此来控制硬件运行。和 GPU 类似, ASIC 的运行依然需要 CPU 的参与: +除了 CPU 和 GPU 异构以外,AISC 在异构体系中也扮演着重要的角色(如下图),尤其是对于 AI 加速。其通过驱动程序和 CSR 和可配置表项交互,以此来控制硬件运行。和 GPU 类似,ASIC 的运行依然需要 CPU 的参与: 1. 数据输入:数据在内存准备好,CPU 控制 ASIC 输入逻辑,把数据从内存搬到处理器 2. 数据输出:CPU 控制 ASIC 输出逻辑,把数据从处理器搬到内存,等待后续处理 @@ -124,11 +124,11 @@ ASIC 工作流示意图如下: 首先我们从三个角度来理解一下为什么超异构的出现是应运而生的: -1. **需求驱动**:软件新应用层出不穷,两年一个新热点。 随着人工智能、大数据、元宇宙等新兴技术的快速发展,对计算能力提出了越来越高的要求。传统单一架构的计算模式难以满足日益增长的计算需求,亟需新的计算架构来突破性能瓶颈。已有的热点技术仍在快速演进。 例如,元宇宙需要将算力提升 1000 倍才能实现逼真的沉浸式体验。超异构计算能够通过融合不同类型计算单元的优势,显著提升计算性能,为元宇宙等新兴技术的落地提供强有力的支持。 +1. **需求驱动**:软件新应用层出不穷,两年一个新热点。随着人工智能、大数据、元宇宙等新兴技术的快速发展,对计算能力提出了越来越高的要求。传统单一架构的计算模式难以满足日益增长的计算需求,亟需新的计算架构来突破性能瓶颈。已有的热点技术仍在快速演进。例如,元宇宙需要将算力提升 1000 倍才能实现逼真的沉浸式体验。超异构计算能够通过融合不同类型计算单元的优势,显著提升计算性能,为元宇宙等新兴技术的落地提供强有力的支持。 -2. **工艺和封装支撑**:Chiplet 封装使得在单芯片层次,可以构建规模数量级提升的超大系统。 Chiplet 封装技术将多个芯片封装在一个封装体内,可以显著提高芯片的集成度和性能。这使得在单一封装内集成多种类型的 XPU 成为可能,进一步推动了超异构计算的发展。 +2. **工艺和封装支撑**:Chiplet 封装使得在单芯片层次,可以构建规模数量级提升的超大系统。Chiplet 封装技术将多个芯片封装在一个封装体内,可以显著提高芯片的集成度和性能。这使得在单一封装内集成多种类型的 XPU 成为可能,进一步推动了超异构计算的发展。 -3. **系统架构持续创新**:通过架构创新,在单芯片层次,实现多个数量级的性能提升。 随着计算机体系结构的不断发展,新的架构设计不断涌现,例如异构架构、多核架构等。这些架构能够通过充分发挥不同类型处理器的优势,显著提升计算性能。异构编程很难,超异构编程更是难上加难。 如何更好地驾驭超异构,是成败的关键。近年来,随着异构编程模型和工具的不断完善,超异构编程的难度逐渐降低,这为超异构计算的推广应用奠定了基础。 +3. **系统架构持续创新**:通过架构创新,在单芯片层次,实现多个数量级的性能提升。随着计算机体系结构的不断发展,新的架构设计不断涌现,例如异构架构、多核架构等。这些架构能够通过充分发挥不同类型处理器的优势,显著提升计算性能。异构编程很难,超异构编程更是难上加难。如何更好地驾驭超异构,是成败的关键。近年来,随着异构编程模型和工具的不断完善,超异构编程的难度逐渐降低,这为超异构计算的推广应用奠定了基础。 通过以上三个角度我们可以推断,超异构计算的出现是顺应时代发展需求的必然选择。它能够突破传统单一架构的性能瓶颈,满足日益增长的计算需求,为各行各业的创新发展注入强劲动力。 diff --git a/02Hardware/03GPUBase/01Works.md b/02Hardware/03GPUBase/01Works.md index ebacf0d7..3e723d1a 100644 --- a/02Hardware/03GPUBase/01Works.md +++ b/02Hardware/03GPUBase/01Works.md @@ -18,19 +18,19 @@ GPU 和 CPU 在架构方面的主要区别包括以下几点: -1. **并行处理能力**: CPU 拥有少量的强大计算单元(ALU),更适合处理顺序执行的任务,可以在很少的时钟周期内完成算术运算,时钟周期的频率很高,复杂的控制逻辑单元(Control)可以在程序有多个分支的情况下提供分支预测能力,因此 CPU 擅长逻辑控制和串行计算,流水线技术通过多个部件并行工作来缩短程序执行时间。GPU 控制单元可以把多个访问合并,采用了数量众多的计算单元(ALU)和线程(Thread),大量的 ALU 可以实现非常大的计算吞吐量,超配的线程可以很好地平衡内存延时问题,因此可以同时处理多个任务,专注于大规模高度并行的计算任务。 +1. **并行处理能力**:CPU 拥有少量的强大计算单元(ALU),更适合处理顺序执行的任务,可以在很少的时钟周期内完成算术运算,时钟周期的频率很高,复杂的控制逻辑单元(Control)可以在程序有多个分支的情况下提供分支预测能力,因此 CPU 擅长逻辑控制和串行计算,流水线技术通过多个部件并行工作来缩短程序执行时间。GPU 控制单元可以把多个访问合并,采用了数量众多的计算单元(ALU)和线程(Thread),大量的 ALU 可以实现非常大的计算吞吐量,超配的线程可以很好地平衡内存延时问题,因此可以同时处理多个任务,专注于大规模高度并行的计算任务。 -2. **内存架构**: CPU 被缓存 Cache 占据了大量空间,大量缓存可以保存之后可能需要访问的数据,可以降低延时。 GPU 缓存很少且为线程(Thread)服务,如果很多线程需要访问一个相同的数据,缓存会合并这些访问之后再去访问 DRMA,获取数据之后由 Cache 分发到数据对应的线程。 GPU 更多的寄存器可以支持大量 Thread。 +2. **内存架构**:CPU 被缓存 Cache 占据了大量空间,大量缓存可以保存之后可能需要访问的数据,可以降低延时。GPU 缓存很少且为线程(Thread)服务,如果很多线程需要访问一个相同的数据,缓存会合并这些访问之后再去访问 DRMA,获取数据之后由 Cache 分发到数据对应的线程。GPU 更多的寄存器可以支持大量 Thread。 -3. **指令集**: CPU 的指令集更加通用,适合执行各种类型的任务。 GPU 的指令集主要用于图形处理和通用计算。CPU 可以在不同的指令集之间快速切换,而 GPU 只是获取大量相同的指令并高速进行推送。 +3. **指令集**:CPU 的指令集更加通用,适合执行各种类型的任务。GPU 的指令集主要用于图形处理和通用计算。CPU 可以在不同的指令集之间快速切换,而 GPU 只是获取大量相同的指令并高速进行推送。 -4. **功耗和散热**: CPU 的功耗相对较低,散热要求也相对较低。由于 GPU 的高度并行特性,其功耗通常较高,需要更好的散热系统来保持稳定运行。 +4. **功耗和散热**:CPU 的功耗相对较低,散热要求也相对较低。由于 GPU 的高度并行特性,其功耗通常较高,需要更好的散热系统来保持稳定运行。 因此,CPU 更适合处理顺序执行的任务,如操作系统、数据分析等;而 GPU 适合处理需要大规模并行计算的任务,如图形处理、深度学习等。在异构系统中,GPU 和 CPU 经常会结合使用,以发挥各自的优势。 - GPU 起初用于处理图形图像和视频编解码相关的工作。 GPU 跟 CPU 最大的不同点在于,GPU 的设计目标是最大化吞吐量(Throughput),相比执行单个任务的快慢,更关心多个任务的并行度(Parallelism),即同时可以执行多少任务;CPU 则更关心延迟(Latency)和并发(Concurrency)。 + GPU 起初用于处理图形图像和视频编解码相关的工作。GPU 跟 CPU 最大的不同点在于,GPU 的设计目标是最大化吞吐量(Throughput),相比执行单个任务的快慢,更关心多个任务的并行度(Parallelism),即同时可以执行多少任务;CPU 则更关心延迟(Latency)和并发(Concurrency)。 - CPU 优化的目标是尽可能快地在尽可能低的延迟下执行完成任务,同时保持在任务之间具体快速切换的能力。它的本质是以序列化的方式处理任务。 GPU 的优化则全部都是用于增大吞吐量的,它允许一次将尽可能多的任务推送到 GPU 内部,然后 GPU 通过大数量的 Core 并行处理任务。 + CPU 优化的目标是尽可能快地在尽可能低的延迟下执行完成任务,同时保持在任务之间具体快速切换的能力。它的本质是以序列化的方式处理任务。GPU 的优化则全部都是用于增大吞吐量的,它允许一次将尽可能多的任务推送到 GPU 内部,然后 GPU 通过大数量的 Core 并行处理任务。 ![带宽、延迟与吞吐](images/01Works02.png) @@ -84,7 +84,7 @@ GPU 和 CPU 在架构方面的主要区别包括以下几点: CPU 和 GPU 的典型架构对比可知 GPU 可以比作一个大型的吞吐器,一部分线程用于等待数据,一部分线程等待被激活去计算,有一部分线程正在计算的过程中。GPU 的硬件设计工程师将所有的硬件资源都投入到增加更多的线程,而不是想办法减少数据搬运的延迟,指令执行的延迟。 -相对应的可以把 CPU 比喻成一台延迟机,主要工作是为了在一个线程里完成所有的工作,因为希望能够使用足够的线程去解决延迟的问题,所以 CPU 的硬件设计者或者硬件设计架构师就会把所有的资源和重心都投入到减少延迟上面,因此 CPU 的线程比只有一点多倍,这也是 SIMD(Single Instruction, Multiple Data)和 SIMT(Single Instruction, Multiple Threads)架构之间最大的区别。 CPU 不是通过增加线程来去解决问题,而是使用相反的方式去优化线程的执行速率和效率,这就是 CPU 跟 GPU 之间最大的区别,也是它们的本质区别。 +相对应的可以把 CPU 比喻成一台延迟机,主要工作是为了在一个线程里完成所有的工作,因为希望能够使用足够的线程去解决延迟的问题,所以 CPU 的硬件设计者或者硬件设计架构师就会把所有的资源和重心都投入到减少延迟上面,因此 CPU 的线程比只有一点多倍,这也是 SIMD(Single Instruction, Multiple Data)和 SIMT(Single Instruction, Multiple Threads)架构之间最大的区别。CPU 不是通过增加线程来去解决问题,而是使用相反的方式去优化线程的执行速率和效率,这就是 CPU 跟 GPU 之间最大的区别,也是它们的本质区别。 ![CPU 和 GPU 典型架构图](images/01Works03.png) @@ -199,9 +199,9 @@ GPU 和 CPU 内存带宽和时延进行比较,在 GPU 中如果把主内存( | 存储类型 | 结构 | 工作原理 | 性能 | 应用 | | ---------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ---------------------------------------- | -| DRAM(Dynamic Random Access Memory) | 一种基本的内存技术,通常以单层平面的方式组织,存储芯片分布在一个平面上 | 当读取数据时,电荷被传递到输出线路,然后被刷新。当写入数据时,电荷被存储在电容中。由于电容会逐渐失去电荷,因此需要周期性刷新来保持数据 | 具有较高的密度和相对较低的成本,但带宽和延迟相对较高 | 常用于个人电脑、笔记本电脑和普通服务器等一般计算设备中 | -| GDDR(Graphics Double Data Rate) | 专门为图形处理器设计的内存技术,具有较高的带宽和性能 | 在数据传输速度和带宽方面优于传统的 DRAM,适用于图形渲染和视频处理等需要大量数据传输的应用 | GDDR 与标准 DDR SDRAM 类似,但在设计上进行了优化以提供更高的数据传输速度。它采用双倍数据速率传输,即在每个时钟周期传输两次数据,提高了数据传输效率 | 主要用于高性能图形处理器(GPU)和游戏主机等需要高带宽内存的设备中 | -| HBM(High Bandwidth Memory) | 使用堆叠设计,将多个 DRAM 存储芯片堆叠在一起,形成三维结构 | 堆叠设计允许更短的数据传输路径和更高的带宽,同时减少了功耗和延迟。每个存储芯片通过硅间连接(Through Silicon Via,TSV)与其他存储芯片通信,实现高效的数据传输 | 具有非常高的带宽和较低的延迟,适用于高性能计算和人工智能等需要大量数据传输的领域 | 主要用于高端图形处理器(GPU)、高性能计算系统和服务器等需要高带宽内存的设备中 | +| DRAM(Dynamic Random Access Memory)| 一种基本的内存技术,通常以单层平面的方式组织,存储芯片分布在一个平面上 | 当读取数据时,电荷被传递到输出线路,然后被刷新。当写入数据时,电荷被存储在电容中。由于电容会逐渐失去电荷,因此需要周期性刷新来保持数据 | 具有较高的密度和相对较低的成本,但带宽和延迟相对较高 | 常用于个人电脑、笔记本电脑和普通服务器等一般计算设备中 | +| GDDR(Graphics Double Data Rate) | 专门为图形处理器设计的内存技术,具有较高的带宽和性能 | 在数据传输速度和带宽方面优于传统的 DRAM,适用于图形渲染和视频处理等需要大量数据传输的应用 | GDDR 与标准 DDR SDRAM 类似,但在设计上进行了优化以提供更高的数据传输速度。它采用双倍数据速率传输,即在每个时钟周期传输两次数据,提高了数据传输效率 | 主要用于高性能图形处理器(GPU)和游戏主机等需要高带宽内存的设备中 | +| HBM(High Bandwidth Memory) | 使用堆叠设计,将多个 DRAM 存储芯片堆叠在一起,形成三维结构 | 堆叠设计允许更短的数据传输路径和更高的带宽,同时减少了功耗和延迟。每个存储芯片通过硅间连接(Through Silicon Via,TSV)与其他存储芯片通信,实现高效的数据传输 | 具有非常高的带宽和较低的延迟,适用于高性能计算和人工智能等需要大量数据传输的领域 | 主要用于高端图形处理器(GPU)、高性能计算系统和服务器等需要高带宽内存的设备中 | 不同存储和传输的带宽和计算强度进行比较,假设 HBM 计算强度为 100,L2 缓存的计算强度只为 39,意味着每个数据只需要执行 39 个操作,L1 的缓存更少,计算强度只需要 8 个操作,这个时候对于硬件来说非常容易实现。这就是为什么 L1 缓存、L2 缓存和寄存器对 GPU 来说如此重要。可以把数据放在 L1 缓存里面然后对数据进行 8 个操作,使得计算达到饱和的状态,使 GPU 里面 SM 的算力利用率更高。但是 PCIe 的带宽很低,整体的时延很高,这将导致整体的算力强度很高,算力利用率很低。 diff --git a/02Hardware/03GPUBase/02Principle.md b/02Hardware/03GPUBase/02Principle.md index 4268b86b..904fd40c 100644 --- a/02Hardware/03GPUBase/02Principle.md +++ b/02Hardware/03GPUBase/02Principle.md @@ -44,11 +44,11 @@ $$Y[i,j] = \sum_{m}\sum_{n} X[i+m, j+n] \cdot K[m,n]$$ 在 AI 计算模式中,不是所有的计算都可以是线程独立的。计算中数据结构元素之间的对应关系有以下三种: -- 1)Element-wise(逐元素):逐元素操作是指对数据结构中的每个元素独立执行操作。这意味着操作应用于输入数据结构中对应元素的每一对,以生成输出数据结构。例如,对两个向量进行逐元素相加或相乘就是将对应元素相加或相乘,得到一个新的向量。 +- 1)Element-wise(逐元素):逐元素操作是指对数据结构中的每个元素独立执行操作。这意味着操作应用于输入数据结构中对应元素的每一对,以生成输出数据结构。例如,对两个向量进行逐元素相加或相乘就是将对应元素相加或相乘,得到一个新的向量。 -- 2)Local(局部):局部操作是指仅针对数据的特定子集执行的操作,而不考虑整个数据结构。这些操作通常涉及局部区域或元素的计算。例如,对图像的卷积运算中元素之间是有交互的,因为它仅影响该区域内的像素值,计算一个元素往往需要周边的元素参与配合。 +- 2)Local(局部):局部操作是指仅针对数据的特定子集执行的操作,而不考虑整个数据结构。这些操作通常涉及局部区域或元素的计算。例如,对图像的卷积运算中元素之间是有交互的,因为它仅影响该区域内的像素值,计算一个元素往往需要周边的元素参与配合。 -- 3)All to All(全对全):全对全操作是指数据结构中的每个元素与同一数据结构或不同数据结构中的每个其他元素进行交互的操作。这意味着所有可能的元素对之间进行信息交换,产生完全连接的通信模式,一个元素的求解得到另一个数据时数据之间的交换并不能够做到完全的线程独立。全对全操作通常用于并行计算和通信算法中,其中需要在所有处理单元之间交换数据。 +- 3)All to All(全对全):全对全操作是指数据结构中的每个元素与同一数据结构或不同数据结构中的每个其他元素进行交互的操作。这意味着所有可能的元素对之间进行信息交换,产生完全连接的通信模式,一个元素的求解得到另一个数据时数据之间的交换并不能够做到完全的线程独立。全对全操作通常用于并行计算和通信算法中,其中需要在所有处理单元之间交换数据。 ![AI 计算模式与线程的关系](images/02principle06.png) @@ -90,15 +90,15 @@ $$\text{计算强度} = \frac{\text{算术运算量}}{\text{数据传输量}}$$ 其中,算术运算量是指执行计算任务所需的浮点运算次数,数据传输量是指从内存读取数据或将数据写入内存的数据传输量。计算强度的值可以用来描述计算任务对计算和数据传输之间的依赖关系: -- 高计算强度:当计算强度较高时,意味着算术运算量较大,计算操作占据主导地位,相对较少的时间用于数据传输。在这种情况下,性能优化的重点通常是提高计算效率,如优化算法、并行计算等。 +- 高计算强度:当计算强度较高时,意味着算术运算量较大,计算操作占据主导地位,相对较少的时间用于数据传输。在这种情况下,性能优化的重点通常是提高计算效率,如优化算法、并行计算等。 - 低计算强度:当计算强度较低时,意味着数据传输量较大,数据传输成为性能瓶颈。在这种情况下,性能优化的关键是减少数据传输、优化数据访问模式等。 对于一个 $N \times N$ 矩阵乘法操作,可以计算其计算强度(Arithmetic Intensity)。 -1. **算术运算量**:对于两个 $N \times N$ 的矩阵相乘,总共需要进行 $N^3$ 次乘法和 $N^2(N-1)$ 次加法运算。因此,总的算术运算量为 $2N^3 - N^2$。 +1. **算术运算量**:对于两个 $N \times N$ 的矩阵相乘,总共需要进行 $N^3$ 次乘法和 $N^2(N-1)$ 次加法运算。因此,总的算术运算量为 $2N^3 - N^2$。 -2. **数据传输量**:在矩阵乘法中,需要从内存中读取两个输入矩阵和将结果矩阵写回内存。假设每个矩阵元素占据一个单位大小的内存空间,则数据传输量可以估计为 $3N^2$,包括读取两个输入矩阵和写入结果矩阵。 +2. **数据传输量**:在矩阵乘法中,需要从内存中读取两个输入矩阵和将结果矩阵写回内存。假设每个矩阵元素占据一个单位大小的内存空间,则数据传输量可以估计为 $3N^2$,包括读取两个输入矩阵和写入结果矩阵。 因此,矩阵乘法的计算强度可以计算为: diff --git a/02Hardware/03GPUBase/04History.md b/02Hardware/03GPUBase/04History.md index 5dcbbf9c..f29d5061 100644 --- a/02Hardware/03GPUBase/04History.md +++ b/02Hardware/03GPUBase/04History.md @@ -332,12 +332,12 @@ H100 一共有 8 组 GPC、66 组 TPC、132 组 SM,总计有 16896 个 CUDA | Hopper 赫柏架构 SM 硬件单元 | Hopper 赫柏架构每个 Process Block | 相比 Ampere 架构 | | --- | --- | --- | | 4 个 Warp Scheduler,4 个 Dispatch Unit | 1 个 Warp Scheduler,1 个 Dispatch Unit | 相同 | -| 128 个 FP32 Core(4 * 32) | 32 个 FP32 Core | x2 | -| 64 个 INT32 Core(4 * 16) | 16 个 INT32 Core | 相同 | -| 64 个 FP64 Core(4 * 16) | 16 个 FP32 Core | x2 | -| 4 个 Tensor Core4.0(4 * 1) | 1 个 Tensor Core | Tensor Core3.0 | -| 32 个 LD/ST Unit(4 * 8) | 8 个 LD/ST Unit | 相同 | -| 16 个 SFU(4 * 4) | 4 个 SFU | 相同 | +| 128 个 FP32 Core(4 * 32)| 32 个 FP32 Core | x2 | +| 64 个 INT32 Core(4 * 16)| 16 个 INT32 Core | 相同 | +| 64 个 FP64 Core(4 * 16)| 16 个 FP32 Core | x2 | +| 4 个 Tensor Core4.0(4 * 1)| 1 个 Tensor Core | Tensor Core3.0 | +| 32 个 LD/ST Unit(4 * 8)| 8 个 LD/ST Unit | 相同 | +| 16 个 SFU(4 * 4)| 4 个 SFU | 相同 | | Tensor Memory Accelerator | | 新增 | ![Hopper 赫柏架构 SM](images/04History30.png) @@ -380,7 +380,7 @@ NVIDIA CUDA 平台针对 NVIDIA Grace CPU,NVIDIA Grace Hopper Superchip 和 NV > > 1. 在贝叶斯统计学领域做出了开创性的工作,提出了许多重要的方法和理论,推动了贝叶斯分析在统计学中的发展。 > -> 2. 在信息论方面的研究成果为该领域的发展做出了重要贡献,提供了许多重要的理论基础和方法。 +> 2. 在信息论方面的研究成果为该领域的发展做出了重要贡献,提供了许多重要的理论基础和方法。 NVIDIA GB200 Grace Blackwell 超级芯片通过 900GB/s 超低功耗的片间互联,将两个 NVIDIA B200 Tensor Core GPU 与 NVIDIA Grace CPU 相连。在 90 天内训练一个 1.8 万亿参数的 MoE 架构 GPT 模型,需要 8000 个 Hopper 架构 GPU,15 兆瓦功率,Blackwell 架构只需要 2000 个 GPU,以及 1/4 的能源消耗。8 年时间,从 Pascal 架构到 Blackwell 架构,英伟达将 AI 计算性能提升了 1000 倍! diff --git a/02Hardware/04NVIDIA/02HistoryTC.md b/02Hardware/04NVIDIA/02HistoryTC.md index b288e6a1..69c6c269 100644 --- a/02Hardware/04NVIDIA/02HistoryTC.md +++ b/02Hardware/04NVIDIA/02HistoryTC.md @@ -172,7 +172,7 @@ Turing 架构的第二代 Tensor Core 在距离上一代 Volta 架构仅一年 对于大型语言模型(LLM)的推理任务而言,内存容量依然是这些加速器所面临的主要限制。因此,在推理过程中降低内存使用量成为了一个亟待解决的问题。通过采用低精度格式如 FP4 和 FP6,可以在保持推理质量的同时,有效减少内存消耗,这对于提升 LLM 推理的效率和可行性至关重要。 -此外,第五代 Tensor Core 还支持社区定义的微缩放格式 MX(Microscaling) Format ,它是一种精度调整技术,相比一般的 scalar format (比如 FP32, FP16),MX Format 的粒度更高,多个 scalar 构成一组数据(vector format),它允许模型在保持相对高精度的同时减少计算资源的消耗。 +此外,第五代 Tensor Core 还支持社区定义的微缩放格式 MX(Microscaling)Format ,它是一种精度调整技术,相比一般的 scalar format (比如 FP32, FP16),MX Format 的粒度更高,多个 scalar 构成一组数据(vector format),它允许模型在保持相对高精度的同时减少计算资源的消耗。 MX Format 的核心特点是其由两个主要部分组成:scale(X)和 element(P)。在这种格式中,k 个 element 共享一个相同的 scale。Element 的定义是基于 scalar format,如 FP32、FP16 等。这种设计允许在保持一定精度的同时,通过共享 scale 来减少存储需求和计算开销。此外,我们可以将 MX Format 视为一种不带 shift 的量化方法。量化是一种将连续或高精度数据转换为低精度表示的技术,通常用于减少模型大小和加速推理过程。MX Format 通过引入 block size k 来定义量化的粒度,即每个 block 中的 element 数量。 diff --git a/02Hardware/05Abroad/04TPUIntrol.md b/02Hardware/05Abroad/04TPUIntrol.md index 14dbb4c7..11b73487 100644 --- a/02Hardware/05Abroad/04TPUIntrol.md +++ b/02Hardware/05Abroad/04TPUIntrol.md @@ -14,13 +14,13 @@ 但是在 2013 年,风向突变,当时谷歌的研究人员做出预测:如果人们每天使用语音搜索并通过深度神经网络(DNN)进行 3 分钟的语音识别,那么当时谷歌的数据中心需要双倍的算力才能满足日益增长的计算需求,而仅仅依靠传统 CPU 来满足这种需求是非常昂贵的。于是,在这个背景下,谷歌开始了 TPU 的设计。 -通常一个芯片的开发需要几年的时间,然而谷歌不愧是谷歌,TPU 从立项到大规模部署只用了 15 个月。TPU 课程的领头人 Norm Jouppi 说到:“我们的芯片设计过程异常迅速,这本身就是一项非凡的成就。令人惊叹的是,我们首批交付的硅片无需进行任何错误修正或掩模的更改。考虑到在整个芯片构建过程中,我们还在同步进行团队的组建,紧接着迅速招募 RTL(寄存器传输级)设计专家,并且急切地补充设计验证团队,整个工作节奏非常紧张。” +通常一个芯片的开发需要几年的时间,然而谷歌不愧是谷歌,TPU 从立项到大规模部署只用了 15 个月。TPU 课程的领头人 Norm Jouppi 说到:“芯片设计过程异常迅速,这本身就是一项非凡的成就。令人惊叹的是,我们首批交付的硅片无需进行任何错误修正或掩模的更改。考虑到在整个芯片构建过程中,我们还在同步进行团队的组建,紧接着迅速招募 RTL(寄存器传输级)设计专家,并且急切地补充设计验证团队,整个工作节奏非常紧张。” ## TPU 芯片与产品 ### 历代 TPU 芯片 -以下表格是不同 TPU 芯片型号的具体参数和规格,我们的 TPU 系列会主要围绕 v1, v2, v3, v4 这一系统去展开。 +以下表格是不同 TPU 芯片型号的具体参数和规格,TPU 系列会主要围绕 v1, v2, v3, v4 这一系统去展开。 | TPU 比较 | TPUv1 | TPUv2 | TPUv3 | Edge TPU v1 | Pixel Neural Core | TPUv4i | TPUv4 | 谷歌 Tensor | |------------------------|-------------|-------------|-------------|-------------|-------------------|-------------|-------------|---------------| diff --git a/02Hardware/05Abroad/05TPU1.md b/02Hardware/05Abroad/05TPU1.md index caac684f..e9494650 100644 --- a/02Hardware/05Abroad/05TPU1.md +++ b/02Hardware/05Abroad/05TPU1.md @@ -38,9 +38,9 @@ ### 脉动阵列简介 -脉冲阵列就是 TPU 的核心,也是本节最重要的内容。脉冲阵列的英文名 Systolic Array 就是源于它处理数据的节奏就像我们的心跳一样,于是便有了这个名字。 +脉冲阵列就是 TPU 的核心,也是本节最重要的内容。脉冲阵列的英文名 Systolic Array 就是源于它处理数据的节奏就像心跳一样,于是便有了这个名字。 -在我们讲到具体的实现之前,我们需要先回顾一下 Img2Col 这个算法。我们知道,在推理场景,在 2017 年附近,卷积神经网络占据了当时场景的半壁江山。在卷积计算的时候,我们实际上不会对真正地对图片或者 feature map 进行卷积,而是会用 Img2Col 的方式把图片变成矩阵,把我们的卷积换成矩阵相乘的方式。在我们之前推理系统里面我们讲过算法是怎么把卷积操作变成在数学上和卷积相同的矩阵乘法操作,再通过 Col2Img 返回来把我们的计算结果变成 feature map。而自然而然,其中计算压力最大的部分便是我刚才提到的“矩阵乘法”操作。 +在我们讲到具体的实现之前,我们需要先回顾一下 Img2Col 这个算法。我们知道,在推理场景,在 2017 年附近,卷积神经网络占据了当时场景的半壁江山。在卷积计算的时候,我们实际上不会对真正地对图片或者 feature map 进行卷积,而是会用 Img2Col 的方式把图片变成矩阵,把卷积换成矩阵相乘的方式。在我们之前推理系统里面我们讲过算法是怎么把卷积操作变成在数学上和卷积相同的矩阵乘法操作,再通过 Col2Img 返回来把计算结果变成 feature map。而自然而然,其中计算压力最大的部分便是我刚才提到的“矩阵乘法”操作。 ![Alt text](images/05TPU103.png) @@ -52,7 +52,7 @@ 下图是一个简单的图解,数据一波一波根据 FIFO(图最左边的蓝色方块)流入 MXU 进行计算,计算出来的结果会被存放在下方的寄存器中进行累加和输出。整个过程就像是心脏泵血,每一个时钟周期都会进行一次庞大的计算,并流入到下一个需要这个结果的地方。 -那么在这里需要注意的是, 因为 TPU 在一个时钟周期可以进行 $256 \times 256 = 65536$ 次计算,每次参与计算的逻辑单元又被串联在一起从而实现了计算结果的复用,这使得 TPU 能够在更少次访问芯片内存的情况下完成更高的计算,降低了内存和芯片带宽的压力,从而让 TPU 的能耗比在同时期达到了领先的状态。 +那么在这里需要注意的是,因为 TPU 在一个时钟周期可以进行 $256 \times 256 = 65536$ 次计算,每次参与计算的逻辑单元又被串联在一起从而实现了计算结果的复用,这使得 TPU 能够在更少次访问芯片内存的情况下完成更高的计算,降低了内存和芯片带宽的压力,从而让 TPU 的能耗比在同时期达到了领先的状态。 ![Alt text](images/05TPU105.png) @@ -60,13 +60,13 @@ 终于讲到了脉动阵列的具体原理,或者说,为什么这种架构相比于传统的数据计算方式有这么大的优势。 -下图中 M(Memory)我们可以理解为芯片上的寄存器,PE(Process Elements)则可以理解为进行数据计算的单元。 +下图中 M(Memory)我们可以理解为芯片上的寄存器,PE(Process Elements)则可以理解为进行数据计算的单元。 传统的计算方式(左图)是数据每计算一次就要存储一次,而下一次要调取计算结果的时候也要从存储器里面重新获得这个数据,往复循环。那么在脉动结构中,单一 PE 被替换成了一串 PE。数据在经手所有 PE 计算之后才会被存储,由于矩阵加乘计算需要大量的数据复用,这种数据计算流程大量地减少了数据被访问的次数,从而实现了更高的效率。 ![Alt text](images/05TPU106.png) -那么有了以上先验知识,我们就以两个 $3 \times 3$ 的矩阵,下图中的矩阵 A 和矩阵 B,去上手一下我们的脉动阵列。首先我们要注意的就是脉动阵列数据的排序,相比于“矩形”的矩阵,数据被人工地进行了错位,以阶梯状输入阵列。可以仔细观察,矩阵 A 和矩阵 B 在不同的行列维度上被分开,原因是矩阵乘法需要 A 的每一行去点乘 B 的每一列,因此有了这种设计。可能在这里会有点一头雾水,但是只要仔细看了下面的每一步的拆解,我相信当你重新读这段话的时候会更容易理解。 +那么有了以上先验知识,我们就以两个 $3 \times 3$ 的矩阵,下图中的矩阵 A 和矩阵 B,去上手一下脉动阵列。首先我们要注意的就是脉动阵列数据的排序,相比于“矩形”的矩阵,数据被人工地进行了错位,以阶梯状输入阵列。可以仔细观察,矩阵 A 和矩阵 B 在不同的行列维度上被分开,原因是矩阵乘法需要 A 的每一行去点乘 B 的每一列,因此有了这种设计。可能在这里会有点一头雾水,但是只要仔细看了下面的每一步的拆解,我相信当你重新读这段话的时候会更容易理解。 ![Alt text](images/05TPU107.png) diff --git a/02Hardware/05Abroad/07TPU3.md b/02Hardware/05Abroad/07TPU3.md index 499ffce2..fe253e55 100644 --- a/02Hardware/05Abroad/07TPU3.md +++ b/02Hardware/05Abroad/07TPU3.md @@ -33,7 +33,7 @@ ### 分布式架构 - 参数服务器 -涉及到集群,我们在训练过程中就需要一个分布式的架构,在当时叫做参数服务器(Parameter Server)。在我们的训练过程中,我们需要在正向传播和反向传播中得到损失值和相应的梯度,而这个计算的压力是分布在每一张计算卡上的,因此在计算结束后需要把从每一张卡的计算结果进行梯度聚合,最后一步再进行参数的更新和参数的重新广播。 +涉及到集群,我们在训练过程中就需要一个分布式的架构,在当时叫做参数服务器(Parameter Server)。在训练过程中,我们需要在正向传播和反向传播中得到损失值和相应的梯度,而这个计算的压力是分布在每一张计算卡上的,因此在计算结束后需要把从每一张卡的计算结果进行梯度聚合,最后一步再进行参数的更新和参数的重新广播。 那么这个过程可以用同步或者异步的方式进行同步: @@ -79,7 +79,7 @@ - A 和 D:CPU 机架 -- B 和 C: TPU v2 机架 +- B 和 C:TPU v2 机架 - 蓝色框:电源管理系统(UPS) @@ -115,7 +115,7 @@ ### 虚拟架构图 -下面是我们的虚拟架构图,整体的架构图也是比较明显的。AI 框架通过 RPC 远程连接到 TPU Host,基于 CPU 去控制 TPU 去实现真正的互联运作执行。 +下面是虚拟架构图,整体的架构图也是比较明显的。AI 框架通过 RPC 远程连接到 TPU Host,基于 CPU 去控制 TPU 去实现真正的互联运作执行。 ![Alt text](images/07TPU306.png) diff --git a/02Hardware/05Abroad/08TPU4.md b/02Hardware/05Abroad/08TPU4.md index 16655b0e..58f5f411 100644 --- a/02Hardware/05Abroad/08TPU4.md +++ b/02Hardware/05Abroad/08TPU4.md @@ -77,7 +77,7 @@ TPU v4 Pod 提供了 exaflops 的算力,原因就是因为其强大的芯片 讲完 TPU v4 的基本结构之后,我们来看一下在当时谷歌的竞品是什么一个状态。英伟达 DGX SuperPod 搭配第四代 NVLink/NVSwitch 最多可以连接 32 个 node 总 256 颗 H100 芯片,并实现每颗 GPU 900G/s 的互连带宽。NV 每机架 4 台 DGX(共 32 颗 H100 GPU),机架内/外需要光纤连接。NV 的每机架算力密度相对更小/更窄,且需要更多的收发激光器和光纤线材,网络成本高。如果 NV 部署 4096 颗 GPU 集群,必须切分成更多个 SuperPod 并独立规划互连网络层,中间完成多层交换,集群内总计需要采购大约 568 个 Infiniband Switch。 -反观 TPU v4 Pod, 与超级计算机一样,工作负载由不同规模的算力承担,称为切片:64 芯片、128 芯片、256 芯片等。与 Infiniband 相比,OCS 的成本更低、功耗更低、速度更快,成本不到系统成本的 5%,功率不到系统功率的 3%。每个 TPU v4 都包含 SparseCores 数据流处理器,可将依赖嵌入的模型加速 5 至 7 倍,但仅使用 5% 的裸片面积和功耗。 +反观 TPU v4 Pod,与超级计算机一样,工作负载由不同规模的算力承担,称为切片:64 芯片、128 芯片、256 芯片等。与 Infiniband 相比,OCS 的成本更低、功耗更低、速度更快,成本不到系统成本的 5%,功率不到系统功率的 3%。每个 TPU v4 都包含 SparseCores 数据流处理器,可将依赖嵌入的模型加速 5 至 7 倍,但仅使用 5% 的裸片面积和功耗。 ## 光路交换机 @@ -121,7 +121,7 @@ TPU v4 Pod 显示出了在成本、功耗和速度方面的优势,但是,尽 - 低时延:3D Tours 因其相邻节点之间的短而直接的连线,可以换来更低的延迟;尤其当节点间需要运行那种密集 I/O 的、紧耦合的并行任务时特别有用。 -- 低网络代价:对于相同数量的节点, 3D Torus 拓扑网络直径低于 Clos 拓扑,两者相比之下,前者的交换机/线材/连接器的保有量更低,网络层次更少,节省硬件成本。 +- 低网络代价:对于相同数量的节点,3D Torus 拓扑网络直径低于 Clos 拓扑,两者相比之下,前者的交换机/线材/连接器的保有量更低,网络层次更少,节省硬件成本。 - 路由可重配:谷歌 OCS 网络支持动态可重配路由,Silic 集群在部署之后可以立即投入生产,无需等待整个网络收敛;并且这种特性更容易隔离/下线故障节点。 diff --git a/02Hardware/06Domestic/04Cambricon.md b/02Hardware/06Domestic/04Cambricon.md index f397e777..007e005f 100644 --- a/02Hardware/06Domestic/04Cambricon.md +++ b/02Hardware/06Domestic/04Cambricon.md @@ -48,7 +48,7 @@ IP 终端 | 边缘端 | 云端推理 | 云端训练 ## 学术基础 -首先,在 2014 年左右,寒武纪公司的前身中科院计算所计算机体系结构国家重点实验室的智能处理器团队(现在的智能处理器研究中心),与国际合作者 Olivier Temam 一起发表了一篇名称为 DianNao([1]) 的论文,这篇论文指出当机器学习算法尤其是神经网络算法越来越普遍应用的情况下,使用专门的机器学习加速器或许可以既能提供提供优秀的性能又能支撑广泛的应用场景。要知道,在 2012 年 AlexNet 大火,刷新了 CV 领域的一些记录,在 2014 年左右,最为典型最为重要的 DNN 模型,也就是 CNN 了(卷积神经网络),因此这个工作也对 CNN 进行了一定的设计。在当时,虽然也有一些关于设计专用硬件来实现机器学习算法的论文,但这篇工作尤其强调加速器的存储设计、性能和功耗。此工作设计了一个性能为 452GOP/s,只有 3.02 平方毫米,485mW 功耗的高吞吐加速器,与一个 128 比特 2GHz 的 SIMD 处理器相比,它快 117.87 倍,可以把总功耗降低 21.08 倍,使用 65nm 工艺。在此工作中,重点关注神经网络的推理阶段而不是训练阶段,文章分析了一个简单的神经网络的访存模式,通过对输入输出做 tiling(切片)完成计算。分析了如果把整个神经网络的权值全部硬化在下来需要很高的成本,因此无法大规模应用,因此设计了自己的体系结构,一个小规模的加速器,它由以下几个部分组成:专门的输入神经元 Buffer(NBin),输出神经元 Buffer(NBout),存放权重的 buffer(SB),还有专门的计算单元 NFU(Neural Function Unit)以及控制单元 CP。此设计中的 NBin、NBout 等是 scratchpad memory(SPM,便笺式存储器),不同于一些国内外 GPGPU 方案中所使用的寄存器文件(register file),这个思想也一直影响着寒武纪的设计哲学。 +首先,在 2014 年左右,寒武纪公司的前身中科院计算所计算机体系结构国家重点实验室的智能处理器团队(现在的智能处理器研究中心),与国际合作者 Olivier Temam 一起发表了一篇名称为 DianNao([1])的论文,这篇论文指出当机器学习算法尤其是神经网络算法越来越普遍应用的情况下,使用专门的机器学习加速器或许可以既能提供提供优秀的性能又能支撑广泛的应用场景。要知道,在 2012 年 AlexNet 大火,刷新了 CV 领域的一些记录,在 2014 年左右,最为典型最为重要的 DNN 模型,也就是 CNN 了(卷积神经网络),因此这个工作也对 CNN 进行了一定的设计。在当时,虽然也有一些关于设计专用硬件来实现机器学习算法的论文,但这篇工作尤其强调加速器的存储设计、性能和功耗。此工作设计了一个性能为 452GOP/s,只有 3.02 平方毫米,485mW 功耗的高吞吐加速器,与一个 128 比特 2GHz 的 SIMD 处理器相比,它快 117.87 倍,可以把总功耗降低 21.08 倍,使用 65nm 工艺。在此工作中,重点关注神经网络的推理阶段而不是训练阶段,文章分析了一个简单的神经网络的访存模式,通过对输入输出做 tiling(切片)完成计算。分析了如果把整个神经网络的权值全部硬化在下来需要很高的成本,因此无法大规模应用,因此设计了自己的体系结构,一个小规模的加速器,它由以下几个部分组成:专门的输入神经元 Buffer(NBin),输出神经元 Buffer(NBout),存放权重的 buffer(SB),还有专门的计算单元 NFU(Neural Function Unit)以及控制单元 CP。此设计中的 NBin、NBout 等是 scratchpad memory(SPM,便笺式存储器),不同于一些国内外 GPGPU 方案中所使用的寄存器文件(register file),这个思想也一直影响着寒武纪的设计哲学。 之后计算所智能处理器团队还进一步发表了 DaDianNao([2])、ShiDianNao([3])、PuDianNao([4])等工作,它们有的在之前的基础上将芯片扩展到 multi chip 系统,支持训练与推理,有的专注于计算机视觉任务,有的专注于传统机器学习算法。Cambricon 指令集([5])这篇论文为了解决日新月异的算法难以全部通过硬化适配的问题,借鉴 RISC 的指令集的设计原则,通过将描述神经网络的复杂指令分解成更短,更简单的指令,来扩大加速器的应用范围。使用简单和短的指令来减少设计和验证的风险,以及译码逻辑的功耗和面积。这一思想也被后续应用到了寒武纪公司的产品中。 @@ -74,14 +74,14 @@ MLU370-X8 智能加速卡是全面升级的数据中心训推一体 AI 加速卡 MLU370-X8 通过 MLU-Link™高速网络,组建大规模训练集群,并实现芯片间互联。新一代 MLU-Link™,不仅支持板卡上 2 个思元 370 芯片间通过 MLU-Link™进行通讯,同时也可以通过 MLU-Link™桥接卡对外互联,板卡间 MLU-Link 互联双向总带宽为 200GB/s,满足大型 AI 模型训练的需要。 -关于芯粒技术,芯粒英文是 Chiplet,是指预先制造好、具有特定功能、可组合集成的晶片(Die),Chiplet 也有翻译为“小芯片”,中科院计算所韩银和等 2020 年时建议将 Chiplet 翻译为“芯粒”。在集成电路领域,我们的发展水平和国外存在差距,“卡脖子”成了很突出的问题。按摩尔定律去发展、去追赶是一条路,但也可以另辟蹊径。 +关于芯粒技术,芯粒英文是 Chiplet,是指预先制造好、具有特定功能、可组合集成的晶片(Die),Chiplet 也有翻译为“小芯片”,中科院计算所韩银和等 2020 年时建议将 Chiplet 翻译为“芯粒”。在集成电路领域,发展水平和国外存在差距,“卡脖子”成了很突出的问题。按摩尔定律去发展、去追赶是一条路,但也可以另辟蹊径。 芯粒集成就是这样的前沿技术。芯粒是指按特定功能进行分解的小芯片,芯粒集成技术则是把制程代际和功能不同的芯粒像搭积木一样组合形成一个芯片去使用。 ## MLU03 核心架构 寒武纪的 MLU 硬件是面向人工智能应用的领域专用处理器,针对人工智能算法的计算特性和访存特性,设计了高效的指令集、流水线、运算部件和访存部件。与通用处理器相比,MLU 硬件在处理人工智能任务时有更高的性能、灵活性和能效比。MLU 硬件针对人工智能中不同特征的访存数据流设计专用的数据通路和运算部件,实现了不同的数据流之间的隔离;同时向软件暴露了灵活的片上存储空间访问功能,提高了处理效率。 -寒武纪硬件的基本组成单元是 MLU Core(就是视频中所述的 IPU)。每个 MLU Core 是具备完整计算、IO 和控制功能的处理器核心,可以独立完成一个计算任务,也可以与其他 MLU Core 协作完成一个计算任务。每 4 个 MLU Core 核心构成一个 Cluster,在 MLUv02 以及后续架构中,每个 Cluster 内还会包含一个额外的 Memory Core 和一块被 Memory Core 和 4 个 MLU Core 共享的 SRAM(Shared RAM,共享存储单元)。Memory Core 不能执行向量和张量计算指令,只能用于 SRAM 与 DDR (Double Data Rate Synchronous Dynamic Random Access Memory,双倍速率同步动态随机存储器,DDR SDRAM 通常简称为 DDR) 和 MLU Core 之间的数据传输。 +寒武纪硬件的基本组成单元是 MLU Core(就是视频中所述的 IPU)。每个 MLU Core 是具备完整计算、IO 和控制功能的处理器核心,可以独立完成一个计算任务,也可以与其他 MLU Core 协作完成一个计算任务。每 4 个 MLU Core 核心构成一个 Cluster,在 MLUv02 以及后续架构中,每个 Cluster 内还会包含一个额外的 Memory Core 和一块被 Memory Core 和 4 个 MLU Core 共享的 SRAM(Shared RAM,共享存储单元)。Memory Core 不能执行向量和张量计算指令,只能用于 SRAM 与 DDR (Double Data Rate Synchronous Dynamic Random Access Memory,双倍速率同步动态随机存储器,DDR SDRAM 通常简称为 DDR)和 MLU Core 之间的数据传输。 下图中展示了 MLU03 的核心架构,MLU03 采用 4 个 IPU 和一个 MPU 组成一个 Cluster(实际上 MLU02 也是),IPU 上有大量的计算单元以及本地 scratchpad memory(NeuronRAM WeightRAM),MPU 上有 SharedRAM,相当于 GPU 的 shared memory。不同 Cluster 数量可以组成不同的产品形态(云端、边缘端、IP) @@ -101,7 +101,7 @@ Cambricon BANG 异构计算平台的核心组件是面向 MLU 硬件的编译器 Cambricon BANG C 在 C/C++ 语言的基础上,增加了 Cambricon BANG 异构并行编程模型必须的语法特性、计算原语、数据类型和内建变量支持。此外,Cambricon BANG C 针对异构编程环境的特点对 C/C++ 进行了简化,禁用了一些不适合异构编程环境的 C/C++ 特性。Cambricon BANG C 程序可以使用 CNGDB 进行调试,有关 CNGDB 的使用方法可以参考《寒武纪 CNGDB 用户手册》。 -Cambricon BANG C 语言整合了不同类型指令集和架构的计算单元,并支持寒武纪推出的云端、边缘端和终端设备。遵循 Cambricon BANG C 编程规范的应用程序,几乎可以无需修改直接运行在包含不同 MLU Core 数量、Cluster 数量的 MLU 硬件上。使用 Cambricon BANG C 编写的异构程序包括主机侧和设备侧的代码。其中,主机侧主要是借用 CNRT(Cambricon Runtime Library,寒武纪运行时库) 或者 CNDrv(Cambricon Driver API,寒武纪软件栈驱动接口) 提供的相关接口实现设备信息查询、设备选择、设备内存分配、任务队列创建、输入数据或参数准备、任务描述、Kernel 启动、输出获取等功能;而设备侧的入口函数就是 Kernel 函数,Kernel 函数中可以使用 Cambricon BANG C 面向设备侧编程时扩展的语法特性、计算原语、数据类型和内建变量等特性。 +Cambricon BANG C 语言整合了不同类型指令集和架构的计算单元,并支持寒武纪推出的云端、边缘端和终端设备。遵循 Cambricon BANG C 编程规范的应用程序,几乎可以无需修改直接运行在包含不同 MLU Core 数量、Cluster 数量的 MLU 硬件上。使用 Cambricon BANG C 编写的异构程序包括主机侧和设备侧的代码。其中,主机侧主要是借用 CNRT(Cambricon Runtime Library,寒武纪运行时库)或者 CNDrv(Cambricon Driver API,寒武纪软件栈驱动接口)提供的相关接口实现设备信息查询、设备选择、设备内存分配、任务队列创建、输入数据或参数准备、任务描述、Kernel 启动、输出获取等功能;而设备侧的入口函数就是 Kernel 函数,Kernel 函数中可以使用 Cambricon BANG C 面向设备侧编程时扩展的语法特性、计算原语、数据类型和内建变量等特性。 ### CNNL @@ -194,11 +194,11 @@ CNCL 提供了操作通信子的相关接口,用户可以管理(创建、销 确定通信实体: -首先在每个进程中定义一组通信子,并设置该通信子关联的 MLU 设备和设备队列。 每个进程需要调用初始化函数 cnclInitComms 对通信子进行初始化。 所有运行的进程内的通信子构成了一个通信域,该域内的每个通信子有一个唯一的序号(rank)对其进行标识。 +首先在每个进程中定义一组通信子,并设置该通信子关联的 MLU 设备和设备队列。每个进程需要调用初始化函数 cnclInitComms 对通信子进行初始化。所有运行的进程内的通信子构成了一个通信域,该域内的每个通信子有一个唯一的序号(rank)对其进行标识。 确定通信原语: -根据通信目的选择要使用的通信原语。 对通信域中的每一个通信实体,在进程中调用相应的通信原语接口完成通信操作。 在调用接口时,通过 rank 号来标识对应的通信实体。如果想用接收数据原位覆盖发送数据,发送/接收数据的地址需要满足对应通信原语的原位操作的特殊条件。 +根据通信目的选择要使用的通信原语。对通信域中的每一个通信实体,在进程中调用相应的通信原语接口完成通信操作。在调用接口时,通过 rank 号来标识对应的通信实体。如果想用接收数据原位覆盖发送数据,发送/接收数据的地址需要满足对应通信原语的原位操作的特殊条件。 判定通信完成: diff --git a/02Hardware/06Domestic/README.md b/02Hardware/06Domestic/README.md index 18de5768..3329cebc 100644 --- a/02Hardware/06Domestic/README.md +++ b/02Hardware/06Domestic/README.md @@ -14,8 +14,8 @@ | 国外 AI 芯片 | 02 壁仞 BR100 芯片架构 | [PPT](./02BR100Detail.pdf), [视频](https://www.bilibili.com/video/BV1G14y1275T)| | 国外 AI 芯片 | 03 燧原科技 AI 芯片 | [PPT](./03SuiyuanDTU.pdf), [视频](https://www.bilibili.com/video/BV15W4y1Z7Hj)| | 国外 AI 芯片 | 04 寒武纪 AI 芯片第一股 | [PPT](./04CambriconProduct.pdf), [视频](https://www.bilibili.com/video/BV1Y8411m7Cd)| -| 国外 AI 芯片 | 05 寒武纪 AI 芯片架构剖析(上) | [PPT](./05CambriconArch.pdf), [视频](https://www.bilibili.com/video/BV1op4y157Qf)| -| 国外 AI 芯片 | 06 寒武纪 AI 芯片架构剖析(下) | [PPT](./06CambriconArch.pdf), [视频](https://www.bilibili.com/video/BV1TV411j7Yx)| +| 国外 AI 芯片 | 05 寒武纪 AI 芯片架构剖析(上)| [PPT](./05CambriconArch.pdf), [视频](https://www.bilibili.com/video/BV1op4y157Qf)| +| 国外 AI 芯片 | 06 寒武纪 AI 芯片架构剖析(下)| [PPT](./06CambriconArch.pdf), [视频](https://www.bilibili.com/video/BV1TV411j7Yx)| ## 备注 diff --git a/02Hardware/07Thought/01Introduction.md b/02Hardware/07Thought/01Introduction.md index b4f33204..a44c9fb9 100644 --- a/02Hardware/07Thought/01Introduction.md +++ b/02Hardware/07Thought/01Introduction.md @@ -22,7 +22,7 @@ AI 芯片主要是为实现并行计算,从程序并行的角度出发,主 3. **多指令流单数据流(Multiple Instruction Single Data,MISD)**:这种架构中有多个处理器执行不同的指令流,但处理相同的数据流。每个处理器执行不同的操作,但是操作的数据相同。MISD 系统在实际应用中较为罕见。 -4. **多指令流多数据流(Multiple Instruction Multiple Data,MIMD)**:这种架构中有多个处理器执行不同的指令流,处理不同的数据流,每个处理器可以独立执行不同的任务,适用于多核处理器、分布式系统或者并行计算集群等场景。 +4. **多指令流多数据流(Multiple Instruction Multiple Data,MIMD)**:这种架构中有多个处理器执行不同的指令流,处理不同的数据流,每个处理器可以独立执行不同的任务,适用于多核处理器、分布式系统或者并行计算集群等场景。 SISD 系统中每个指令部件每次仅译码一条指令,而且在执行时仅为操作部件提供一份数据,此时支持串行计算,硬件并不支持并行计算,在时钟周期内 CPU 只能处理一个数据流。 @@ -56,7 +56,7 @@ NVIDIA GPU 架构围绕可扩展的多线程流式多处理器 (Streaming Multip > 训练(Training):在训练阶段,神经网络通过输入数据和对应的标签(ground truth)来学习模式和特征,以调整网络中的参数(如权重和偏置)以最小化损失函数。训练的目标是使神经网络能够准确地预测输出结果。训练阶段通常包括前向传播(forward propagation)计算输出,反向传播(backpropagation)计算梯度,并使用优化算法(如梯度下降)更新参数。 > -> 推理(Inference):在推理阶段,经过训练的神经网络被用于对新的未见数据进行预测或分类。在推理阶段,神经网络的参数保持不变,不再进行参数调整。推理阶段的目标是利用训练好的神经网络模型对输入数据进行预测,并生成输出结果。推理阶段通常只包括前向传播计算,不涉及反向传播或参数更新。 +> 推理(Inference):在推理阶段,经过训练的神经网络被用于对新的未见数据进行预测或分类。在推理阶段,神经网络的参数保持不变,不再进行参数调整。推理阶段的目标是利用训练好的神经网络模型对输入数据进行预测,并生成输出结果。推理阶段通常只包括前向传播计算,不涉及反向传播或参数更新。 无论是训练还是推理阶段都需要执行大量的矩阵乘计算。训练阶段中,矩阵乘法通常用于计算前向传播(输入数据与权重矩阵相乘,生成输出结果)和反向传播中的梯度传播(梯度与权重矩阵的转置相乘,计算参数的梯度)。在推理阶段中,矩阵乘法用于计算输入数据与训练好的权重矩阵之间的乘积,从而生成预测结果,该过程与训练阶段的前向传播过程类似。 diff --git a/02Hardware/07Thought/03SPMT.md b/02Hardware/07Thought/03SPMT.md index 87075062..c85abbe9 100644 --- a/02Hardware/07Thought/03SPMT.md +++ b/02Hardware/07Thought/03SPMT.md @@ -6,7 +6,7 @@ 从指令级别的执行方式来看,一共有三种不同的编程模型,串行(SISD)、数据并行(SIMD)和多线程(MIMD/SPMD): -- **SISD(Single Instruction, Single Data)**:程序按顺序执行,每条指令依次处理单个数据。这是传统的串行编程模型,适合于简单的顺序执行任务,如传统的单线程程序。 这种方式适合于简单的任务和小规模数据处理,但在处理大规模数据或需要高性能的情况下,串行编程效率较低。 +- **SISD(Single Instruction, Single Data)**:程序按顺序执行,每条指令依次处理单个数据。这是传统的串行编程模型,适合于简单的顺序执行任务,如传统的单线程程序。这种方式适合于简单的任务和小规模数据处理,但在处理大规模数据或需要高性能的情况下,串行编程效率较低。 - **SIMD(Single Instruction, Multiple Data)**:程序通过向量化或并行化指令来处理多个数据,每个处理单元独立执行相同的任务,但是处理不同的数据。程序员可以编写单一指令,但该指令会同时应用于多个数据元素。这种模型适合于需要高度并行化处理的任务,如图像处理或科学计算。 diff --git a/02Hardware/07Thought/06AIChip.md b/02Hardware/07Thought/06AIChip.md index ebc839b1..cc87bbaa 100644 --- a/02Hardware/07Thought/06AIChip.md +++ b/02Hardware/07Thought/06AIChip.md @@ -74,7 +74,7 @@ DSA 难点在于既要对模型进行针对性的优化,同时还须保持一 ## 半导体供应链的选型 -计算逻辑的进步速度很快,但是芯片布线(制程工艺)的发展速度则较慢。 SRAM 和 HBM 比 DDR4 和 GDDR6 速度更快,能效更高,因此 AI 芯片需要根据数据的操作格式选用一定的存储设备。在大模型训练过程中普遍使用 BF16,部分会使用 FP8 进行推理,如果选型有问题,比如只能用 FP32 模拟 BF16,将减慢大模型训练迭代的速度。 +计算逻辑的进步速度很快,但是芯片布线(制程工艺)的发展速度则较慢。SRAM 和 HBM 比 DDR4 和 GDDR6 速度更快,能效更高,因此 AI 芯片需要根据数据的操作格式选用一定的存储设备。在大模型训练过程中普遍使用 BF16,部分会使用 FP8 进行推理,如果选型有问题,比如只能用 FP32 模拟 BF16,将减慢大模型训练迭代的速度。 ![芯片制程与存储类型的匹配](images/06AIChip12.png) @@ -92,7 +92,7 @@ DSA 的编译器需要对 AI 模型进行分析和优化,通过编译器把 AI - 向量、矩阵、张量等功能单元的数据级并行; -- 322~400 位 VLIW(Very Long Instruction Word) 指令集的指令级并行,一条指令可以同时包含多个操作,这些操作可以在同一时钟周期内并行执行; +- 322~400 位 VLIW(Very Long Instruction Word)指令集的指令级并行,一条指令可以同时包含多个操作,这些操作可以在同一时钟周期内并行执行; - 编译优化取决于软硬件能否进行缓存,编译器需要管理内存传输; diff --git a/03Compiler/01Tradition/01Introduction.md b/03Compiler/01Tradition/01Introduction.md index 28755892..432888de 100644 --- a/03Compiler/01Tradition/01Introduction.md +++ b/03Compiler/01Tradition/01Introduction.md @@ -45,7 +45,7 @@ 两者最大的差别在于编译器将一个程序作为一个整体进行翻译,而解释器则一条一条地翻译一个程序。编译器的情况下生成中间代码或目标代码,而解释器不创建中间代码。在执行效率上,编译器比解释器要快得多,因为编译器一次完成整个程序,而解释器则是依次编译每一行代码,非常的耗时。从资源占用方面来看,由于要生成目标代码,编译器比解释器需要更多的内存。 -实际上编程的体验差异也非常大,编译器同时显示所有错误,很难检测错误,而解释器则逐个显示每条语句的错误,更容易检测错误。具体的,在编译器中,当程序中出现错误时,它会停止翻译,并在删除错误后重新翻译整个程序。相反,当解释器中发生错误时,它会阻止其翻译,在删除错误后,翻译才继续执行。 +实际上编程的体验差异也非常大,编译器同时显示所有错误,很难检测错误,而解释器则逐个显示每条语句的错误,更容易检测错误。具体的,在编译器中,当程序中出现错误时,它会停止翻译,并在删除错误后重新翻译整个程序。相反,当解释器中发生错误时,它会阻止其翻译,在删除错误后,翻译才继续执行。 ## JIT 和 AOT 编译方式 diff --git a/03Compiler/01Tradition/02History.md b/03Compiler/01Tradition/02History.md index c2bf4040..280c09f1 100644 --- a/03Compiler/01Tradition/02History.md +++ b/03Compiler/01Tradition/02History.md @@ -186,7 +186,7 @@ GCC 原本使用 C 开发,后来因为 LLVM、 Clang 的崛起,令 GCC 更 由于 GCC 已成为 GNU 系统的官方编译器(包括 GNU/Linux 家族),它也成为编译与创建其他操作系统的主要编译器,包括 BSD 家族、Mac OS X、NeXTSTEP 与 BeOS。 -GCC 通常是跨平台软件的编译器首选。有别于一般局限于特定系统与运行环境的编译器,GCC 在所有平台上都使用同一个前端处理程序,产生一样的中介码,因此此中介码在各个其他平台上使用 GCC 编译,有很大的机会可得到正确无误的输出程序。 +GCC 通常是跨平台软件的编译器首选。有别于一般局限于特定系统与运行环境的编译器,GCC 在所有平台上都使用同一个前端处理程序,产生一样的中介码,因此此中介码在各个其他平台上使用 GCC 编译,有很大的机会可得到正确无误的输出程序。 - 总结 @@ -198,7 +198,7 @@ GNU 计划本来是为了开发一个自由系统来取代 UNIX 的,但是由 Clang 课程在 2005 年由苹果电脑发起,是 LLVM 编译器工具集的前端(front-end),目的是输出代码对应的抽象语法树(Abstract Syntax Tree, AST),并将代码编译成 LLVM Bitcode。接着在后端(back-end)使用 LLVM 编译成平台相关的机器语言。它的目标是提供一个 GNU 编译器套装(GCC)的替代品。Clang 课程包括 Clang 前端和 Clang 静态分析器等。 -Clang 本身性能优异,其生成的 AST 所耗用掉的内存仅仅是 GCC 的 20% 左右。FreeBSD 10 将 Clang/LLVM 作为默认编译器。测试证明 Clang 编译 Objective-C 代码时速度为 GCC 的 3 倍,还能针对用户发生的编译错误准确地给出建议。 +Clang 本身性能优异,其生成的 AST 所耗用掉的内存仅仅是 GCC 的 20% 左右。FreeBSD 10 将 Clang/LLVM 作为默认编译器。测试证明 Clang 编译 Objective-C 代码时速度为 GCC 的 3 倍,还能针对用户发生的编译错误准确地给出建议。 - Clang 历史 @@ -208,7 +208,7 @@ Apple 吸收 Chris Lattner 的目的要比改进 GCC 代码优化宏大得多, ![苹果开发 LLVM 与 Clang](images/02History09.png) -正像名字所写的那样,Clang 只支持 C,C++ 和 Objective-C 三种 C 家族语言。2007 年开始开发,C 编译器最早完成,而由于 Objective-C 相对简单,只是 C 语言的一个简单扩展,很多情况下甚至可以等价地改写为 C 语言对 Objective-C 运行库的函数调用,因此在 2009 年时,已经完全可以用于生产环境。C++ 的支持也热火朝天地进行着。 +正像名字所写的那样,Clang 只支持 C,C++ 和 Objective-C 三种 C 家族语言。2007 年开始开发,C 编译器最早完成,而由于 Objective-C 相对简单,只是 C 语言的一个简单扩展,很多情况下甚至可以等价地改写为 C 语言对 Objective-C 运行库的函数调用,因此在 2009 年时,已经完全可以用于生产环境。C++ 的支持也热火朝天地进行着。 - 总结 diff --git a/03Compiler/01Tradition/03GCC.md b/03Compiler/01Tradition/03GCC.md index b5886561..248f36b6 100644 --- a/03Compiler/01Tradition/03GCC.md +++ b/03Compiler/01Tradition/03GCC.md @@ -30,7 +30,7 @@ GCC 的编译过程可以大致分为预处理、编译、汇编和链接四个 ### 源程序(文本) -当编写源程序时,通常会使用以 .c 或 .cpp 为扩展名的文件。下面以打印宏定义 HELLOWORD 为例,我们使用 C 语言编写 hello.c 源文件: +当编写源程序时,通常会使用以 .c 或 .cpp 为扩展名的文件。下面以打印宏定义 HELLOWORD 为例,我们使用 C 语言编写 hello.c 源文件: ```c #include @@ -51,7 +51,7 @@ int main(void){ gcc -E hello.c -o hello.i ``` -在预处理过程中,源代码会被读入,并检查其中包含的预处理指令和宏定义,然后进行相应的替换操作。此外,预处理过程还会删除程序中的注释和多余空白字符。最终生成的.i 文件包含了经过预处理后的代码内容。 +在预处理过程中,源代码会被读入,并检查其中包含的预处理指令和宏定义,然后进行相应的替换操作。此外,预处理过程还会删除程序中的注释和多余空白字符。最终生成的.i 文件包含了经过预处理后的代码内容。 当高级语言代码经过预处理生成.i 文件时,预处理过程会涉及宏替换、条件编译等操作。以下是对这些预处理操作的解释: @@ -145,7 +145,7 @@ L_.str: ## @.str 在这一步中,我们将汇编代码转换成机器指令。这一步是通过汇编器(as)完成的。汇编器是 GCC 的后端,其主要功能是将汇编代码转换成机器指令。 -汇编器的工作是将人类可读的汇编代码转换为机器指令或二进制码,生成一个可重定位的目标程序,通常以 .o 作为文件扩展名。这个目标文件包含了逐行转换后的机器码,以二进制形式存储。这种可重定位的目标程序为后续的链接和执行提供了基础,使得我们的汇编代码能够被计算机直接执行。 +汇编器的工作是将人类可读的汇编代码转换为机器指令或二进制码,生成一个可重定位的目标程序,通常以 .o 作为文件扩展名。这个目标文件包含了逐行转换后的机器码,以二进制形式存储。这种可重定位的目标程序为后续的链接和执行提供了基础,使得汇编代码能够被计算机直接执行。 生成文件 hello.o diff --git a/03Compiler/01Tradition/05LLVMIR.md b/03Compiler/01Tradition/05LLVMIR.md index 436d5ad2..d39829d9 100644 --- a/03Compiler/01Tradition/05LLVMIR.md +++ b/03Compiler/01Tradition/05LLVMIR.md @@ -87,7 +87,7 @@ int main(void) } ``` -接下来我们使用 Clang 编译器将 C 语言源文件 test.c 编译成 LLVM 格式的中间代码。具体参数的含义如下: +接下来我们使用 Clang 编译器将 C 语言源文件 test.c 编译成 LLVM 格式的中间代码。具体参数的含义如下: - clang:Clang 编译器 - -S:生成汇编代码而非目标文件 diff --git a/03Compiler/01Tradition/06LLVMDetail.md b/03Compiler/01Tradition/06LLVMDetail.md index 705f5bd8..61b3b38f 100644 --- a/03Compiler/01Tradition/06LLVMDetail.md +++ b/03Compiler/01Tradition/06LLVMDetail.md @@ -152,7 +152,7 @@ ret i32 %0 ret i32 %1 ``` -静态单赋值好处: +静态单赋值好处: 1. 每个值都由单一的赋值操作定义,这使得我们可以轻松地从值的使用点直接追溯到其定义的指令。这种特性极大地方便了编译器进行正向和反向的编译过程。 diff --git a/03Compiler/01Tradition/07LLVMFrontend.md b/03Compiler/01Tradition/07LLVMFrontend.md index cbf15bd3..4483fc61 100644 --- a/03Compiler/01Tradition/07LLVMFrontend.md +++ b/03Compiler/01Tradition/07LLVMFrontend.md @@ -44,7 +44,7 @@ int main() { } ``` -对 hello.c 文件进行词法分析,执行以下代码: +对 hello.c 文件进行词法分析,执行以下代码: ```shell clang -cc1 -dump-tokens hello.c @@ -235,7 +235,7 @@ LLVM 优化层在输入的时候是一个 AST 语法树,输出的时候已经 分析 Pass 用于收集信息和了解程序的行为,而转换 Pass 则用于修改程序以实现优化或修改功能。在 LLVM 中,这两种 Pass 通常结合使用,以实现对程序进行全面优化和改进。 -优化过程需要执行以下代码: +优化过程需要执行以下代码: 首先我们需要生成 hello.bc 文件: diff --git a/03Compiler/01Tradition/08LLVMBackend.md b/03Compiler/01Tradition/08LLVMBackend.md index dc0e64cc..c7c41062 100644 --- a/03Compiler/01Tradition/08LLVMBackend.md +++ b/03Compiler/01Tradition/08LLVMBackend.md @@ -112,7 +112,7 @@ Code Emission(代码生成)是 LLVM 后端的重要阶段,其目标是将 - **循环展开(Loop Unrolling)**:通过展开循环体,减少循环控制开销,提高指令流水线效率。 - **循环交换(Loop Exchange)**:调整嵌套循环的顺序,提高数据局部性。 - - **循环合并(Loop Fusion)**:将多个循环合并为一个循环,减少循环开销。 这些优化在 LLVM 的循环优化器(Loop Optimizer)中实现,优化后的循环结构会在代码生成阶段进一步优化。 + - **循环合并(Loop Fusion)**:将多个循环合并为一个循环,减少循环开销。这些优化在 LLVM 的循环优化器(Loop Optimizer)中实现,优化后的循环结构会在代码生成阶段进一步优化。 #### 代码输出的实现 diff --git a/03Compiler/02AICompiler/02Stage.md b/03Compiler/02AICompiler/02Stage.md index f6278828..48f71c56 100644 --- a/03Compiler/02AICompiler/02Stage.md +++ b/03Compiler/02AICompiler/02Stage.md @@ -148,7 +148,7 @@ Buffer Fusion 是一种优化技术,它通过合并多个连续的内存访问 在阶段二专用 AI 编译器之后,就是 AI 编译器发展阶段三的到来。阶段三代表着通用 AI 编译器的重要发展阶段。阶段三的通用 AI 编译器代表了 AI 编译器技术的进一步成熟和进步。目前工业界的发展还处于 AI 编译器发展阶段的阶段二,也就是专用 AI 编译器的发展阶段。 -但展望未来,阶段三的通用 AI 编译器将带来一系列创新特点,成为我们的发展目标: +但展望未来,阶段三的通用 AI 编译器将带来一系列创新特点,成为发展目标: * 统一表达:在阶段三,通用 AI 编译器将实现计算图(Graph)和算子(Operators)的统一表达。这一创新意味着编译器能够在统一的框架下,同时进行图级别的优化和算子级别的优化,提升了编译过程的效率和效果。 @@ -167,7 +167,7 @@ Buffer Fusion 是一种优化技术,它通过合并多个连续的内存访问 * 针对神经网络,泛化优化能力。 * 模块化表示和组合,提升可用性。 -======= 对 AI 编译器历史的三阶段,分三句话分别总结。 - 阶段 1 朴素 AI 编译器:xxxx。 +======= 对 AI 编译器历史的三阶段,分三句话分别总结。- 阶段 1 朴素 AI 编译器:xxxx。 ## 本节视频 diff --git a/03Compiler/02AICompiler/03Architecture.md b/03Compiler/02AICompiler/03Architecture.md index 827055ae..ff588cf6 100644 --- a/03Compiler/02AICompiler/03Architecture.md +++ b/03Compiler/02AICompiler/03Architecture.md @@ -32,7 +32,7 @@ Ops Optimizer 接收到 Tensor IR 后,其会针对每个算子进行具体的 编译器前端(Compiler Frontend)主要负责接收和处理来自不同深度学习框架的模型,并将其转换为通用的中间表示(IR),进行初步优化。 -编译器前端的组成集中展示在上图中间靠左部分。输入的神经网络模型格式可以来自多种框架(如TensorFlow、PyTorch等);这些模型通过符号表示的转换(如TVM、nGraph等)生成计算图;高级IR/图IR(设备无关)包含了DAG(有向无环图)和基于let-binding的表示形式以及张量计算;计算图经过多种优化,如代数简化、操作融合、操作下沉、公共子表达式消除(CSE)、死代码消除(DCE)和静态内存规划等,得到初步优化的计算图;随后通过模式匹配和图重写等方法进一步优化,最终生成优化后的计算图;同时,编译器前端也提供了调试工具(如IR dumping)可以以文本或DAG形式呈现。 +编译器前端的组成集中展示在上图中间靠左部分。输入的神经网络模型格式可以来自多种框架(如 TensorFlow、PyTorch 等);这些模型通过符号表示的转换(如 TVM、nGraph 等)生成计算图;高级 IR/图 IR(设备无关)包含了 DAG(有向无环图)和基于 let-binding 的表示形式以及张量计算;计算图经过多种优化,如代数简化、操作融合、操作下沉、公共子表达式消除(CSE)、死代码消除(DCE)和静态内存规划等,得到初步优化的计算图;随后通过模式匹配和图重写等方法进一步优化,最终生成优化后的计算图;同时,编译器前端也提供了调试工具(如 IR dumping)可以以文本或 DAG 形式呈现。 **Input Format of DL Models(输入格式)**:支持多种深度学习框架,如 TensorFlow、PyTorch、Caffe2、MXNet、飞桨(PaddlePaddle)和 ONNX 等。 @@ -52,7 +52,7 @@ Ops Optimizer 接收到 Tensor IR 后,其会针对每个算子进行具体的 编译器后端(Compiler Backend)负责将优化后的计算图转换为特定硬件平台的低层次表示,并进行硬件特定优化和代码生成。 -编译器后端的组成集中展示再上图中间靠右部分。首先进行硬件特定的优化,包括内在映射、内存分配、内存延迟隐藏、循环优化、并行化等;这些优化通过自动调度(如多面体模型)和手动调度(如Halide)进行;自动调优模块包含参数化、成本模型和参数搜索,旨在进一步优化性能;后端还利用内核库(如Intel DNNL、NV cuDNN/TensorRT、AMD MIOpen等)来提高效率;低级IR/操作符IR(设备相关)采用Halide、多面体模型等独特的IR实现;编译方案支持即时(Just-In-Time)和提前(Ahead-Of-Time)编译;最终,代码生成模块将生成LLVM、CUDA、OpenCL、OpenGL等代码,以支持各种目标平台,包括CPU(X86、ARM、RISC-V)、GPU(NVIDIA、AMD)、ASIC(TPU、Inferentia、NNP等)和DSP等,从而适配越来越多的加速器。 +编译器后端的组成集中展示再上图中间靠右部分。首先进行硬件特定的优化,包括内在映射、内存分配、内存延迟隐藏、循环优化、并行化等;这些优化通过自动调度(如多面体模型)和手动调度(如 Halide)进行;自动调优模块包含参数化、成本模型和参数搜索,旨在进一步优化性能;后端还利用内核库(如 Intel DNNL、NV cuDNN/TensorRT、AMD MIOpen 等)来提高效率;低级 IR/操作符 IR(设备相关)采用 Halide、多面体模型等独特的 IR 实现;编译方案支持即时(Just-In-Time)和提前(Ahead-Of-Time)编译;最终,代码生成模块将生成 LLVM、CUDA、OpenCL、OpenGL 等代码,以支持各种目标平台,包括 CPU(X86、ARM、RISC-V)、GPU(NVIDIA、AMD)、ASIC(TPU、Inferentia、NNP 等)和 DSP 等,从而适配越来越多的加速器。 **Hardware Specific Optimizations(硬件特定优化)**:针对特定硬件的优化技术,包括:Intrinsic mapping(内在映射)、Memory allocation(内存分配)、Memory latency hiding(内存延迟隐藏)、Loop oriented optimization(面向循环的优化)以及 Parallelization(并行化)等。 @@ -172,15 +172,15 @@ DL 编译器(如 TVM、nGraph 和 TC)在代码生成阶段可以生成对这 ## 小结与思考 -**AI编译器通用架构**:分为编译器前端与编译器后端。编译器前端包含输入格式转换、高级IR/图IR、计算图优化方案以及调试工具等;编译器后端包含硬件特定优化、自动调度、自动调优、特定优化的内核库、低级IR/操作符IR、编译方案以及针对不同平台的代码生成等。 +**AI 编译器通用架构**:分为编译器前端与编译器后端。编译器前端包含输入格式转换、高级 IR/图 IR、计算图优化方案以及调试工具等;编译器后端包含硬件特定优化、自动调度、自动调优、特定优化的内核库、低级 IR/操作符 IR、编译方案以及针对不同平台的代码生成等。 -**中间表达IR**:分为高级IR和低级IR。 +**中间表达 IR**:分为高级 IR 和低级 IR。 **前端优化**:包括节点级优化、块级(窥孔、局部)优化以及数据流级(全局)优化。 **后端优化**:包括特定硬件优化、自动调整、Halide/TVM 方法、多面体模型参数调整方法以及内核库优化。 -**AI编译器全栈产品**:自底向上分为Hardware层(硬件层)、Kernel层(异构计算架构层)、Graph层(计算图层)和DL Models层(深度学习框架层)。 +**AI 编译器全栈产品**:自底向上分为 Hardware 层(硬件层)、Kernel 层(异构计算架构层)、Graph 层(计算图层)和 DL Models 层(深度学习框架层)。 ## 本节视频 diff --git a/03Compiler/02AICompiler/04Future.md b/03Compiler/02AICompiler/04Future.md index d862d090..6321cb73 100644 --- a/03Compiler/02AICompiler/04Future.md +++ b/03Compiler/02AICompiler/04Future.md @@ -193,11 +193,11 @@ Scale Out/Up 的最大挑战之一是效率墙,其解决依赖于复杂的切 ## 小结与思考 -**目前主流AI编译器**:TVM、XLA、nGraph、TC(Tensor Comprehensions)、Glow等。 +**目前主流 AI 编译器**:TVM、XLA、nGraph、TC(Tensor Comprehensions)、Glow 等。 -**AI编译器目前遇到的挑战**:AI编译器目前主要遇到五个挑战,分别是动态 Shape 问题、Python 编译静态化、发挥硬件性能、特殊优化方法以及易用性与性能兼顾问题。 +**AI 编译器目前遇到的挑战**:AI 编译器目前主要遇到五个挑战,分别是动态 Shape 问题、Python 编译静态化、发挥硬件性能、特殊优化方法以及易用性与性能兼顾问题。 -**AI编译器的未来**:未来的 AI 编译器将分为推理和训练两个阶段,采用 AOT(Ahead-of-Time)和 JIT(Just-in-Time)两种编译方式;将拥有一个统一的中间表达IR;能够实现自动并行编译优化;能够提供先进的自动微分功能,支持高阶微分计算方式;能实现自动化Kernel代码生成,降低开发门槛。 +**AI 编译器的未来**:未来的 AI 编译器将分为推理和训练两个阶段,采用 AOT(Ahead-of-Time)和 JIT(Just-in-Time)两种编译方式;将拥有一个统一的中间表达 IR;能够实现自动并行编译优化;能够提供先进的自动微分功能,支持高阶微分计算方式;能实现自动化 Kernel 代码生成,降低开发门槛。 **思考问题**:图算能否统一表达,统一编译优化,形成通用的 AI 编译器?完全的自动并行是否可行?AI 芯片需要编译器吗?AI 芯片需要 AI 编译器吗? diff --git a/03Compiler/03Frontend/03OPFusion.md b/03Compiler/03Frontend/03OPFusion.md index 809d5add..3b7fdacf 100644 --- a/03Compiler/03Frontend/03OPFusion.md +++ b/03Compiler/03Frontend/03OPFusion.md @@ -190,9 +190,9 @@ TVM 主要用于推理场景。在架构上,主要包括 Relay 和 TIR 两层 - 支配树:各个点的支配点构成的树 - 支配点:所有能够到达当前节点的路径的公共祖先点( Least Common Ancestors,LCA) -具体而言,对于一张有向图(可以有环)我们规定一个起点 $r$, 从 $r$ 点到图上另一个点 $w$ 可能存在很多条路径(下面将 $r$ 到 $w$ 简写为 $r→w$)。如果对于 $r→w$ 的任意一条路径中都存在一个点 $p$, 那么我们称点 $p$ 为 $w$ 的支配点(也可以称作是 $r→w$ 的必经点), 注意 $r$ 点不讨论支配点。 +具体而言,对于一张有向图(可以有环)我们规定一个起点 $r$,从 $r$ 点到图上另一个点 $w$ 可能存在很多条路径(下面将 $r$ 到 $w$ 简写为 $r→w$)。如果对于 $r→w$ 的任意一条路径中都存在一个点 $p$,那么我们称点 $p$ 为 $w$ 的支配点(也可以称作是 $r→w$ 的必经点),注意 $r$ 点不讨论支配点。 -下面用 $idom[u]$ 表示离点 $u$ 最近的支配点。对于原图上除 $r$ 外每一个点 $u$, 从 $idom[u]$ 向 $u$ 建一条边, 最后我们可以得到一个以 $r$ 为根的树。这个树我们就叫它"支配树"。 +下面用 $idom[u]$ 表示离点 $u$ 最近的支配点。对于原图上除 $r$ 外每一个点 $u$,从 $idom[u]$ 向 $u$ 建一条边,最后我们可以得到一个以 $r$ 为根的树。这个树我们就叫它"支配树"。 如下图所示,到达 Node8 的路径有 Node3->4->7->8,Node3->5->7->8,Node3->6->7->8,因此 Node4,Node5,Node6,Node7 为 Node8 的支配点。 diff --git a/03Compiler/03Frontend/04LayoutPrinc.md b/03Compiler/03Frontend/04LayoutPrinc.md index b4f33ff7..3acf9731 100644 --- a/03Compiler/03Frontend/04LayoutPrinc.md +++ b/03Compiler/03Frontend/04LayoutPrinc.md @@ -85,7 +85,7 @@ 如上图所示,这个例子中的图片分为红绿蓝三个通道,假设我们使用的是"NCHW"的数据排布方式,这里我们先将各方向的定义在这个图中详细说明,1 2 3 这个方向为 W,1 4 这个方向为 H,1 7 13 这个方向为 C,N 方向只有一个图片,暂时不讨论,"NCHW"的数据排布方式,是先取 W 方向的数据,即 123,再取 H 方向的数据,即 123 456,再取 C 方向的数据,即 123456 789/10/11/12 13/14/15/16/17/18,在计算机中存储时即为图示下侧的序列,简单地来说,也就是先在一个通道中,按照 W 方向/H 方向存储数据,接着再到剩余通道中按同样方式存储数据,其突出特点是同一个通道的数值连续排布,更适合需要对每个通道单独运算的操作,如 MaxPooling 最大池化操作,"NCHW"的计算时需要的存储更多,一次存储对应一个通道的数据,适合 GPU 运算,正好利用了 GPU 内存带宽较大并且并行性强的特点,其访存与计算的控制逻辑相对简单。 -如下图所示,按照"NCHW"的数据排布方式,我们的目标是计算灰度值,那么我们需要先将通道一的数据加载进内存,乘以 0.299,然后每次计算都可能需要加载一整个完整通道的数据,通道二所有数据值乘以 0.587,通道三所有数据值乘以 0.114,最后将三个通道结果相加得到灰度值,三个操作是独立的,可以在 GPU 上并行完成。 +如下图所示,按照"NCHW"的数据排布方式,目标是计算灰度值,那么我们需要先将通道一的数据加载进内存,乘以 0.299,然后每次计算都可能需要加载一整个完整通道的数据,通道二所有数据值乘以 0.587,通道三所有数据值乘以 0.114,最后将三个通道结果相加得到灰度值,三个操作是独立的,可以在 GPU 上并行完成。 ![NCHW 计算灰度值示意图](images/nchw02.png) @@ -97,7 +97,7 @@ ![NHWC 示意图](images/nhwc01.png) -如“Conv1x1”1x1 卷积操作:"NHWC"更适合多核 CPU 运算,CPU 的内存带宽相对较小,每个像素计算的时延较低,临时空间也很小,有时计算机采取异步的方式边读边算来减小访存时间,计算控制灵活且复杂。如下图所示,按照"NHWC"的数据排布方式,我们的目标是计算灰度值,假设我们现在有 3 个 cpu 的核,那么就可以通过 3 个核分别并行处理这三个通道的同一位置元素,最后进行累加得到灰度值。 +如“Conv1x1”1x1 卷积操作:"NHWC"更适合多核 CPU 运算,CPU 的内存带宽相对较小,每个像素计算的时延较低,临时空间也很小,有时计算机采取异步的方式边读边算来减小访存时间,计算控制灵活且复杂。如下图所示,按照"NHWC"的数据排布方式,目标是计算灰度值,假设我们现在有 3 个 cpu 的核,那么就可以通过 3 个核分别并行处理这三个通道的同一位置元素,最后进行累加得到灰度值。 ![NHWC 示意图](images/nhwc02.png) diff --git a/03Compiler/03Frontend/10algebraic.md b/03Compiler/03Frontend/10algebraic.md index f9e11fb4..43d1a866 100644 --- a/03Compiler/03Frontend/10algebraic.md +++ b/03Compiler/03Frontend/10algebraic.md @@ -12,7 +12,7 @@ ### 结合律 -非正式的讲,结合律是说: 不论我们怎样结合数字(即先计算那些数字),答案都是一样的。即: +非正式的讲,结合律是说:不论我们怎样结合数字(即先计算那些数字),答案都是一样的。即: $$ (a+b)+c = a+(b+c) diff --git a/03Compiler/03Frontend/srt/10.srt b/03Compiler/03Frontend/srt/10.srt index 054f982c..2a5e3e9b 100644 --- a/03Compiler/03Frontend/srt/10.srt +++ b/03Compiler/03Frontend/srt/10.srt @@ -116,7 +116,7 @@ AI 编译器前端的各种优化 30 00:01:03,600 --> 00:01:07,133 -但这并不意味着我们的计算图的优化 +但这并不意味着计算图的优化 31 00:01:07,133 --> 00:01:09,533 @@ -300,7 +300,7 @@ AI 编译器前端的各种优化 76 00:02:45,500 --> 00:02:46,866 -是我们的系统工程师 +是系统工程师 77 00:02:46,866 --> 00:02:49,299 @@ -324,11 +324,11 @@ AI 工程师去专门去做的 82 00:02:56,466 --> 00:02:58,399 -才能够更好的知道我们的子图 +才能够更好的知道子图 83 00:02:58,400 --> 00:03:00,700 -我们的计算图长成什么样子 +计算图长成什么样子 84 00:03:02,400 --> 00:03:04,800 @@ -420,7 +420,7 @@ a 和 b 进行卷积之后呢求逆 106 00:03:48,066 --> 00:03:50,066 -给我们的 c 进行一个相乘 +给 c 进行一个相乘 107 00:03:50,133 --> 00:03:51,066 @@ -544,7 +544,7 @@ a 乘以 c 加上 a 乘以 b 137 00:04:52,500 --> 00:04:55,933 -我现在呢 A GEMM B 就是我们的矩阵乘 +我现在呢 A GEMM B 就是矩阵乘 138 00:04:57,066 --> 00:04:58,966 @@ -940,11 +940,11 @@ Op2 这种对合算子呢 236 00:08:35,100 --> 00:08:36,700 -首先我们的标量只有一个数 +首先标量只有一个数 237 00:08:36,700 --> 00:08:38,366 -而我们的矩阵我们的张量呢 +而矩阵张量呢 238 00:08:38,366 --> 00:08:39,399 @@ -972,7 +972,7 @@ Op2 这种对合算子呢 244 00:08:49,200 --> 00:08:50,966 -之后呢才跟我们的 mat1 +之后呢才跟 mat1 245 00:08:51,133 --> 00:08:52,733 diff --git a/03Compiler/03Frontend/srt/11.srt b/03Compiler/03Frontend/srt/11.srt index 8cbe15f6..d16b714e 100644 --- a/03Compiler/03Frontend/srt/11.srt +++ b/03Compiler/03Frontend/srt/11.srt @@ -72,7 +72,7 @@ summary 里面主要讲啥内容呢 19 00:00:46,766 --> 00:00:48,466 -在我们的 AI 编译器 +在 AI 编译器 20 00:00:48,666 --> 00:00:51,066 @@ -84,7 +84,7 @@ summary 里面主要讲啥内容呢 22 00:00:54,933 --> 00:00:56,299 -我们的 AI 框架 + AI 框架 23 00:00:56,300 --> 00:00:58,400 @@ -100,7 +100,7 @@ summary 里面主要讲啥内容呢 26 00:01:01,100 --> 00:01:03,100 -传给我们的整个 AI 编译器呢 +传给整个 AI 编译器呢 27 00:01:03,100 --> 00:01:04,733 @@ -156,7 +156,7 @@ summary 里面主要讲啥内容呢 40 00:01:30,000 --> 00:01:32,333 -就跟我们的神经网络更相关的 +就跟神经网络更相关的 41 00:01:32,333 --> 00:01:35,066 @@ -304,7 +304,7 @@ memory allocation 这个内容 77 00:02:51,800 --> 00:02:53,666 -在我们的推理框架里面呢 +在推理框架里面呢 78 00:02:53,666 --> 00:02:56,199 @@ -372,7 +372,7 @@ memory allocation 这个内容 94 00:03:31,766 --> 00:03:33,733 -往我们的计算图上面去套 +往计算图上面去套 95 00:03:33,733 --> 00:03:35,933 @@ -412,7 +412,7 @@ memory allocation 这个内容 104 00:03:51,533 --> 00:03:54,266 -但是呢常量折叠确实给我们的性能 +但是呢常量折叠确实给性能 105 00:03:54,266 --> 00:03:55,866 @@ -488,7 +488,7 @@ memory allocation 这个内容 123 00:04:28,100 --> 00:04:30,266 -它消除的是我们的计算的节点 +它消除的是计算的节点 124 00:04:32,266 --> 00:04:34,499 diff --git a/03Compiler/04Backend/01Introduction.md b/03Compiler/04Backend/01Introduction.md index f9d96f80..731ec779 100644 --- a/03Compiler/04Backend/01Introduction.md +++ b/03Compiler/04Backend/01Introduction.md @@ -82,7 +82,7 @@ AI 编译器分为多层架构,最顶层由各种 AI 训练框架编写的神 - 如何应对 AI 领域算子迭代更新快:AI 领域的算法和模型经常迭代更新,导致算子库需要及时跟进以支持新的算法或模型结构。这可能需要算子库开发者不断更新和优化现有的算子实现,以适应新的需求。 -- 如何解决同一算子在多平台移植后一致性问题: 算子库通常是为特定硬件平台(如 GPU、CPU)进行优化设计的。但是,在将算子库移植到不同的平台上时,可能会遇到一致性问题。不同平台上的硬件架构和指令集可能存在差异,可能需要进行特定的优化和调整,以确保在多平台上实现一致的计算结果。 +- 如何解决同一算子在多平台移植后一致性问题:算子库通常是为特定硬件平台(如 GPU、CPU)进行优化设计的。但是,在将算子库移植到不同的平台上时,可能会遇到一致性问题。不同平台上的硬件架构和指令集可能存在差异,可能需要进行特定的优化和调整,以确保在多平台上实现一致的计算结果。 - 如何面对算子组合爆炸问题?如参数多样,融合大算子等:在 AI 计算中,经常会遇到大量算子的组合,例如复杂的模型结构或多阶段数据处理流程。这可能导致算子的组合爆炸问题,其中算子之间的参数和组合方式变得多样化和复杂化。 @@ -92,7 +92,7 @@ AI 编译器分为多层架构,最顶层由各种 AI 训练框架编写的神 目前有两种主流的自动生成算法: -- Auto Tuning: Auto Tuning 是一种通过自动搜索和优化参数组合来生成高效的 kernel 代码的方法。该方法通常基于启发式算法或机器学习技术,自动探索不同参数组合以找到最佳的性能配置。Auto Tuning 可以根据具体的硬件平台和任务特性,自动选择适当的优化策略,从而提高计算核心的性能和效率。 +- Auto Tuning:Auto Tuning 是一种通过自动搜索和优化参数组合来生成高效的 kernel 代码的方法。该方法通常基于启发式算法或机器学习技术,自动探索不同参数组合以找到最佳的性能配置。Auto Tuning 可以根据具体的硬件平台和任务特性,自动选择适当的优化策略,从而提高计算核心的性能和效率。 - Polyhedral:Polyhedral 方法是一种基于数学多面体理论的编译优化方法,用于描述循环嵌套的迭代空间和数据依赖关系,并生成高效的循环 kernel 代码。通过对循环迭代空间进行变换和重组,Polyhedral 方法可以实现循环并行化、内存局部性优化等优化,从而提高计算核心的性能和效率。 diff --git a/03Compiler/04Backend/03Optimization.md b/03Compiler/04Backend/03Optimization.md index cb3b2d3d..f6a2ae4e 100644 --- a/03Compiler/04Backend/03Optimization.md +++ b/03Compiler/04Backend/03Optimization.md @@ -240,7 +240,7 @@ Triton 的前端是基于 Python 实现的,这使得用户的学习成本大 - [jax-ml/jax-triton](https://github.com/jax-ml/jax-triton):JAX 是一个用于加速数值计算的 Python 库,使用 Triton 编写可以嵌入到 JAX 程序中的自定义 GPU 内核。在 JAX 中可以使用 triton_call 方便的调用 Triton kernel。 -- [PyTorch/inductor](https://github.com/pytorch/pytorch/tree/ab148da66cb9433effac90c7bd4930a961481d9/torch/_inductor/triton_ops):Inductor 在 Triton 的集成方面做得更加全面且务实。Inductor 一共包含三种使用 Triton 的方式, 针对非计算密集算子,基于 Inductor IR,实现了相对通用的[Codegen](https://link.zhihu.com/?target=https%3A//github.com/pytorch/pytorch/blob/ab148da66cb9433effac90c7bd4930a961481d19/torch/_inductor/codegen/triton.py%23L997)的支持。针对 GEMM,基于 Jinja2,通过模式匹配的方式实现了[半定制的 codegen](https://link.zhihu.com/?target=https%3A//github.com/pytorch/pytorch/blob/ab148da66cb9433effac90c7bd4930a961481d19/torch/_inductor/kernel/mm.py%23L27)。针对 Conv,调用[pre-baked Triton kernel](https://link.zhihu.com/?target=https%3A//github.com/pytorch/pytorch/blob/ab148da66cb9433effac90c7bd4930a961481d19/torch/_inductor/triton_ops/conv.py%23L14),没有提供定制能力。 +- [PyTorch/inductor](https://github.com/pytorch/pytorch/tree/ab148da66cb9433effac90c7bd4930a961481d9/torch/_inductor/triton_ops):Inductor 在 Triton 的集成方面做得更加全面且务实。Inductor 一共包含三种使用 Triton 的方式,针对非计算密集算子,基于 Inductor IR,实现了相对通用的[Codegen](https://link.zhihu.com/?target=https%3A//github.com/pytorch/pytorch/blob/ab148da66cb9433effac90c7bd4930a961481d19/torch/_inductor/codegen/triton.py%23L997)的支持。针对 GEMM,基于 Jinja2,通过模式匹配的方式实现了[半定制的 codegen](https://link.zhihu.com/?target=https%3A//github.com/pytorch/pytorch/blob/ab148da66cb9433effac90c7bd4930a961481d19/torch/_inductor/kernel/mm.py%23L27)。针对 Conv,调用[pre-baked Triton kernel](https://link.zhihu.com/?target=https%3A//github.com/pytorch/pytorch/blob/ab148da66cb9433effac90c7bd4930a961481d19/torch/_inductor/triton_ops/conv.py%23L14),没有提供定制能力。 ## 小结与思考 diff --git a/03Compiler/04Backend/06AutoTuning.md b/03Compiler/04Backend/06AutoTuning.md index 86e3ed74..037681a5 100644 --- a/03Compiler/04Backend/06AutoTuning.md +++ b/03Compiler/04Backend/06AutoTuning.md @@ -201,7 +201,7 @@ MetaSchedule 提供以下特性: + 用于实现手动调优、AutoTVM 风格和 AutoScheduler 风格的统一 API。 -+ 所有调度原语的可扩展性,包括张量化和循环分块。 在自动调优中使用新的原语几乎不需要额外的努力。 ++ 所有调度原语的可扩展性,包括张量化和循环分块。在自动调优中使用新的原语几乎不需要额外的努力。 + 自动化基础设施在其每个组件上都是可扩展的。每个组件的系统可以在纯 python 或 C++或两者中轻松自定义。例如,可以开发一个新的在 python 中的调度空间生成器或者新的 ProgramRunner 等。 diff --git a/03Compiler/04Backend/srt/03.srt b/03Compiler/04Backend/srt/03.srt index 357c2b4f..f81a3e57 100644 --- a/03Compiler/04Backend/srt/03.srt +++ b/03Compiler/04Backend/srt/03.srt @@ -49,7 +49,7 @@ Hello,大家好,我是 ZOMI 13 00:00:35,120 --> 00:00:36,640 -我们的算子的优化 +算子的优化 14 00:00:36,640 --> 00:00:38,000 @@ -85,11 +85,11 @@ AI 编译器的后端最主要的组成方式 22 00:00:52,320 --> 00:00:54,480 -就是我们的图的 IR 图的模式 +就是图的 IR 图的模式 23 00:00:54,480 --> 00:00:56,440 -变成我们的 Tensor 的 IR +变成 Tensor 的 IR 24 00:00:56,600 --> 00:00:57,840 @@ -97,7 +97,7 @@ AI 编译器的后端最主要的组成方式 25 00:00:57,840 --> 00:01:00,280 -然后给我们的后端的优化去做 +然后给后端的优化去做 26 00:01:00,280 --> 00:01:01,360 @@ -117,7 +117,7 @@ AI 编译器的后端最主要的组成方式 30 00:01:07,560 --> 00:01:08,800 -然后给我们的后端 +然后给后端 31 00:01:08,800 --> 00:01:13,200 @@ -137,7 +137,7 @@ AI 编译器的后端最主要的组成方式 35 00:01:19,640 --> 00:01:22,000 -那调度树就是把我们的一些算子 +那调度树就是把一些算子 36 00:01:22,360 --> 00:01:24,880 @@ -149,7 +149,7 @@ AI 编译器的后端最主要的组成方式 38 00:01:28,560 --> 00:01:31,600 -左边这个就是我们的低级的 IR +左边这个就是低级的 IR 39 00:01:31,600 --> 00:01:33,280 @@ -165,7 +165,7 @@ OPS IR 或者 Tensor IR 42 00:01:38,200 --> 00:01:41,080 -Schedule Trees 来代表我们的 Lower IR +Schedule Trees 来代表 Lower IR 43 00:01:41,080 --> 00:01:42,160 @@ -205,7 +205,7 @@ Schedule Trees 来代表我们的 Lower IR 52 00:01:58,640 --> 00:02:00,360 -可以看到其实我们的算子 +可以看到其实算子 53 00:02:00,360 --> 00:02:03,000 @@ -313,7 +313,7 @@ Schedule Trees 来代表我们的 Lower IR 79 00:02:59,480 --> 00:03:01,440 -或者在我们的图像迭代里面 +或者在图像迭代里面 80 00:03:01,600 --> 00:03:03,920 @@ -345,7 +345,7 @@ Schedule Trees 来代表我们的 Lower IR 87 00:03:17,600 --> 00:03:18,720 -扔到我们的内存里面 +扔到内存里面 88 00:03:18,720 --> 00:03:21,040 @@ -365,7 +365,7 @@ Schedule Trees 来代表我们的 Lower IR 92 00:03:25,960 --> 00:03:27,200 -每次我们的编译器 +每次编译器 93 00:03:27,320 --> 00:03:28,520 @@ -373,11 +373,11 @@ Schedule Trees 来代表我们的 Lower IR 94 00:03:28,520 --> 00:03:30,000 -丢给我们的硬件再去执行 +丢给硬件再去执行 95 00:03:30,000 --> 00:03:31,120 -然后从我们的编译栈 +然后从编译栈 96 00:03:31,120 --> 00:03:32,240 @@ -437,7 +437,7 @@ Schedule Trees 来代表我们的 Lower IR 110 00:04:04,320 --> 00:04:07,080 -这个数据其实跟我们的迭代 i 和 j +这个数据其实跟迭代 i 和 j 111 00:04:07,080 --> 00:04:08,480 @@ -465,11 +465,11 @@ tmp = x / (y - 1) 117 00:04:19,400 --> 00:04:20,680 -或者在我们的 cache 里面 +或者在 cache 里面 118 00:04:20,840 --> 00:04:22,200 -读到我们的 tmp 里面 +读到 tmp 里面 119 00:04:22,560 --> 00:04:24,360 @@ -525,7 +525,7 @@ tmp = x / (y - 1) 132 00:04:48,240 --> 00:04:50,120 -实例化到我们的循环外面 +实例化到循环外面 133 00:04:50,120 --> 00:04:51,080 @@ -613,11 +613,11 @@ tmp = x / (y - 1) 154 00:05:34,720 --> 00:05:36,880 -都可以去调用我们的一个算子库 +都可以去调用一个算子库 155 00:05:36,880 --> 00:05:38,760 -然后跑在我们的硬件上面的 +然后跑在硬件上面的 156 00:05:39,800 --> 00:05:41,320 @@ -625,7 +625,7 @@ tmp = x / (y - 1) 157 00:05:41,320 --> 00:05:42,960 -或者在我们的 AI 框架里面 +或者在 AI 框架里面 158 00:05:43,080 --> 00:05:44,760 @@ -641,11 +641,11 @@ tmp = x / (y - 1) 161 00:05:48,160 --> 00:05:49,400 -再去调我们的 Runtime +再去调 Runtime 162 00:05:49,400 --> 00:05:50,960 -或者调我们的算子库 +或者调算子库 163 00:05:51,200 --> 00:05:52,280 @@ -653,7 +653,7 @@ tmp = x / (y - 1) 164 00:05:52,440 --> 00:05:53,840 -可能直接调我们的算子库 +可能直接调算子库 165 00:05:54,120 --> 00:05:55,560 @@ -661,7 +661,7 @@ tmp = x / (y - 1) 166 00:05:56,240 --> 00:05:58,560 -最后在我们的硬件上面去跑的 +最后在硬件上面去跑的 167 00:05:58,840 --> 00:06:00,600 @@ -777,7 +777,7 @@ AI 编译器里面的计算的特征 195 00:07:03,760 --> 00:07:06,000 -或者我们的 ops optimizer +或者 ops optimizer 196 00:07:06,560 --> 00:07:07,480 @@ -785,7 +785,7 @@ AI 编译器里面的计算的特征 197 00:07:07,560 --> 00:07:09,160 -大部分集中在对我们的 +大部分集中在对 198 00:07:09,160 --> 00:07:12,840 @@ -857,7 +857,7 @@ AI 编译器里面的计算的特征 215 00:07:44,880 --> 00:07:46,800 -我们可以变成我们的存储分配 +我们可以变成存储分配 216 00:07:47,320 --> 00:07:49,480 @@ -869,7 +869,7 @@ AI 编译器里面的计算的特征 218 00:07:51,920 --> 00:07:53,440 -都是跟我们的存储相关的 +都是跟存储相关的 219 00:07:53,800 --> 00:07:55,200 diff --git a/03Compiler/04Backend/srt/04.srt b/03Compiler/04Backend/srt/04.srt index 5b813815..7fa7ffe3 100644 --- a/03Compiler/04Backend/srt/04.srt +++ b/03Compiler/04Backend/srt/04.srt @@ -25,7 +25,7 @@ AI 编译器后端优化的循环优化 7 00:00:17,680 --> 00:00:19,874 -也就是针对我们的循环进行 +也就是针对循环进行 8 00:00:19,874 --> 00:00:23,480 @@ -41,7 +41,7 @@ AI 编译器后端优化的循环优化 11 00:00:29,040 --> 00:00:32,000 -就是 loop 我们的循环 +就是 loop 循环 12 00:00:33,400 --> 00:00:34,920 @@ -57,7 +57,7 @@ AI 编译器后端优化的循环优化 15 00:00:39,360 --> 00:00:40,360 -就是我们的 +就是 16 00:00:40,960 --> 00:00:42,360 @@ -129,7 +129,7 @@ loop optimization 33 00:01:21,600 --> 00:01:23,560 -导致我们的 NPU 或者 CPU +导致 NPU 或者 CPU 34 00:01:23,560 --> 00:01:25,240 @@ -141,11 +141,11 @@ GPU 的处理器空转 36 00:01:27,480 --> 00:01:29,600 -这个时候我们的 AI 编译器 +这个时候 AI 编译器 37 00:01:29,600 --> 00:01:31,320 -就可以为我们的指令调度 +就可以为指令调度 38 00:01:31,320 --> 00:01:33,800 @@ -233,15 +233,15 @@ j 在迭代的时候 59 00:02:23,120 --> 00:02:25,320 -或者在我们的处理器里面 +或者在处理器里面 60 00:02:25,320 --> 00:02:27,400 -我们的显存内存 +显存内存 61 00:02:27,400 --> 00:02:29,320 -或者我们的 cache 都是有限的 +或者 cache 都是有限的 62 00:02:29,320 --> 00:02:31,000 @@ -261,7 +261,7 @@ j 在迭代的时候 66 00:02:36,840 --> 00:02:39,360 -直接加载到我们的设备里面 +直接加载到设备里面 67 00:02:39,600 --> 00:02:42,800 @@ -289,7 +289,7 @@ j 在迭代的时候 73 00:02:52,680 --> 00:02:56,440 -我们的显存存储 cache 的空间是有限的 +显存存储 cache 的空间是有限的 74 00:02:56,440 --> 00:02:58,680 @@ -305,7 +305,7 @@ j 在迭代的时候 77 00:03:05,240 --> 00:03:07,680 -是刚好满足我们的 cache 的大小 +是刚好满足 cache 的大小 78 00:03:07,680 --> 00:03:10,040 @@ -365,7 +365,7 @@ j 在迭代的时候 92 00:03:39,920 --> 00:03:42,320 -这样可以提高我们的流水线的效率 +这样可以提高流水线的效率 93 00:03:42,320 --> 00:03:43,360 @@ -377,7 +377,7 @@ j 在迭代的时候 95 00:03:45,800 --> 00:03:47,640 -应该跟我们的缓存块 +应该跟缓存块 96 00:03:47,640 --> 00:03:50,040 @@ -385,7 +385,7 @@ j 在迭代的时候 97 00:03:50,040 --> 00:03:52,200 -或者我们的处理器的整体的效率 +或者处理器的整体的效率 98 00:03:53,560 --> 00:03:56,560 @@ -401,7 +401,7 @@ j 在迭代的时候 101 00:04:02,400 --> 00:04:04,000 -一般来说我们的实现思路 +一般来说实现思路 102 00:04:04,000 --> 00:04:07,080 @@ -457,11 +457,11 @@ j 在迭代的时候 115 00:04:31,440 --> 00:04:33,320 -从而确保我们的 inner loop +从而确保 inner loop 116 00:04:33,320 --> 00:04:35,120 -能够满足我们的 cache +能够满足 cache 117 00:04:35,120 --> 00:04:37,000 @@ -529,7 +529,7 @@ A(i) += B(j) 133 00:05:12,600 --> 00:05:14,240 -分给了我们的 j_i +分给了 j_i 134 00:05:14,240 --> 00:05:18,440 @@ -541,7 +541,7 @@ A(i) += B(j) 136 00:05:19,960 --> 00:05:24,400 -可能就是完完全全能够塞到我们的处理器里面 +可能就是完完全全能够塞到处理器里面 137 00:05:24,400 --> 00:05:26,560 @@ -553,7 +553,7 @@ A(i) += B(j) 139 00:05:28,080 --> 00:05:30,480 -然后系统或者我们的芯片 +然后系统或者芯片 140 00:05:30,480 --> 00:05:33,640 @@ -690,7 +690,7 @@ loop reorder 173 00:06:53,040 --> 00:06:56,400 -那这个空间更多的是指我们的内存空间 +那这个空间更多的是指内存空间 174 00:06:56,400 --> 00:06:57,678 @@ -750,7 +750,7 @@ A(i, j) = B(i, j) * C(i, j) 188 00:07:28,600 --> 00:07:30,880 -那我们循环里面都用到我们的 i, j +那我们循环里面都用到 i, j 189 00:07:30,880 --> 00:07:32,600 @@ -770,15 +770,15 @@ A(i, j) = B(i, j) * C(i, j) 193 00:07:39,160 --> 00:07:40,640 -全都塞到我们的 cache 里面 +全都塞到 cache 里面 194 00:07:40,680 --> 00:07:42,720 -才能够让我们的通信 +才能够让通信 195 00:07:42,720 --> 00:07:44,640 -或者我们的异步开销更小 +或者异步开销更小 196 00:07:44,640 --> 00:07:45,880 @@ -794,7 +794,7 @@ A(i, j) = B(i, j) * C(i, j) 199 00:07:49,800 --> 00:07:53,120 -把我们的一万挪到我们的外层循环 +把一万挪到外层循环 200 00:07:53,120 --> 00:07:54,120 @@ -814,7 +814,7 @@ A(i, j) = B(i, j) * C(i, j) 204 00:08:00,520 --> 00:08:03,240 -那刚好对应我们的 cache 的空间是相同的 +那刚好对应 cache 的空间是相同的 205 00:08:03,240 --> 00:08:05,640 @@ -838,7 +838,7 @@ A(i, j) = B(i, j) * C(i, j) 210 00:08:14,280 --> 00:08:17,200 -和我们的流水线的一个满载负荷 +和流水线的一个满载负荷 211 00:08:17,200 --> 00:08:18,920 @@ -846,7 +846,7 @@ A(i, j) = B(i, j) * C(i, j) 212 00:08:18,920 --> 00:08:22,400 -就是我们的循环融合 loop fusion +就是循环融合 loop fusion 213 00:08:22,400 --> 00:08:23,880 @@ -982,7 +982,7 @@ for i 等于 1 到 n-1 246 00:09:30,520 --> 00:09:32,600 -就是跟我们的第六行对齐 +就是跟第六行对齐 247 00:09:32,600 --> 00:09:36,480 @@ -1014,7 +1014,7 @@ for i 等于 1 到 n-1 254 00:09:51,400 --> 00:09:53,639 -也是需要跟我们的 cache 满足 +也是需要跟 cache 满足 255 00:09:53,639 --> 00:09:55,280 @@ -1054,7 +1054,7 @@ for i 等于 1 到 n-1 264 00:10:15,520 --> 00:10:18,000 -这个条件呢是我们的控制流 +这个条件呢是控制流 265 00:10:18,000 --> 00:10:22,160 @@ -1110,7 +1110,7 @@ if else while 这种我们叫做条件循环 278 00:10:53,240 --> 00:10:54,680 -在我们的 GPU NPU +在 GPU NPU 279 00:10:54,680 --> 00:10:57,400 @@ -1122,7 +1122,7 @@ if else while 这种我们叫做条件循环 281 00:10:59,480 --> 00:11:01,976 -更多的是善于做我们的一些 +更多的是善于做一些 282 00:11:01,976 --> 00:11:03,640 diff --git a/03Compiler/04Backend/srt/05.srt b/03Compiler/04Backend/srt/05.srt index 0f9a479c..edf78d6f 100644 --- a/03Compiler/04Backend/srt/05.srt +++ b/03Compiler/04Backend/srt/05.srt @@ -37,7 +37,7 @@ Hello 大家好 10 00:00:25,320 --> 00:00:28,600 -其实我们之前已经讲了我们的循环优化 +其实我们之前已经讲了循环优化 11 00:00:28,680 --> 00:00:32,240 @@ -209,7 +209,7 @@ Tensorization 53 00:02:02,520 --> 00:02:04,920 -是随着我们的神经网络 +是随着神经网络 54 00:02:05,120 --> 00:02:07,000 @@ -253,7 +253,7 @@ Tensorization 64 00:02:25,520 --> 00:02:28,160 -这些 Core 都是我们的计算单元 +这些 Core 都是计算单元 65 00:02:28,520 --> 00:02:29,200 @@ -265,7 +265,7 @@ Tensorization 67 00:02:30,840 --> 00:02:32,560 -我们的张量化的计算 +张量化的计算 68 00:02:32,560 --> 00:02:34,000 @@ -321,7 +321,7 @@ Tensorization 81 00:02:53,280 --> 00:02:54,160 -对我们的神经网络 +对神经网络 82 00:02:54,160 --> 00:02:55,560 @@ -421,7 +421,7 @@ cuBLAS 还有 cuDNN 106 00:03:32,680 --> 00:03:34,160 -或者需要对我们的张量 +或者需要对张量 107 00:03:34,160 --> 00:03:35,400 @@ -733,19 +733,19 @@ Latency Hiding 184 00:05:55,760 --> 00:05:57,240 -去执行我们的算子 +去执行算子 185 00:05:57,240 --> 00:05:58,600 -或者执行我们的计算 +或者执行计算 186 00:05:58,600 --> 00:05:59,920 -但是我们的计算 +但是计算 187 00:05:59,920 --> 00:06:02,000 -是严重依赖于我们的 Memory 的 +是严重依赖于 Memory 的 188 00:06:02,000 --> 00:06:03,760 @@ -765,7 +765,7 @@ Latency Hiding 192 00:06:07,280 --> 00:06:09,400 -存回到我们的 Memory 里面 +存回到 Memory 里面 193 00:06:09,840 --> 00:06:12,600 @@ -781,7 +781,7 @@ Latency Hiding 196 00:06:16,200 --> 00:06:19,080 -把我们的内存的操作和计算进行重叠 +把内存的操作和计算进行重叠 197 00:06:19,360 --> 00:06:21,080 @@ -833,7 +833,7 @@ GPU 更多的是依赖于 wrap Schedule 209 00:06:52,240 --> 00:06:55,440 -对我们的多线程或者 SM 进行一个管理 +对多线程或者 SM 进行一个管理 210 00:06:55,640 --> 00:06:57,480 @@ -841,7 +841,7 @@ GPU 更多的是依赖于 wrap Schedule 211 00:06:57,480 --> 00:07:00,200 -这种方式去掩盖我们的访存延迟 +这种方式去掩盖访存延迟 212 00:07:00,560 --> 00:07:03,360 @@ -889,11 +889,11 @@ DAE 是什么我们后面将会展开 223 00:07:27,600 --> 00:07:30,000 -L1 的 cache 就离我们的 CUDA Core +L1 的 cache 就离 CUDA Core 224 00:07:30,000 --> 00:07:31,840 -或者我们的 Tensor Core 非常近了 +或者 Tensor Core 非常近了 225 00:07:31,840 --> 00:07:35,480 @@ -921,7 +921,7 @@ L1 的 cache 就离我们的 CUDA Core 231 00:07:46,640 --> 00:07:48,560 -Wrap Schedule 就会对我们的指令 +Wrap Schedule 就会对指令 232 00:07:48,680 --> 00:07:50,120 @@ -929,7 +929,7 @@ Wrap Schedule 就会对我们的指令 233 00:07:50,120 --> 00:07:52,480 -然后给我们的 Instruction Dispatch Unit +然后给 Instruction Dispatch Unit 234 00:07:52,480 --> 00:07:54,760 @@ -953,7 +953,7 @@ Wrap Schedule 就会对我们的指令 239 00:08:04,720 --> 00:08:07,400 -而 Instruction3 就是我们的 Wrap0 +而 Instruction3 就是 Wrap0 240 00:08:07,400 --> 00:08:09,880 @@ -969,7 +969,7 @@ Wrap Schedule 就会对我们的指令 243 00:08:14,160 --> 00:08:15,560 -或者我们的线程的阻塞 +或者线程的阻塞 244 00:08:15,560 --> 00:08:17,960 @@ -1037,11 +1037,11 @@ Wrap 的控制 260 00:08:50,200 --> 00:08:51,720 -跟我们的管道分离 +跟管道分离 261 00:08:51,720 --> 00:08:53,800 -那执行处理器就是我们的 EP +那执行处理器就是 EP 262 00:08:53,800 --> 00:08:56,760 @@ -1105,7 +1105,7 @@ TPU 里面就采用一种技术 277 00:09:23,240 --> 00:09:25,200 -就是我们的 Pipeline 是比较明确的 +就是 Pipeline 是比较明确的 278 00:09:25,200 --> 00:09:27,080 @@ -1198,11 +1198,11 @@ TPU 里面就采用一种技术 300 00:10:16,880 --> 00:10:18,440 -编译器会在我们的 CPU +编译器会在 CPU 301 00:10:18,440 --> 00:10:21,080 -或者在我们的系统里面的内存占空间 +或者在系统里面的内存占空间 302 00:10:21,080 --> 00:10:24,400 @@ -1234,7 +1234,7 @@ TPU 里面就采用一种技术 309 00:10:36,800 --> 00:10:38,840 -在我们的函数外部去声明的 +在函数外部去声明的 310 00:10:39,280 --> 00:10:40,400 @@ -1294,7 +1294,7 @@ TPU 里面就采用一种技术 324 00:11:08,840 --> 00:11:10,760 -就会把我们的整体的内存块 +就会把整体的内存块 325 00:11:10,760 --> 00:11:12,080 @@ -1306,7 +1306,7 @@ TPU 里面就采用一种技术 327 00:11:15,640 --> 00:11:16,920 -去访问我们的内存的 +去访问内存的 328 00:11:17,080 --> 00:11:19,960 @@ -1330,11 +1330,11 @@ TPU 里面就采用一种技术 333 00:11:28,680 --> 00:11:29,960 -就是我们的栈堆 +就是栈堆 334 00:11:30,280 --> 00:11:31,760 -还有我们的静态存储空间 +还有静态存储空间 335 00:11:31,760 --> 00:11:34,520 @@ -1358,7 +1358,7 @@ TPU 里面就采用一种技术 340 00:11:41,200 --> 00:11:42,800 -这种就是告诉我们的系统 +这种就是告诉系统 341 00:11:43,080 --> 00:11:44,360 @@ -1366,7 +1366,7 @@ TPU 里面就采用一种技术 342 00:11:44,360 --> 00:11:46,080 -怎么去分配我们的内存空间呢 +怎么去分配内存空间呢 343 00:11:46,560 --> 00:11:47,320 @@ -1474,7 +1474,7 @@ TPU 里面就采用一种技术 369 00:12:28,720 --> 00:12:29,880 -到我们的指令优化 +到指令优化 370 00:12:29,880 --> 00:12:30,760 diff --git a/03Compiler/04Backend/srt/06.srt b/03Compiler/04Backend/srt/06.srt index d9da3764..f0835eca 100644 --- a/03Compiler/04Backend/srt/06.srt +++ b/03Compiler/04Backend/srt/06.srt @@ -32,7 +32,7 @@ AutoTuning 这个内容 9 00:00:22,074 --> 00:00:24,200 -靠的是我们的 AutoTuning 的方法 +靠的是 AutoTuning 的方法 10 00:00:24,200 --> 00:00:26,996 @@ -108,7 +108,7 @@ AutoTuning 这个内容 28 00:01:13,200 --> 00:01:16,100 -然后让我们的整个程序的性能最佳 +然后让整个程序的性能最佳 29 00:01:16,100 --> 00:01:18,500 @@ -180,7 +180,7 @@ AutoTuning 这个内容 46 00:01:59,100 --> 00:02:01,200 -对于我们的一些大量的计算 +对于一些大量的计算 47 00:02:01,200 --> 00:02:02,900 @@ -292,7 +292,7 @@ Auto-Tuning in AI 74 00:02:56,900 --> 00:02:58,000 -就是我们的成本函数 +就是成本函数 75 00:02:58,000 --> 00:02:59,100 @@ -356,7 +356,7 @@ AI 加速芯片上面去执行 90 00:03:29,300 --> 00:03:31,800 -就是我们的神经网络或者 Kernel 的算子 +就是神经网络或者 Kernel 的算子 91 00:03:31,800 --> 00:03:34,000 @@ -372,7 +372,7 @@ AI 加速芯片上面去执行 94 00:03:37,500 --> 00:03:40,600 -我们的数据的结构的维度是非常的高的 +数据的结构的维度是非常的高的 95 00:03:40,600 --> 00:03:43,200 @@ -420,7 +420,7 @@ Max Pooling, Average Pooling 106 00:04:03,700 --> 00:04:06,100 -还有我们的 Normalization +还有 Normalization 107 00:04:06,100 --> 00:04:07,100 @@ -596,7 +596,7 @@ Layer Normalization 150 00:05:37,100 --> 00:05:39,200 -然后把我们的一些大量的循环 +然后把一些大量的循环 151 00:05:39,200 --> 00:05:41,100 @@ -644,7 +644,7 @@ Parameterization 就是参数化 162 00:05:56,500 --> 00:05:59,600 -去评价我们的参数化到底合不合理 +去评价参数化到底合不合理 163 00:05:59,600 --> 00:06:01,300 @@ -672,7 +672,7 @@ Search Algorithm 169 00:06:10,300 --> 00:06:12,700 -让我们的成本函数最小化 +让成本函数最小化 170 00:06:12,700 --> 00:06:15,800 @@ -712,7 +712,7 @@ Search Algorithm 179 00:06:27,900 --> 00:06:29,400 -或者我们的搜索空间呢 +或者搜索空间呢 180 00:06:29,400 --> 00:06:31,000 @@ -780,7 +780,7 @@ TVM 呢 196 00:06:55,700 --> 00:06:58,100 -对我们的调度树进行一个表达 +对调度树进行一个表达 197 00:06:58,100 --> 00:07:00,600 @@ -820,7 +820,7 @@ TVM 呢 206 00:07:14,700 --> 00:07:17,000 -我们的 factor 可以等于各种内容 + factor 可以等于各种内容 207 00:07:17,000 --> 00:07:17,800 @@ -916,7 +916,7 @@ TVM 呢 230 00:07:51,900 --> 00:07:54,300 -为了让我们的成本函数计算的越快 +为了让成本函数计算的越快 231 00:07:54,300 --> 00:07:57,400 @@ -944,15 +944,15 @@ TVM 呢 237 00:08:04,700 --> 00:08:07,800 -就是基于我们的 NPU 的硬件的一个黑盒模式 +就是基于 NPU 的硬件的一个黑盒模式 238 00:08:07,800 --> 00:08:09,700 -就真正的我们的成本函数 +就真正的成本函数 239 00:08:09,700 --> 00:08:11,800 -跑在我们的 AI 加速芯片上面 +跑在 AI 加速芯片上面 240 00:08:11,800 --> 00:08:12,500 @@ -988,7 +988,7 @@ TVM 呢 248 00:08:23,200 --> 00:08:24,800 -模拟我们的 NPU +模拟 NPU 249 00:08:24,800 --> 00:08:25,700 @@ -1036,11 +1036,11 @@ TVM 呢 260 00:08:47,400 --> 00:08:49,400 -对我们的搜索空间进行搜索 +对搜索空间进行搜索 261 00:08:49,400 --> 00:08:50,800 -对我们的参数化的内容 +对参数化的内容 262 00:08:50,800 --> 00:08:53,800 @@ -1108,7 +1108,7 @@ AutoTuning 的第一个内容就对应于 Section 4 278 00:09:18,406 --> 00:09:19,600 -我们的参数化 +参数化 279 00:09:19,600 --> 00:09:21,100 @@ -1148,11 +1148,11 @@ AutoTuning 的第一个内容就对应于 Section 4 288 00:09:34,800 --> 00:09:36,700 -我们就可以很好的对我们的计算 +我们就可以很好的对计算 289 00:09:36,700 --> 00:09:37,900 -还有对我们的硬件呢 +还有对硬件呢 290 00:09:37,900 --> 00:09:39,400 @@ -1164,7 +1164,7 @@ AutoTuning 的第一个内容就对应于 Section 4 292 00:09:40,500 --> 00:09:43,200 -就建立了我们的成本的模型 +就建立了成本的模型 293 00:09:43,200 --> 00:09:44,100 @@ -1196,11 +1196,11 @@ AutoTuning 的第一个内容就对应于 Section 4 300 00:09:54,300 --> 00:09:55,700 -搜索我们的整个空间 +搜索整个空间 301 00:09:55,700 --> 00:09:58,000 -使得我们的成本函数最优 +使得成本函数最优 302 00:09:58,000 --> 00:09:59,300 @@ -1384,7 +1384,7 @@ AutoTuning 的第一个内容就对应于 Section 4 347 00:11:11,900 --> 00:11:13,800 -还有我们的 Cost Model 去配合 +还有 Cost Model 去配合 348 00:11:13,800 --> 00:11:16,100 diff --git a/03Compiler/README.md b/03Compiler/README.md index ff6fb464..454ba211 100644 --- a/03Compiler/README.md +++ b/03Compiler/README.md @@ -10,7 +10,7 @@ - [**《传统编译器》**](./01Tradition/)会粗略地回顾传统编译器中的前端、后端、IR 中间表达等主要的概念,并对目前最常用的 GCC 和 LLVM 的发展历史,GCC 的使用方式和 LLVM 的架构前后端优化划分,两大编译器 GCC 和 LLVM 进行简单的展开,去了解 GCC 的编译流程和编译方式,并回顾 LLVM 的整体架构,了解传统编译器的整体架构和脉络。 -- [**《AI 编译器》**](./02AICompiler/)是本节的概览重点,去了解本节的主要内容 AI 编译器的整体架构,包括他的发展阶段,目前主要的组成模块,整体的技术演进方向等概念性的内容,因为近年来 AI 编译器发展迅猛,可以横向去了解 AI 编译器整体技术。AI 编译器发展时间并不长,从第一代开始到目前进入第二代,AI 编译器整体架构基本上已经清晰,可是也会遇到很多挑战和技术难点。 +- [**《AI 编译器》**](./02AICompiler/)是本节的概览重点,去了解本节的主要内容 AI 编译器的整体架构,包括他的发展阶段,目前主要的组成模块,整体的技术演进方向等概念性的内容,因为近年来 AI 编译器发展迅猛,可以横向去了解 AI 编译器整体技术。AI 编译器发展时间并不长,从第一代开始到目前进入第二代,AI 编译器整体架构基本上已经清晰,可是也会遇到很多挑战和技术难点。 - [**《前端优化》**](./03Frontend/)前端优化作为 AI 编译器的整体架构主要模块,主要优化的对象是计算图,而计算图是通过 AI 框架产生的,值得注意的是并不是所有的 AI 框架都会生成计算图,有了计算图就可以结合深度学习的原理知识进行图的优化。前端优化包括图算融合、数据排布、内存优化等跟深度学习相结合的优化手段,同时把传统编译器关于代数优化的技术引入到计算图中。 diff --git a/04Inference/01Inference/01Introduction.md b/04Inference/01Inference/01Introduction.md index 7beba6c2..3919d840 100644 --- a/04Inference/01Inference/01Introduction.md +++ b/04Inference/01Inference/01Introduction.md @@ -136,11 +136,11 @@ s ## 小结与思考 -- 推理系统与推理引擎基础:推理系统是专门部署AI模型并执行预测任务的系统,类似于Web服务但专注于AI;推理引擎则是推理系统中负责模型加载与执行的核心组件,提供高效快捷的执行环境。 +- 推理系统与推理引擎基础:推理系统是专门部署 AI 模型并执行预测任务的系统,类似于 Web 服务但专注于 AI;推理引擎则是推理系统中负责模型加载与执行的核心组件,提供高效快捷的执行环境。 - 模型优化技术:包括模型小型化、离线优化压缩等技术,通过量化、剪枝、知识蒸馏等手段减少模型大小、提高执行效率,同时保持模型精度,以适应端侧设备有限的资源。 -- 推理系统应用与挑战:推理系统广泛应用于日常生活,如人脸landmark识别、人脸和手势检测等,同时面临API设计、数据质量、网络延迟、硬件加速、服务稳定性等维护挑战。 +- 推理系统应用与挑战:推理系统广泛应用于日常生活,如人脸 landmark 识别、人脸和手势检测等,同时面临 API 设计、数据质量、网络延迟、硬件加速、服务稳定性等维护挑战。 ## 本节视频 diff --git a/04Inference/01Inference/02Constraints.md b/04Inference/01Inference/02Constraints.md index a7ed9e2a..f53b7830 100644 --- a/04Inference/01Inference/02Constraints.md +++ b/04Inference/01Inference/02Constraints.md @@ -100,7 +100,7 @@ 然而,在推理阶段中,数据经过训练好的网络模型用于发现和预测新输入中的信息。在这个阶段,这些输入数据通过更小的批量大小输入网络,且由于有延迟的约束,大的批量大小需要所有批量内样本都处理完才能响应,容易造成延迟超出约束(例如,每个用户请求要求 100ms 内进行响应)。 -对于推理阶段,性能目标与训练阶段有所不同。 为了最大限度地减少网络的端到端响应时间(End to End Response Time),推理通常比训练批量输入更少的输入样本,也就是更小的批量大小,因为依赖推理工作的服务(例如,基于云的图像处理管道)需要尽可能的更快响应,因此用户不需要让系统累积样本形成更大的批量大小,从而避免了等待几秒钟的响应时间。在推理阶段,低延迟是更为关键的性能指标,而高吞吐量虽然在训练期间是重要的,但在推理时则相对次要。 +对于推理阶段,性能目标与训练阶段有所不同。为了最大限度地减少网络的端到端响应时间(End to End Response Time),推理通常比训练批量输入更少的输入样本,也就是更小的批量大小,因为依赖推理工作的服务(例如,基于云的图像处理管道)需要尽可能的更快响应,因此用户不需要让系统累积样本形成更大的批量大小,从而避免了等待几秒钟的响应时间。在推理阶段,低延迟是更为关键的性能指标,而高吞吐量虽然在训练期间是重要的,但在推理时则相对次要。 接下来,可以通过以下使用 Pytorch 实现的 ResNet50 模型在 TensorRT 的推理过程实例来了解模型推理的常见步骤。 @@ -263,7 +263,7 @@ print("Predicted output:", output_data) ### 推理系统 -如下图的推理系统组件与架构图所示,推理系统中常常涉及相应模块并完成相应功能,将在后面章节中逐步展开。 通过下图可以看到推理系统的全貌: +如下图的推理系统组件与架构图所示,推理系统中常常涉及相应模块并完成相应功能,将在后面章节中逐步展开。通过下图可以看到推理系统的全貌: ![推理系统组件与架构图](images/02Constrains05.png) diff --git a/04Inference/01Inference/03Workflow.md b/04Inference/01Inference/03Workflow.md index b80c4c04..32306ae6 100644 --- a/04Inference/01Inference/03Workflow.md +++ b/04Inference/01Inference/03Workflow.md @@ -76,7 +76,7 @@ - 安全性风险增加:由于边缘设备更接近物理世界,直接面对用户或攻击者,其上的推理系统更容易受到恶意攻击,如模型窃取、对抗样本攻击、侧信道攻击等。保障边缘推理的安全性需要强化模型加密、完整性验证、反逆向工程等防护措施,以及实施实时监控与异常检测机制。 -- 平台多样性与缺乏通用解决方案: 边缘设备类型繁多,操作系统各异,硬件架构差异明显,导致没有一种通用的 DNN 平台或解决方案能无缝适配所有设备。开发者需要面对跨平台兼容性问题,可能需要为不同设备定制化模型编译与优化过程,增加了部署与维护成本。 +- 平台多样性与缺乏通用解决方案:边缘设备类型繁多,操作系统各异,硬件架构差异明显,导致没有一种通用的 DNN 平台或解决方案能无缝适配所有设备。开发者需要面对跨平台兼容性问题,可能需要为不同设备定制化模型编译与优化过程,增加了部署与维护成本。 其次就是在边缘部署要做的工作: @@ -131,7 +131,7 @@ 除了云端的部署,神经网络模型的另一大场景就是边缘(Edge)部署,随着越来越多的物联网设备智能化,越来越多的移动端系统中开始部署神经网络模型。移动端部署应用常常有以下场景:智能设备,智慧城市,智能工业互联网,智慧办公室等。 -边缘部署和推理方式常见的为以下几种: +边缘部署和推理方式常见的为以下几种: ![边缘设备上的计算](images/03Workflow04.png) @@ -167,9 +167,9 @@ ## 小结与思考 -- 云端部署:在云服务器上进行AI模型推理,具有高吞吐量和集中管理的优势,但需要解决网络延迟、高成本和数据隐私保护的问题。 +- 云端部署:在云服务器上进行 AI 模型推理,具有高吞吐量和集中管理的优势,但需要解决网络延迟、高成本和数据隐私保护的问题。 -- 边缘侧部署:在边缘设备上执行AI模型推理,以降低延迟和节省带宽,面临硬件资源有限、数据分散和安全性风险等挑战。 +- 边缘侧部署:在边缘设备上执行 AI 模型推理,以降低延迟和节省带宽,面临硬件资源有限、数据分散和安全性风险等挑战。 ## 本节视频 diff --git a/04Inference/01Inference/05Inference.md b/04Inference/01Inference/05Inference.md index 67abfdcd..f066530b 100644 --- a/04Inference/01Inference/05Inference.md +++ b/04Inference/01Inference/05Inference.md @@ -242,9 +242,9 @@ AI 模型的运行离不开强大的计算资源支持,尤其是面对诸如 布局,即数据在内存中的组织方式。布局转换优化,是对数据存取路径的重构,旨在最小化内存访问延迟,最大化数据复用,特别是在神经网络模型广泛依赖的矩阵运算中,合理的布局选择能显著提升计算密集型任务的执行速度。例如,从 NHWC 到 NCHW 的转换,或是其他特定硬件偏好的布局格式调整,虽看似简单,实则深刻影响着内存访问模式与带宽利用。 -如 **NHWC** 是 TensorFlow 等一些框架默认使用的布局,尤其在 CPU 上较为常见。这里的字母分别代表:**N**:批量大小(Batch Size),**H**:高度(Height),**W**:宽度(Width), **C**:通道数(Channels),对于 RGB 图像,C=3; +如 **NHWC** 是 TensorFlow 等一些框架默认使用的布局,尤其在 CPU 上较为常见。这里的字母分别代表:**N**:批量大小(Batch Size),**H**:高度(Height),**W**:宽度(Width),**C**:通道数(Channels),对于 RGB 图像,C=3; -**NCHW** 布局更受 CUDA 和 CuDNN 等 GPU 库的青睐,尤其是在进行深度学习加速时。布局变为: **N**:批量大小,**C**:通道数,**H**:高度,**W**:宽度。 +**NCHW** 布局更受 CUDA 和 CuDNN 等 GPU 库的青睐,尤其是在进行深度学习加速时。布局变为:**N**:批量大小,**C**:通道数,**H**:高度,**W**:宽度。 例如,一个处理单张 RGB 图像的张量布局为 NHWC 时,形状表示为[1, 224, 224, 3],意味着 1 个样本,图像尺寸为 224x224 像素,3 个颜色通道。同样的例子,在 NCHW 布局下,张量形状会是[1, 3, 224, 224]。 diff --git a/04Inference/02Mobilenet/01Introduction.md b/04Inference/02Mobilenet/01Introduction.md index 3bb4a2c1..b55a778e 100644 --- a/04Inference/02Mobilenet/01Introduction.md +++ b/04Inference/02Mobilenet/01Introduction.md @@ -67,7 +67,7 @@ MACs 全称 Multiply–Accumulate Operations,即乘加累积操作,**1MACs ### 内存带宽 -内存带宽决定了它将数据从内存(vRAM) 移动到计算核心的速度,是比计算速度更具代表性的指标,内存带宽值取决于内存和计算核心之间数据传输速度,以及这两个部分之间总线中单独并行链路数量。 +内存带宽决定了它将数据从内存(vRAM)移动到计算核心的速度,是比计算速度更具代表性的指标,内存带宽值取决于内存和计算核心之间数据传输速度,以及这两个部分之间总线中单独并行链路数量。 ### 神经网络的计算量 diff --git a/04Inference/02Mobilenet/02Squeezenet.md b/04Inference/02Mobilenet/02Squeezenet.md index 62f9f138..d5346e3e 100644 --- a/04Inference/02Mobilenet/02Squeezenet.md +++ b/04Inference/02Mobilenet/02Squeezenet.md @@ -22,7 +22,7 @@ SqueezeNet 算法的主要目标是构建轻量参数的 CNN 架构,同时不 ### Fire 模块 -**Fire 模块**组成:主要包括挤压层(squeeze) 和拓展层(expand); +**Fire 模块**组成:主要包括挤压层(squeeze)和拓展层(expand); - **Squeeze**:只有 1×1 卷积滤波器 ; - **Expand**:混合有 1×1 和 3×3 卷积滤波器 ; diff --git a/04Inference/02Mobilenet/04Mobilenet.md b/04Inference/02Mobilenet/04Mobilenet.md index e8fa542e..f92b5563 100644 --- a/04Inference/02Mobilenet/04Mobilenet.md +++ b/04Inference/02Mobilenet/04Mobilenet.md @@ -134,7 +134,7 @@ class MobileNetV1(nn.Module): ## MobileNet V2 -在上一章节中介绍了 MobileNetV1 版本,主要是将普通卷积转成逐点和逐通道卷积,也讲到了用于调整模型的大小和计算复杂性的宽度和分辨率因子。在本节中主要会讲解基于 V1 构建的更高效更轻量的网络结构。 +在上一章节中介绍了 MobileNetV1 版本,主要是将普通卷积转成逐点和逐通道卷积,也讲到了用于调整模型的大小和计算复杂性的宽度和分辨率因子。在本节中主要会讲解基于 V1 构建的更高效更轻量的网络结构。 ### 贡献概述 @@ -350,7 +350,7 @@ $$ ![h-swish](images/04.mobilenet_09.png) -在网络结构搜索中,作者结合两种技术:资源受限的 NAS(platform-aware NAS)与 NetAdapt,前者用于在计算和参数量受限的前提下搜索网络的各个模块,所以称之为模块级的搜索(Block-wise Search) ,后者用于对各个模块确定之后网络层的微调。 +在网络结构搜索中,作者结合两种技术:资源受限的 NAS(platform-aware NAS)与 NetAdapt,前者用于在计算和参数量受限的前提下搜索网络的各个模块,所以称之为模块级的搜索(Block-wise Search),后者用于对各个模块确定之后网络层的微调。 ### NAS 搜索全局结构(Block-wise Search) @@ -808,11 +808,11 @@ FFN 是由两个 1x1 的点状卷积(PW)堆叠而成,并在它们之间加 操作强度的重要性:近期在视觉模型的研究中,人们大多致力于减少算术运算(MACs)以提高效率。然而,在移动加速器上性能的真正瓶颈往往不是计算而是内存访问。这是因为加速器提供的计算能力远大于内存带宽。因此,仅仅最小化 MACs 可能并不会导致更好的性能。相反,作者必须考虑操作强度,即算术运算与内存访问的比率。 -MQA 在混合模型中是高效的:MHSA 将 Query、键和值投影到多个空间以捕捉信息的不同方面。多 Query 注意力(MQA) 通过在所有头之间使用共享的键和值简化了这一点。尽管多个 Query 头是必要的,但大型语言模型可以有效共享单个键和值的头,而不会牺牲准确度。对于键和值使用一个共享的头,当批处理的 Token 数量相对于特征维度较小时,可以大大减少内存访问需求,从而显著提高操作强度。 +MQA 在混合模型中是高效的:MHSA 将 Query、键和值投影到多个空间以捕捉信息的不同方面。多 Query 注意力(MQA)通过在所有头之间使用共享的键和值简化了这一点。尽管多个 Query 头是必要的,但大型语言模型可以有效共享单个键和值的头,而不会牺牲准确度。对于键和值使用一个共享的头,当批处理的 Token 数量相对于特征维度较小时,可以大大减少内存访问需求,从而显著提高操作强度。 这对于面向移动应用的混合视觉模型通常是这种情况,在这种情况下,仅在具有高特征维度的低分辨率后期阶段使用注意力,并且批大小通常为 1。作者的实验证实了 MQA 在混合模型中的优势。如表 1 所示,与 MHSA 相比,MQA 在 EdgeTPUs 和三星 S23GPU 上实现了超过 39%的加速,而质量损失可以忽略不计(-0.03%)。MQA 还将 MACs 和模型参数减少了 25%以上。据作者所知,作者是第一个在移动视觉中使用 MQA 的。 -采用非对称空间下采样: 受到 MQA 的启发,它在 Query、键和值之间使用非对称计算,作者将空间缩减注意力(SRA)融合到作者优化的 MQA 模块中,以降低键和值的分辨率,同时保持高分辨率 Query。这一策略是由混合模型中空间相邻标记之间的观察到的相关性所启发的,这归因于早期层中的空间混合卷积滤波器。 +采用非对称空间下采样:受到 MQA 的启发,它在 Query、键和值之间使用非对称计算,作者将空间缩减注意力(SRA)融合到作者优化的 MQA 模块中,以降低键和值的分辨率,同时保持高分辨率 Query。这一策略是由混合模型中空间相邻标记之间的观察到的相关性所启发的,这归因于早期层中的空间混合卷积滤波器。 通过非对称空间下采样,作者在输入和输出之间保持了相同的标记数量,保持了注意力的高分辨率并显著提高了效率。与不同,作者的方法用步长为 2 的 3x3 深度卷积替换了 AvgPooling,为提高模型容量提供了一种成本效益高的方式。 diff --git a/04Inference/02Mobilenet/05ESPNet.md b/04Inference/02Mobilenet/05ESPNet.md index 9b8d6dbd..5953d5c8 100644 --- a/04Inference/02Mobilenet/05ESPNet.md +++ b/04Inference/02Mobilenet/05ESPNet.md @@ -109,7 +109,7 @@ ESPNet 的不同变体如下图所示。第一个变体,ESPNet-A(图 a),是 从 ESPNet- a 到 ESPNet 的路径。红色和绿色色框分别代表负责下采样和上采样操作的模块。空间级别的 l 在(a)中的每个模块的左侧。本文将每个模块表示为(#输入通道,#输出通道)。这里,conv-n 表示 n × n 卷积。 -为了在不改变网络拓扑结构的情况下构建具有较深计算效率的边缘设备网络,超参数α控制网络的深度;ESP 模块在空间层次 l 上重复 $α_{l}$ 次。在更高的空间层次(l = 0 和 l = 1), cnn 需要更多的内存,因为这些层次的特征图的空间维数较高。为了节省内存,ESP 和卷积模块都不会在这些空间级别上重复。 +为了在不改变网络拓扑结构的情况下构建具有较深计算效率的边缘设备网络,超参数α控制网络的深度;ESP 模块在空间层次 l 上重复 $α_{l}$ 次。在更高的空间层次(l = 0 和 l = 1),cnn 需要更多的内存,因为这些层次的特征图的空间维数较高。为了节省内存,ESP 和卷积模块都不会在这些空间级别上重复。 ## ESPNet V2 @@ -129,7 +129,7 @@ ESPNet V2 与 V1 版本相比,其特点如下: 6. 使用级联(concatenation)取代对应元素加法操作(element-wise addition operation ) -### DDConv 模块 +### DDConv 模块 深度分离空洞卷积(Depth-wise dilated separable convolution,DDConv)分两步: diff --git a/04Inference/02Mobilenet/06FBNet.md b/04Inference/02Mobilenet/06FBNet.md index 61286360..19316791 100644 --- a/04Inference/02Mobilenet/06FBNet.md +++ b/04Inference/02Mobilenet/06FBNet.md @@ -134,7 +134,7 @@ class MixedOperation(nn.Module): ### 网络结构 -FBNet 是通过 DNAS 神经架构搜索发现的一种卷积神经架构。 它采用受 MobileNetv2 启发的基本类型图像模型块,该模型利用深度卷积和反向残差结构(请参阅组件,见下图)。其中,FBNet-A 和 FBNet-B、FBNet-C 的区别在于最后一个卷积的输出 channel 不一样 +FBNet 是通过 DNAS 神经架构搜索发现的一种卷积神经架构。它采用受 MobileNetv2 启发的基本类型图像模型块,该模型利用深度卷积和反向残差结构(请参阅组件,见下图)。其中,FBNet-A 和 FBNet-B、FBNet-C 的区别在于最后一个卷积的输出 channel 不一样 ![FBNet](images/06.fpnet_03.png) @@ -200,7 +200,7 @@ E:为了保留所有搜索到的输入分辨率的感受域,在卷积之前必 此外,注意到可以实现相同的效果,而不需要构造一个更小的张量,具有适当步长的膨胀卷积;进行子采样以避免修改 F 运算。 -上面说了在 channel 维度的做法。 在空间维度的做法也是类似的,作者也想构造一种加权和的形式表征不同分辨率的特征图。如图 A 所示,不同分辨率的 tensor 不能直接相加。图 B 说明了在边缘 padding 的方式不行,像素无法对齐。图 C 这种方式会又带来感受野错位的问题:如图 D 所示,Interspersing zero-padding 之后,一个 3x3 的 kenel 有效感受野变成了 2x2。所以图 E 才是作者最终的解决方法:和 F 运算完之后再 padding。 +上面说了在 channel 维度的做法。在空间维度的做法也是类似的,作者也想构造一种加权和的形式表征不同分辨率的特征图。如图 A 所示,不同分辨率的 tensor 不能直接相加。图 B 说明了在边缘 padding 的方式不行,像素无法对齐。图 C 这种方式会又带来感受野错位的问题:如图 D 所示,Interspersing zero-padding 之后,一个 3x3 的 kenel 有效感受野变成了 2x2。所以图 E 才是作者最终的解决方法:和 F 运算完之后再 padding。 ![FBNetV2](images/06.fpnet_06.png) diff --git a/04Inference/02Mobilenet/07EfficientNet.md b/04Inference/02Mobilenet/07EfficientNet.md index f9d02249..7fcc9949 100644 --- a/04Inference/02Mobilenet/07EfficientNet.md +++ b/04Inference/02Mobilenet/07EfficientNet.md @@ -46,7 +46,7 @@ $$ 通常 ConvNet 的设计焦点在 $F_{i}$,但是这篇文章的焦点,或者说 Model Scaling 的焦点则是在模型的深度($L_{i}$)、宽度($C_{i}$)和输入图片的大小($H_{i}$,$W_{i}$),而不改变在 baseline 中预先定义好的 $F_{i}$。 -通过固定 $F_{i}$,简化了对于新资源约束的 Model Scaling 设计问题,但仍然有很大的设计空间来探索每一层的不同 $L_{i}$,$C_{i}$,$H_{i}$,$W_{i}$。为了进一步减小设计空间,作者又限制所有层必须以恒定比率均匀地做 Scaling。我们的目标是在任何给定的资源约束下最大化模型精度,这可以表述为优化问题: +通过固定 $F_{i}$,简化了对于新资源约束的 Model Scaling 设计问题,但仍然有很大的设计空间来探索每一层的不同 $L_{i}$,$C_{i}$,$H_{i}$,$W_{i}$。为了进一步减小设计空间,作者又限制所有层必须以恒定比率均匀地做 Scaling。目标是在任何给定的资源约束下最大化模型精度,这可以表述为优化问题: $$ max_{d,w,r}Accurracy(N(d,w,r)) @@ -146,7 +146,7 @@ class InvertedResidualConfig: index: str, # 1a, 2a, 2b, ... width_coefficient: float): # 网络宽度的倍率因子,即 channel 数 xn self.input_c = self.adjust_channels(input_c, width_coefficient) - self.expanded_c = self.input_c * expanded_ratio # expanded_ratio: 1 or 6 表示 MBConvm 模块内第一个 1x1 卷积层维度扩展输出的 channel 的扩展因子 + self.expanded_c = self.input_c * expanded_ratio # expanded_ratio:1 or 6 表示 MBConvm 模块内第一个 1x1 卷积层维度扩展输出的 channel 的扩展因子 self.kernel = kernel self.out_c = self.adjust_channels(out_c, width_coefficient) self.use_se = use_se diff --git a/04Inference/02Mobilenet/08GhostNet.md b/04Inference/02Mobilenet/08GhostNet.md index 35611071..8a831e0c 100644 --- a/04Inference/02Mobilenet/08GhostNet.md +++ b/04Inference/02Mobilenet/08GhostNet.md @@ -10,7 +10,7 @@ ### Ghost Module -利用`Ghost Module`生成与普通卷积层相同数量的特征图,我们可以轻松地将`Ghost Module`替换卷积层,集成到现有设计好的神经网络结构中,以减少计算成本。第一、先通过普通的 conv 生成一些特征图。 第二、对生成的特征图进行 cheap 操作生成冗余特征图,这步使用的卷积是 DW 卷积。 第三将 conv 生成的特征图与 cheap 操作生成的特征图进行 concat 操作。 如下图(b)所示,展示了 Ghost 模块和普通卷积的过程。 +利用`Ghost Module`生成与普通卷积层相同数量的特征图,我们可以轻松地将`Ghost Module`替换卷积层,集成到现有设计好的神经网络结构中,以减少计算成本。第一、先通过普通的 conv 生成一些特征图。第二、对生成的特征图进行 cheap 操作生成冗余特征图,这步使用的卷积是 DW 卷积。第三将 conv 生成的特征图与 cheap 操作生成的特征图进行 concat 操作。如下图(b)所示,展示了 Ghost 模块和普通卷积的过程。 ![GhostModule](images/08.ghostnet_01.png) ===== ???? @@ -157,7 +157,7 @@ $$ a_{hw} =\sum_{w'=1}^{W}F^{W}_{w,hw'}\odot z_{h'w},h=1,2,...,H,w=1,2,...,W \tag{3} $$ -其中,$F^{H}$ 和 $F^{W}$ 是变换的权重。输入原始特征 Z,并依次应用公式(2)和公式(3),分别提取沿两个方向的长距离依赖关系。 作者将此操作称为解耦全连接注意力(decoupled fully connected attention,DFC attention),其信息流如下图所示: +其中,$F^{H}$ 和 $F^{W}$ 是变换的权重。输入原始特征 Z,并依次应用公式(2)和公式(3),分别提取沿两个方向的长距离依赖关系。作者将此操作称为解耦全连接注意力(decoupled fully connected attention,DFC attention),其信息流如下图所示: ![Ghost bottleneck](images/08.ghostnet_03.png) diff --git a/04Inference/02Mobilenet/09MobileVit.md b/04Inference/02Mobilenet/09MobileVit.md index b2590fb6..1077c83f 100644 --- a/04Inference/02Mobilenet/09MobileVit.md +++ b/04Inference/02Mobilenet/09MobileVit.md @@ -8,7 +8,7 @@ **MobileVit V1** :MobileViT 是一种基于 ViT(Vision Transformer)架构的轻量级视觉模型,旨在适用于移动设备和嵌入式系统。ViT 是一种非常成功的神经网络模型,用于图像分类和其他计算机视觉任务,但通常需要大量的计算资源和参数。MobileViT 的目标是在保持高性能的同时,减少模型的大小和计算需求,以便在移动设备上运行,据作者介绍,这是第一次基于轻量级 CNN 网络性能的轻量级 ViT 工作,性能 SOTA。性能优于 MobileNetV3、CrossviT 等网络。 -### Mobile ViT 块 +### Mobile ViT 块 标准卷积涉及三个操作:展开+局部处理+折叠,利用 Transformer 将卷积中的局部建模替换为全局建模,这使得 MobileViT 具有 CNN 和 ViT 的性质。MobileViT Block 如下图所示: @@ -255,7 +255,7 @@ class MobileViTBlock(nn.Module): return fm ``` -### 多尺度采样训练 +### 多尺度采样训练 在基于 ViT 的模型中,学习多尺度表示的标准方法是微调。例如,在不同尺寸上对经过 224×224 空间分辨率训练的 DeiT 模型进行了独立微调。由于位置嵌入需要根据输入大小进行插值,而网络的性能受插值方法的影响,因此这种学习多尺度表示的方法对 vit 更有利。与 CNN 类似,MobileViT 不需要任何位置嵌入,它可以从训练期间的多尺度输入中受益。 @@ -378,7 +378,7 @@ class InvertedResidual(nn.Module): ### 可分离的自注意力 MHA(下图 a)允许 Transformer 对 tokens 间的关系进行编码。具体来说,MHA 将输入喂到三个分支,即查询 Q、键 K 和值 V。每个分支(Q、K 和 V)由输入 $x\in R^{k \times d}$ 组成,其中包含 k 个 d 维 tokens(或 patches)嵌入。每个分支包含(Q、K 和 V)包含 h 个头(或层),可以使 Transformer 学习输入的多个视角。然后将输入 x 馈入所有 h 个头,然后进行 softmax 操作 σ 以在 Q 和 K 的线性层的输出之间产生注意力(或上下文映射)点积,然后同时计算矩阵 - $a\in R^{k\times k \times h}$。然后在 a 和 V 线性层的输出之间计算另一个点积,以产生加权和输出 $y_{w}\in R^{k\times d_{h}\times h}$,其中 $d_{h}=\frac{d}{h}$ 是头部尺寸。这 h 个头的输出被连接起来产生一个带有 k 个 d 维 tokens 的张量, 馈送到另一个具有权重 $W_{o}\in R^{d \times d}$ 的线性层以产生 MHA $y \in R^{k \times d}$ 的输出。然后在数学上,这个操作可以描述为: + $a\in R^{k\times k \times h}$。然后在 a 和 V 线性层的输出之间计算另一个点积,以产生加权和输出 $y_{w}\in R^{k\times d_{h}\times h}$,其中 $d_{h}=\frac{d}{h}$ 是头部尺寸。这 h 个头的输出被连接起来产生一个带有 k 个 d 维 tokens 的张量,馈送到另一个具有权重 $W_{o}\in R^{d \times d}$ 的线性层以产生 MHA $y \in R^{k \times d}$ 的输出。然后在数学上,这个操作可以描述为: $$ y=Concat\Bigg(\underbrace{<σ(),xW_{v}^{0}>}_{a^{0} \in R^{k \times k}}..., \underbrace{<σ(),xW_{v}^{h}>}_{a^{h} \in R^{k \times k}} @@ -399,7 +399,7 @@ $$ 上下文向量 $c_{v}$ 在某种意义上类似等式(1)中的注意力矩阵 a,它也编码输入 x 中所有 tokens 的信息,但计算成本较低。 -$c_{v}$ 中编码的上下文信息与 x 中的所有 tokens 共享。为此,输入 x 然后通过广播的逐元素乘法运算传播到 $x_{V}$。结果输出后跟 ReLU 激活函数以产生输出 $x_{V} \in R^{k\times d }$。$c_{v}$ 中的上下文信息使用权重 $W_{v} \in R^{d\times d}$ 的值分支 V 线性映射到 d 维空间, 然后将其馈送到权重 $W_{o} \in R^{d \times d}$ 的另一个线性层以产生最终输出 $y \in R^{k \times d}$。在数学上,可分离自注意力可以定义为: +$c_{v}$ 中编码的上下文信息与 x 中的所有 tokens 共享。为此,输入 x 然后通过广播的逐元素乘法运算传播到 $x_{V}$。结果输出后跟 ReLU 激活函数以产生输出 $x_{V} \in R^{k\times d }$。$c_{v}$ 中的上下文信息使用权重 $W_{v} \in R^{d\times d}$ 的值分支 V 线性映射到 d 维空间,然后将其馈送到权重 $W_{o} \in R^{d \times d}$ 的另一个线性层以产生最终输出 $y \in R^{k \times d}$。在数学上,可分离自注意力可以定义为: $$ y = \Bigg( \underbrace{\sum \bigg( \overbrace{σ(xW_{I})}^{c_{s}\in R^{k}} *xW_{K} \bigg)}_{c_{v} \in R^{d}}*ReLU(xW_{V}) \Bigg ) @@ -423,7 +423,7 @@ $$ ### 网络结构 -为了证明所提出的可分离自注意力在资源受限设备上的有效性,将可分离自注意力与最近基于 ViT 的模型 MobileViT 相结合。 MobileViT 是一个轻量级、对移动设备友好的混合网络,其性能明显优于其他基于 CNN、基于 Transformer 或混合模型的竞争模型,包括 MobileNets。 +为了证明所提出的可分离自注意力在资源受限设备上的有效性,将可分离自注意力与最近基于 ViT 的模型 MobileViT 相结合。MobileViT 是一个轻量级、对移动设备友好的混合网络,其性能明显优于其他基于 CNN、基于 Transformer 或混合模型的竞争模型,包括 MobileNets。 MobileViTv2 将 MobileViTv1 中的 Transformer 块中的 MHA 替换为提出的可分离自注意力方法。也没有在 MobileViT 块中使用 skip-connection 连接和融合块,因为它略微提高了性能。此外,为了创建不同复杂度的 MobileViTv2 模型,我们使用宽度乘数 α ∈ {0.5, 2.0} 统一缩放 MobileViTv2 网络的宽度。这与为移动设备训练三种特定架构(XXS、XS 和 S)的 MobileViTv1 形成对比。 diff --git a/04Inference/02Mobilenet/10MobileFormer.md b/04Inference/02Mobilenet/10MobileFormer.md index f043b93a..60883d29 100644 --- a/04Inference/02Mobilenet/10MobileFormer.md +++ b/04Inference/02Mobilenet/10MobileFormer.md @@ -22,7 +22,7 @@ $$ A_{X->Z} = [Attn(\widetilde{z_{i}}W_{i}^{Q},\widetilde{x_{i}},\widetilde{x_{i}})]_{i=1:h}W^{o}\tag{1} $$ -其中局部特征 X 和全局 tokens Z 被拆分进入 h 个头,即 $X=[\widetilde{x_{1}}...\widetilde{x_{h}}],Z=[\widetilde{z_{1}}...\widetilde{z_{h}}]$ 表示多头注意力。第 i 个头的拆分 $\widetilde{z_{1}}\in R^{M \times \frac {d}{h} }$ 与第 i 个 token$\widetilde{z_{1}}\in R^{d}$ 不同。$W_{i}^{Q}$ 是第 i 个头的查询映射矩阵。 $W^{O}$ 用于将多个头组合在一起。Attn(Q,K,V)是查询 Q、键 K 和值 V 的标准注意力函数,即 : +其中局部特征 X 和全局 tokens Z 被拆分进入 h 个头,即 $X=[\widetilde{x_{1}}...\widetilde{x_{h}}],Z=[\widetilde{z_{1}}...\widetilde{z_{h}}]$ 表示多头注意力。第 i 个头的拆分 $\widetilde{z_{1}}\in R^{M \times \frac {d}{h} }$ 与第 i 个 token$\widetilde{z_{1}}\in R^{d}$ 不同。$W_{i}^{Q}$ 是第 i 个头的查询映射矩阵。$W^{O}$ 用于将多个头组合在一起。Attn(Q,K,V)是查询 Q、键 K 和值 V 的标准注意力函数,即 : $$softmax(\frac{QK^{T}}{\sqrt{d_{k}}}) $$ @@ -49,9 +49,9 @@ Former 子块:Former 子块是一个标准的 Transformer 块,包括一个 Mobile→Former:文章提出的轻量级交叉注意力(式 1)用于将局部特征 X 融合到全局特征 tokens Z。与标准注意力相比,映射矩阵的键 $W^{K}$ 和值 $W^{V}$(在局部特征 X 上)被移除以节省计算(见上图)。 -Mobile←Former:这里的交叉注意力(式 2) 与 Mobile→Former 的方向相反,其将全局 tokens 融入本地特征。局部特征是查询,全局 tokens 是键和值。因此,我们保留键 $W^{K}$ 和值 $W^{V}$ 中的映射矩阵,但移除查询 $W^{Q}$ 的映射矩阵以节省计算,如上图所示。 +Mobile←Former:这里的交叉注意力(式 2)与 Mobile→Former 的方向相反,其将全局 tokens 融入本地特征。局部特征是查询,全局 tokens 是键和值。因此,我们保留键 $W^{K}$ 和值 $W^{V}$ 中的映射矩阵,但移除查询 $W^{Q}$ 的映射矩阵以节省计算,如上图所示。 -计算复杂度:Mobile-Former 块的四个核心部分具有不同的计算成本。给定输入大小为 $HW\timesC$ 的特征图,以及尺寸为 d 的 M 个全局 tokens,Mobile 占据了大部分的计算量 $O(HWC^{2})$。Former 和双线桥是重量级的,占据不到总计算成本的 20%。具体而言,Former 的自注意力和 FFN 具有复杂度 $O(M^{2}d+Md^{2})$。 Mobile→Former 和 Mobile←Former 共享交叉注意力的复杂度 $O(MHWC+MdC)$。 +计算复杂度:Mobile-Former 块的四个核心部分具有不同的计算成本。给定输入大小为 $HW\timesC$ 的特征图,以及尺寸为 d 的 M 个全局 tokens,Mobile 占据了大部分的计算量 $O(HWC^{2})$。Former 和双线桥是重量级的,占据不到总计算成本的 20%。具体而言,Former 的自注意力和 FFN 具有复杂度 $O(M^{2}d+Md^{2})$。Mobile→Former 和 Mobile←Former 共享交叉注意力的复杂度 $O(MHWC+MdC)$。 **代码** diff --git a/04Inference/02Mobilenet/11EfficientFormer.md b/04Inference/02Mobilenet/11EfficientFormer.md index 5a69165c..db7c2c85 100644 --- a/04Inference/02Mobilenet/11EfficientFormer.md +++ b/04Inference/02Mobilenet/11EfficientFormer.md @@ -140,7 +140,7 @@ $$ ### 网络结构 -EfficientFormer 一共有 4 个阶段。每个阶段都有一个 Embeding(两个 3x3 的 Conv 组成一个 Embeding) 来投影 Token 长度(可以理解为 CNN 中的 feature map)。EfficientFormer 是一个完全基于 Transformer 设计的模型,并没有集成 MobileNet 相关内容。最后通过 AUTOML 来搜索 MB_3D 和 MB_4D block 相关参数。最后堆叠 block 形成最终网络。 +EfficientFormer 一共有 4 个阶段。每个阶段都有一个 Embeding(两个 3x3 的 Conv 组成一个 Embeding)来投影 Token 长度(可以理解为 CNN 中的 feature map)。EfficientFormer 是一个完全基于 Transformer 设计的模型,并没有集成 MobileNet 相关内容。最后通过 AUTOML 来搜索 MB_3D 和 MB_4D block 相关参数。最后堆叠 block 形成最终网络。 **代码** @@ -335,7 +335,7 @@ EfficientFormerV2 相对于 EfficientFormer 的主要改进如下图所示。 通过统一的 FFN 和删除残差连接的 token mixer,V2 检查来自 EfficientFormer 的搜索空间是否仍然足够,特别是在深度方面。论文改变了网络深度(每个阶段中的 block 数)和宽度(通道数),并发现更深和更窄的网络会带来更好的精度(0.2%的改进)、更少的参数(0.3M 的减少)和更少的耗时(0.1ms 的加速),如上表所示。因此,论文将此网络设置为新的基线(精度 80.5%),以验证后续的设计修改,并为架构搜索提供更深入的超网络。 -此外,具有进一步缩小的空间分辨率(1/64)的 5 阶段模型已广泛用于有效的 ViT 工作。为了证明是否应该从一个 5 阶段超网络中搜索,论文在当前的基线网络中添加了一个额外的阶段,并验证了性能增益和开销。值得注意的是,尽管考虑到小的特征分辨率,计算开销不是一个问题,但附加阶段是参数密集型的。因此需要缩小网络维度(深度或宽度),以将参数和延迟与基线模型对齐,以便进行公平比较。如上表所示,尽管节省了 MACs(0.12G),但 5 阶段模型的最佳性能在更多参数(0.39M)和延迟开销(0.2ms)的情况下意外降至 80.31%。这符合我们的直觉,即五阶段计算效率高,但参数密集。鉴于 5 阶段网络无法在现有的规模和速度范围内引入更多潜力,论文坚持 4 阶段设计。这一分析也解释了为什么某些 ViT 在 MACs 精度方面提供了出色的 Pareto curve,但在大小上往往非常冗余。作为最重要的一点,优化单一度量很容易陷入困境。 +此外,具有进一步缩小的空间分辨率(1/64)的 5 阶段模型已广泛用于有效的 ViT 工作。为了证明是否应该从一个 5 阶段超网络中搜索,论文在当前的基线网络中添加了一个额外的阶段,并验证了性能增益和开销。值得注意的是,尽管考虑到小的特征分辨率,计算开销不是一个问题,但附加阶段是参数密集型的。因此需要缩小网络维度(深度或宽度),以将参数和延迟与基线模型对齐,以便进行公平比较。如上表所示,尽管节省了 MACs(0.12G),但 5 阶段模型的最佳性能在更多参数(0.39M)和延迟开销(0.2ms)的情况下意外降至 80.31%。这符合直觉,即五阶段计算效率高,但参数密集。鉴于 5 阶段网络无法在现有的规模和速度范围内引入更多潜力,论文坚持 4 阶段设计。这一分析也解释了为什么某些 ViT 在 MACs 精度方面提供了出色的 Pareto curve,但在大小上往往非常冗余。作为最重要的一点,优化单一度量很容易陷入困境。 **代码** @@ -527,7 +527,7 @@ class Attention4D(torch.nn.Module): ``` ```python -#AttnFFN(Local Global 模块) +#AttnFFN(Local Global 模块) class AttnFFN(nn.Module): def __init__(self, dim, mlp_ratio=4., act_layer=nn.ReLU, norm_layer=nn.LayerNorm, @@ -781,7 +781,7 @@ $$ ### 网络结构 -对于模型大小,EfficientFormerV 2-S0 比 EdgeViT-XXS 超出了 1.3%的 top-1 精度,甚至少了 0.6M 参数,比 MobileNetV 2 ×1.0 优于 3.5%的 top-1,参数数量相似。对于大模型,EfficientFormerV 2-L 模型实现了与最近的 EfficientFormerL 7 相同的精度,同时小 3.1 倍。在速度方面,在延迟相当或更低的情况下,EfficientFormerV2-S2 的性能分别优于 UniNet-B1,EdgeViT-S 和 EfficientFormerL 1,分别为 0.8%,0.6%和 2.4%。 EiffcientFormer V2-S1 的效率分别比 MobileViT-XS、EdgeViT-XXS 和 EdgeViTXS 高出 4.2%、4.6%和 1.5%,其中 MES 要高得多。 +对于模型大小,EfficientFormerV 2-S0 比 EdgeViT-XXS 超出了 1.3%的 top-1 精度,甚至少了 0.6M 参数,比 MobileNetV 2 ×1.0 优于 3.5%的 top-1,参数数量相似。对于大模型,EfficientFormerV 2-L 模型实现了与最近的 EfficientFormerL 7 相同的精度,同时小 3.1 倍。在速度方面,在延迟相当或更低的情况下,EfficientFormerV2-S2 的性能分别优于 UniNet-B1,EdgeViT-S 和 EfficientFormerL 1,分别为 0.8%,0.6%和 2.4%。EiffcientFormer V2-S1 的效率分别比 MobileViT-XS、EdgeViT-XXS 和 EdgeViTXS 高出 4.2%、4.6%和 1.5%,其中 MES 要高得多。 ## 小结与思考 diff --git a/04Inference/03Slim/01Introduction.md b/04Inference/03Slim/01Introduction.md index 02ecdf2e..b8f8436d 100644 --- a/04Inference/03Slim/01Introduction.md +++ b/04Inference/03Slim/01Introduction.md @@ -24,7 +24,7 @@ 此外,模型压缩算法分为低成本和高成本算法,与上述分类标准无关。高成本的压缩算法需要基于大型数据集进行再训练过程。因此,它们可以生成更准确的压缩模型,但需要更多的时间来压缩模型。另一方面,低成本压缩算法仅基于少量校准数据执行简单的权重调整过程,但是需要注意可能的精度损失,因此训练后量化是常用的低成本压缩算法。 -## 模型压缩流程 +## 模型压缩流程 如下图所示,模型压缩通常处于机器学习模型训练和生产部署之间的阶段。它在模型训练完成后,准备将模型部署到目标环境之前进行。 diff --git a/04Inference/03Slim/02Quant.md b/04Inference/03Slim/02Quant.md index dec5f6a3..2962c903 100644 --- a/04Inference/03Slim/02Quant.md +++ b/04Inference/03Slim/02Quant.md @@ -194,7 +194,7 @@ $$ ## 小结与思考 -- 低比特量化原理:将浮点数表示的模型参数转换为低比特整数(如INT8或INT4)以减少模型大小、内存消耗和推理延迟,尽管可能会牺牲一定精度。 +- 低比特量化原理:将浮点数表示的模型参数转换为低比特整数(如 INT8 或 INT4)以减少模型大小、内存消耗和推理延迟,尽管可能会牺牲一定精度。 - 神经网络量化优势:量化可以显著减少模型参数量,加速计算,节省内存,并降低能耗和芯片面积需求,适合推理场景。 diff --git a/04Inference/03Slim/03QAT.md b/04Inference/03Slim/03QAT.md index 727f64e5..ce72bc94 100644 --- a/04Inference/03Slim/03QAT.md +++ b/04Inference/03Slim/03QAT.md @@ -3,7 +3,7 @@ # 感知量化训练 QAT ======== 一句话介绍本节内容 -======== 感知量化训练上次说这个内容过于简单,需要继续拓展和深入一下的,参考我的文章https://blog.csdn.net/m0_37046057/article/details/122356151 +======== 感知量化训练上次说这个内容过于简单,需要继续拓展和深入一下的,参考我的文章 https://blog.csdn.net/m0_37046057/article/details/122356151 ## 感知量化训练流程 @@ -90,7 +90,7 @@ $$ ![反向传播梯度计算](images/03QAT05.png) -## 感知量化训练的技巧 +## 感知量化训练的技巧 1. 从已校准的表现最佳的 PTQ 模型开始 diff --git a/04Inference/03Slim/04PTQ.md b/04Inference/03Slim/04PTQ.md index b355d683..cf650aba 100644 --- a/04Inference/03Slim/04PTQ.md +++ b/04Inference/03Slim/04PTQ.md @@ -2,7 +2,7 @@ # 训练后量化与部署(DONE) -本节将会重点介绍训练后量化技术的两种方式:动态和静态方法,将模型权重和激活从浮点数转换为整数,以减少模型大小和加速推理。并以KL散度作为例子讲解校准方法和量化粒度控制来平衡模型精度和性能。 +本节将会重点介绍训练后量化技术的两种方式:动态和静态方法,将模型权重和激活从浮点数转换为整数,以减少模型大小和加速推理。并以 KL 散度作为例子讲解校准方法和量化粒度控制来平衡模型精度和性能。 ## 训练后量化的方式 @@ -46,9 +46,9 @@ $$ | 量化方法 | 方法详解 | | -------- | ------------------------------------------------------------ | -| $abs_{max}$ | 选取所有激活值的绝对值的最大值作为截断值α。此方法的计算最为简单,但是容易受到某些绝对值较大的极端值的影响,适用于几乎不存在极端值的情况。 | -| $KL$ | 使用参数在量化前后的 KL 散度作为量化损失的衡量指标。此方法是 TensorRT 所使用的方法。在大多数情况下,使用 KL 方法校准的表现要优于 abs_max 方法。 | -| $avg $ | 选取所有样本的激活值的绝对值最大值的平均数作为截断值α。此方法计算较为简单,可以在一定程度上消除不同数据样本的激活值的差异,抵消一些极端值影响,总体上优于 abs_max 方法。 | +| $abs_{max}$ | 选取所有激活值的绝对值的最大值作为截断值α。此方法的计算最为简单,但是容易受到某些绝对值较大的极端值的影响,适用于几乎不存在极端值的情况。| +| $KL$ | 使用参数在量化前后的 KL 散度作为量化损失的衡量指标。此方法是 TensorRT 所使用的方法。在大多数情况下,使用 KL 方法校准的表现要优于 abs_max 方法。| +| $avg $ | 选取所有样本的激活值的绝对值最大值的平均数作为截断值α。此方法计算较为简单,可以在一定程度上消除不同数据样本的激活值的差异,抵消一些极端值影响,总体上优于 abs_max 方法。| ### 量化粒度 @@ -211,9 +211,9 @@ $$ ## 小结与思考 -- 训练后量化方式:分为动态离线量化(PTQ Dynamic)和静态离线量化(PTQ Static)。动态量化将权重从FP32映射到INT8/16,而激活在运行时量化,可能导致性能下降;静态量化使用少量校准数据计算量化比例因子,无需重新训练,快速获得量化模型。 +- 训练后量化方式:分为动态离线量化(PTQ Dynamic)和静态离线量化(PTQ Static)。动态量化将权重从 FP32 映射到 INT8/16,而激活在运行时量化,可能导致性能下降;静态量化使用少量校准数据计算量化比例因子,无需重新训练,快速获得量化模型。 -- KL散度校准法:一种静态量化方法,通过最小化量化分布和FP32分布之间的KL散度来确定最优的量化阈值,适用于选择量化比例因子。 +- KL 散度校准法:一种静态量化方法,通过最小化量化分布和 FP32 分布之间的 KL 散度来确定最优的量化阈值,适用于选择量化比例因子。 - 端侧量化推理部署:涉及量化、反量化和重量化操作,根据输入输出数据类型和硬件平台要求,选择不同的推理结构和量化策略,以实现模型在端侧设备上的高效部署。 diff --git a/04Inference/04Converter/01Introduction.md b/04Inference/04Converter/01Introduction.md index 15f0b280..a087a218 100644 --- a/04Inference/04Converter/01Introduction.md +++ b/04Inference/04Converter/01Introduction.md @@ -26,9 +26,9 @@ 1. AI 框架算子的统一 -神经网络模型本身包含众多算子,它们的重合度高但不完全相同。推理引擎需要用有限的算子去实现不同框架的算子。 +神经网络模型本身包含众多算子,它们的重合度高但不完全相同。推理引擎需要用有限的算子去实现不同框架的算子。 -| 框架 | 导出方式 | 导出成功率 | 算子数(不完全统计) | 冗余度 | +| 框架 | 导出方式 | 导出成功率 | 算子数(不完全统计)| 冗余度 | |:----------:|:------------:|:-----:|:----------:|:---:| | Caffe | Caffe | 高 | 52 | 低 | | TensorFlow | I.X | 高 | 1566 | 高 | @@ -273,7 +273,7 @@ Converter 转换模块由前端转换部分 Frontends 和图优化部分 Graph O - 推理引擎架构:包含优化阶段和运行阶段,优化阶段负责模型转换和图优化,运行阶段则涉及模型的实际加载与执行,包括调度与执行两层。 -- 转换模块挑战:包括AI框架算子的统一、不同框架模型文件格式的支持、主流网络结构的适配,以及各类输入输出的兼容。 +- 转换模块挑战:包括 AI 框架算子的统一、不同框架模型文件格式的支持、主流网络结构的适配,以及各类输入输出的兼容。 - 优化模块目标:通过消除结构冗余、精度冗余、算法冗余和读写冗余,提高模型的效率和性能,同时保持模型的准确性和功能性。 diff --git a/04Inference/04Converter/02Principle.md b/04Inference/04Converter/02Principle.md index 7ee81b2c..3b54784c 100644 --- a/04Inference/04Converter/02Principle.md +++ b/04Inference/04Converter/02Principle.md @@ -110,7 +110,7 @@ model.eval() `torch.save` 将序列化对象保存到磁盘。该函数使用 Python 的 pickle 实用程序进行序列化。使用此函数可以保存各种对象的模型、张量和字典。 -`torch.nn.Module.load_state_dict` 使用反序列化的 state_dict 加载模型的参数字典 。在 PyTorch 中,模型的可学习参数(即权重和偏差) `torch.nn.Module` 包含在模型的参数中 (通过访问 `model.parameters()`)。 +`torch.nn.Module.load_state_dict` 使用反序列化的 state_dict 加载模型的参数字典 。在 PyTorch 中,模型的可学习参数(即权重和偏差)`torch.nn.Module` 包含在模型的参数中 (通过访问 `model.parameters()`)。 `state_dict`只是一个 Python 字典对象,它将每个层映射到其参数张量。请注意,只有具有可学习参数的层(卷积层、线性层等)和注册缓冲区(batchnorm 的 running_mean)在模型的`state_dict`中具有条目。 @@ -196,7 +196,7 @@ message Net{ // message 属于 Net 域; - required:一个格式良好的消息一定要含有 1 个这种字段。表示该值是必须要设置的。 - optional:消息格式中该字段可以有 0 个或 1 个值(不超过 1 个)。 - - repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括 0 次)。重复的值的顺序会被保留。表示该值可以重复,相当于 java 中的 List。 + - repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括 0 次)。重复的值的顺序会被保留。表示该值可以重复,相当于 java 中的 List。 #### Protobuf 例子 @@ -315,7 +315,7 @@ root_type Monster; 很多 AI 推理框架都是用的 FlatBuffers,最主要的有以下两个: -**[MNN](https://github.com/alibaba/MNN/blob/master/README_CN.md):** 阿里巴巴的深度神经网络推理引擎,是一个轻量级的深度神经网络引擎,支持深度学习的推理与训练。适用于服务器/个人电脑/手机/嵌入式各类设备。目前,MNN 已经在阿里巴巴的手机淘宝、手机天猫、优酷等 30 多个 App 中使用,覆盖直播、短视频、搜索推荐、商品图像搜索、互动营销、权益发放、安全风控等场景。 MNN 模型文件采用的存储结构是 FlatBuffers。 +**[MNN](https://github.com/alibaba/MNN/blob/master/README_CN.md):** 阿里巴巴的深度神经网络推理引擎,是一个轻量级的深度神经网络引擎,支持深度学习的推理与训练。适用于服务器/个人电脑/手机/嵌入式各类设备。目前,MNN 已经在阿里巴巴的手机淘宝、手机天猫、优酷等 30 多个 App 中使用,覆盖直播、短视频、搜索推荐、商品图像搜索、互动营销、权益发放、安全风控等场景。MNN 模型文件采用的存储结构是 FlatBuffers。 **[MindSpore Lite](https://www.mindspore.cn/lite/en):** 一种适用于端边云场景的新型开源深度学习训练/推理框架,提供离线转换模型功能的工具,支持多种类型的模型转换,转换后的模型可用于推理。除了基本的模型转换功能之外,还支持用户对模型进行自定义的优化与构建,生成用户自定义算子的模型。 @@ -332,18 +332,18 @@ MindSpore Lite 提供了一套注册机制,允许用户基于转换工具进 | 支持语言 | C/C++, C#, Go, Java, Python, Ruby, Objective-C, Dart | C/C++, C#, Go, Java, JavaScript, TypeScript, Lua, PHP, Python, Rust, Lobster | | 版本 | 2.x/3.x,不相互兼容 | 1.x | | 协议文件 | .proto,需指定协议文件版本 | .fbs | -| 代码生成工具 | 有(生成代码量较多) | 有(生成代码量较少) | +| 代码生成工具 | 有(生成代码量较多) | 有(生成代码量较少)| | 协议字段类型 | bool, bytes, int32, int64, uint32, uint64, sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, float, double, string | bool, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float, double, string, vector | ## 小结 - 模型序列化:模型序列化是将训练好的模型从内存中保存到硬盘上,以供将来使用的必要步骤,涉及将模型的参数、结构等信息存储到文件中。 -- 序列化分类:序列化方法分为跨平台跨语言通用序列化方法(如XML、JSON、Protocol Buffers和Flatbuffers)、模型本身提供的自定义序列化方法、语言级通用序列化方法(如Python的pickle和joblib、R的rda)以及用户自定义序列化方法。 +- 序列化分类:序列化方法分为跨平台跨语言通用序列化方法(如 XML、JSON、Protocol Buffers 和 Flatbuffers)、模型本身提供的自定义序列化方法、语言级通用序列化方法(如 Python 的 pickle 和 joblib、R 的 rda)以及用户自定义序列化方法。 -- Pytorch模型序列化:PyTorch提供了基于内部格式和ONNX的序列化方法。内部格式通过torch.save和torch.load实现模型状态的保存与加载,而ONNX通过torch.onnx.export导出模型,支持不同框架和平台之间的模型转换与部署。 +- Pytorch 模型序列化:PyTorch 提供了基于内部格式和 ONNX 的序列化方法。内部格式通过 torch.save 和 torch.load 实现模型状态的保存与加载,而 ONNX 通过 torch.onnx.export 导出模型,支持不同框架和平台之间的模型转换与部署。 -- 目标文件格式:Protobuf和FlatBuffers是两种流行的目标文件格式。Protobuf是一种高效、与语言无关的数据序列化机制,而FlatBuffers提供了无需解析即可直接访问序列化数据的能力,适合性能要求高的应用场景。 +- 目标文件格式:Protobuf 和 FlatBuffers 是两种流行的目标文件格式。Protobuf 是一种高效、与语言无关的数据序列化机制,而 FlatBuffers 提供了无需解析即可直接访问序列化数据的能力,适合性能要求高的应用场景。 ## 本节视频 diff --git a/04Inference/04Converter/03IR.md b/04Inference/04Converter/03IR.md index a936852b..54752c5f 100644 --- a/04Inference/04Converter/03IR.md +++ b/04Inference/04Converter/03IR.md @@ -60,7 +60,7 @@ N 个输入张量经过算子的计算产生 M 个输出张量。举例来说, > (OpenVINO)推理引擎是一组 C++库,提供通用 API,可在您选择的平台(CPU、GPU 或 VPU)上提供推理解决方案。使用推理引擎 API 读取中间表示(IR)、设置输入和输出格式并在设备上执行模型。虽然 C++库是主要实现,但 C 库和 Python bindings(通过 Python 调用 C/C++ 库)也可用。 -计算图是实现高效推理和跨平台部署的关键。计算图的标准化表示使得推理引擎能够在不同硬件平台上进行高效部署。在推理过程中,模型通常会被转换为一种中间表示(IR)。这种表示形式能够抽象出模型的计算过程,使得模型能够在不同硬件平台上高效执行。 推理引擎通过分析计算图,能够识别和优化常见的算子模式。例如,连续的算子可以进行融合,减少计算开销和内存访问次数,从而提高推理速度。 +计算图是实现高效推理和跨平台部署的关键。计算图的标准化表示使得推理引擎能够在不同硬件平台上进行高效部署。在推理过程中,模型通常会被转换为一种中间表示(IR)。这种表示形式能够抽象出模型的计算过程,使得模型能够在不同硬件平台上高效执行。推理引擎通过分析计算图,能够识别和优化常见的算子模式。例如,连续的算子可以进行融合,减少计算开销和内存访问次数,从而提高推理速度。 推理引擎还可以通过计算图精确管理内存的分配和使用,减少内存碎片和重复数据拷贝。同时,计算图明确了各个算子的执行顺序和依赖关系,推理引擎可以据此进行高效的任务调度,最大化利用多核处理器和并行计算资源。 @@ -74,7 +74,7 @@ AI 框架计算图与推理引擎计算图在多个方面存在差异: 3. **分布式并行:** 在训练场景中,AI 框架的计算图通常支持各种分布式并行策略,以加速模型的训练过程。这包括数据并行、张量并行、流水线并行等,并行切分策略,可以利用 AI 集群计算中心的多个计算节点来同时处理大规模的训练数据和计算任务,以提高训练效率和扩展性。在推理场景中,推理引擎计算图往往以单卡推理服务为主,很少考虑分布式推理。因为推理任务通常是针对单个输入进行的,并不需要大规模的并行化处理。相反,推理引擎更注重于模型工业级部署应用,在对外提供服务时,需要保证推理任务的高效执行和低延迟响应。 -4. **使用场景:** AI 框架计算图主要用于模型的训练场景,适用于科研创新、模型训练和微调等场景。研究人员可以利用 AI 框架的计算图来构建、训练和优化各种类型的神经网络模型,以提高模型的性能和精度。 推理引擎计算图主要用于模型的推理场景,适合模型的工业级部署应用。推理引擎计算图注重模型的高效执行和低延迟响应,在对外提供服务时需要保证推理任务的快速执行和稳定性。 +4. **使用场景:** AI 框架计算图主要用于模型的训练场景,适用于科研创新、模型训练和微调等场景。研究人员可以利用 AI 框架的计算图来构建、训练和优化各种类型的神经网络模型,以提高模型的性能和精度。推理引擎计算图主要用于模型的推理场景,适合模型的工业级部署应用。推理引擎计算图注重模型的高效执行和低延迟响应,在对外提供服务时需要保证推理任务的快速执行和稳定性。 ![AI 框架计算图 vs 推理引擎计算图](images/03IR03.png) @@ -112,12 +112,12 @@ DT_INT8 = 6, **Tensor 数据内存排布格式:** 即张量在内存中的存储顺序,不同的框架和算法可能使用不同的数据排布格式来表示张量数据。 -- ND: 表示多维数组(N-dimensional),即没有特定的数据排布格式,各维度的数据顺序不固定。 -- NCHW: 表示通道-高度-宽度的排布格式,通常在卷积神经网络中使用,数据按照批次(Batch)、通道(Channel)、高度(Height)、宽度(Width)的顺序排列。 -- NHWC: 表示高度-宽度-通道的排布格式,通常在某些框架中使用,数据按照批次、高度、宽度、通道的顺序排列。 -- NC4HW4: 表示通道-高度-宽度的排布格式,通常在某些硬件加速器(如 GPU)中使用,数据按照批次、通道、高度、宽度的顺序排列,同时对通道和高度宽度做了 4 的倍数的扩展。 -- NC1HWC0: 表示通道-1-高度-宽度-0 的排布格式,通常在某些硬件加速器(如 Ascend 芯片)中使用,数据按照批次、通道、高度、宽度的顺序排列,并对高度和宽度做了一定的扩展。 -- UNKNOWN: 表示未知的数据排布格式,可能由于某些特定的需求或算法而无法归类到已知的排布格式中。 +- ND:表示多维数组(N-dimensional),即没有特定的数据排布格式,各维度的数据顺序不固定。 +- NCHW:表示通道-高度-宽度的排布格式,通常在卷积神经网络中使用,数据按照批次(Batch)、通道(Channel)、高度(Height)、宽度(Width)的顺序排列。 +- NHWC:表示高度-宽度-通道的排布格式,通常在某些框架中使用,数据按照批次、高度、宽度、通道的顺序排列。 +- NC4HW4:表示通道-高度-宽度的排布格式,通常在某些硬件加速器(如 GPU)中使用,数据按照批次、通道、高度、宽度的顺序排列,同时对通道和高度宽度做了 4 的倍数的扩展。 +- NC1HWC0:表示通道-1-高度-宽度-0 的排布格式,通常在某些硬件加速器(如 Ascend 芯片)中使用,数据按照批次、通道、高度、宽度的顺序排列,并对高度和宽度做了一定的扩展。 +- UNKNOWN:表示未知的数据排布格式,可能由于某些特定的需求或算法而无法归类到已知的排布格式中。 ``` // 定义 Tensor 数据排布格式 diff --git a/04Inference/04Converter/04Detail.md b/04Inference/04Converter/04Detail.md index 81ee6c2d..3e481440 100644 --- a/04Inference/04Converter/04Detail.md +++ b/04Inference/04Converter/04Detail.md @@ -159,7 +159,7 @@ else: print("Model correct") ``` -`onnx.load`函数用于读取一个 ONNX 模型。`onnx.checker.check_model`用于检查模型格式是否正确,如果有错误的话该函数会直接报错。我们的模型是正确的,控制台中应该会打印出"Model correct"。 +`onnx.load`函数用于读取一个 ONNX 模型。`onnx.checker.check_model`用于检查模型格式是否正确,如果有错误的话该函数会直接报错。模型是正确的,控制台中应该会打印出"Model correct"。 使用 Netron(开源的模型可视化工具)来可视化 ONNX 模型: @@ -203,11 +203,11 @@ else: ## 小结与思考 -- 模型转换流程:涉及将神经网络模型从一种框架转换到另一种框架,包括直接转换和通过开放式文件格式如ONNX的规范式转换。 +- 模型转换流程:涉及将神经网络模型从一种框架转换到另一种框架,包括直接转换和通过开放式文件格式如 ONNX 的规范式转换。 - 关键技术细节:在模型转换过程中,需要处理算子兼容性、张量格式差异、参数适配以及计算图优化等问题,以确保模型在目标框架中的性能和精度。 -- 工具与实例:多种模型转换工具如MMdnn和ONNX支持不同框架间的迁移,示例代码展示了如何将PyTorch模型转换为ONNX格式,进而实现跨框架模型部署。 +- 工具与实例:多种模型转换工具如 MMdnn 和 ONNX 支持不同框架间的迁移,示例代码展示了如何将 PyTorch 模型转换为 ONNX 格式,进而实现跨框架模型部署。 ## 本节视频 diff --git a/04Inference/05Optimize/01Optimizer.md b/04Inference/05Optimize/01Optimizer.md index 68d7e0cc..11695997 100644 --- a/04Inference/05Optimize/01Optimizer.md +++ b/04Inference/05Optimize/01Optimizer.md @@ -3,7 +3,7 @@ # 计算图优化 ========== 一句话介绍本节要介绍的内容,下面这段话融合到里面的内容。 -========== 图片按规范命名,文档名字是01Optimizer.md, 那么图片是01Optimizer01.png,这种 +========== 图片按规范命名,文档名字是 01Optimizer.md, 那么图片是 01Optimizer01.png,这种 现在来到推理引擎转换中的图优化模块,这里主要负责实现计算图中的算子融合、布局转换、算子替换、内存优化等非常多不同的类型的优化的 pass,以达到更好的推理效果。 @@ -101,7 +101,7 @@ y = (a + b) * c Extended: 扩展优化仅在运行特定后端,如 CPU、CUDA、NPU 后端执行提供程序时适用。其针对硬件进行特殊且复杂的 Kernel 融合策略和方法。 -示例: CUDA 后端的算子融合,以下是一个简单的计算图优化的例子,通过在 CUDA 中合并加法和乘法操作来实现的。 +示例:CUDA 后端的算子融合,以下是一个简单的计算图优化的例子,通过在 CUDA 中合并加法和乘法操作来实现的。 ```C++ // 优化前:(1)独立的 CUDA 内核实现加法 @@ -209,7 +209,7 @@ result = session.run([output_name], {input_name: dummy_input.numpy()}) ### 优化级别 -ONNX 运行时定义了“ GraphOptimizationLevel”枚举,以确定将启用上述哪些优化级别。 选择一个级别将启用该级别的优化,以及所有先前级别的优化。 例如,启用扩展优化,也将启用基本优化。 这些级别到枚举的映射如下: +ONNX 运行时定义了“ GraphOptimizationLevel”枚举,以确定将启用上述哪些优化级别。选择一个级别将启用该级别的优化,以及所有先前级别的优化。例如,启用扩展优化,也将启用基本优化。这些级别到枚举的映射如下: ```c++ GraphOptimizationLevel::ORT_DISABLE_ALL -> Disable all optimizations diff --git a/04Inference/05Optimize/02Basic.md b/04Inference/05Optimize/02Basic.md index a4a26727..9980947c 100644 --- a/04Inference/05Optimize/02Basic.md +++ b/04Inference/05Optimize/02Basic.md @@ -2,7 +2,7 @@ # 离线图优化技术 -========== 图片按规范命名,文档名字是01Optimizer.md, 那么图片是01Optimizer01.png,这种 +========== 图片按规范命名,文档名字是 01Optimizer.md, 那么图片是 01Optimizer01.png,这种 上一章节主要回顾了计算图优化的各个组成部分,包括基础优化、扩展优化以及布局和内存优化。这些优化方式在预优化阶段、优化阶段和后优化阶段都有所应用,以提高计算效率。同时,还介绍了 AI 框架和推理引擎在图优化方面的不同应用和侧重点。接下来,我们从计算图优化的各个组成部分开始逐步进行讲解。 @@ -56,13 +56,13 @@ Fuse Const To Binary:Binary 折叠,Binary Op 第二个输入是标量 Const 如图所示,我们有两个常量输入,通过两个操作 Op1 和 Op2 进行处理。具体来说,Op1 接收两个常量作为输入,Opened 接收 Op1 的输出作为输入。在离线计算中,我们实际上可以预先计算出这两个常量的结果,然后把这个结果作为一个新的常量输入给 Op2。这种预先计算并替换常量的策略即为常量折叠。 -(2) ExpandDims 折叠: ExpandDims Op 指定维度的输入是常量 Const,则把这个维度以参数的形式折叠到 ExpandDims 算子中。 +(2) ExpandDims 折叠:ExpandDims Op 指定维度的输入是常量 Const,则把这个维度以参数的形式折叠到 ExpandDims 算子中。 ![ExpandDims](image/ExpandDims.png) 在处理计算图优化的过程中,当 ExpandDims 操作的指定维度输入是常量时,我们可以直接将其堆叠进参数,并放在 ExpandDims 这个操作符内部。这样一来,我们就减少了一个操作符的使用。因为常量可能是一个操作符,或者可能占用一块内存空间。 -(3) Binary 折叠: Binary Op 第二个输入是标量 Const ,把这个标量以参数的形式折叠到 Binary Op 的属性中。 +(3) Binary 折叠:Binary Op 第二个输入是标量 Const ,把这个标量以参数的形式折叠到 Binary Op 的属性中。 ![Binary](image/Binary.png) @@ -122,7 +122,7 @@ Binary 折叠其原理与 ExpandDims 的折叠类似。在 Binary 折叠中, 图二:与 Cast 算子类似,UnSqueeze 算子如果在计算图中没有起到实质作用,那么我们也可以选择将其删除,以简化计算图的结构。 -图三:部分情况下,我们的输入数据给到了某个算子 OP1,但是 OP1 的输出并没有被其他的算子接收,这就意味着 OP1 的计算结果没有被利用起来。在这种情况下,我们可以选择删除这个分支,因为其存在并没有实际意义。 +图三:部分情况下,输入数据给到了某个算子 OP1,但是 OP1 的输出并没有被其他的算子接收,这就意味着 OP1 的计算结果没有被利用起来。在这种情况下,我们可以选择删除这个分支,因为其存在并没有实际意义。 图四:在处理 Global pooling 算子的过程中,我们发现它后面接的一些 Reshape 或者 Flatten 算子其实是没有意义的。因为这些算子的存在并不会改变 Global pooling 的输出结果。所以,我们可以选择将这些算子删除,以优化计算图的结构。 @@ -146,18 +146,18 @@ Quant Dequant Eliminate: 连续进行量化和反量化,可同时删除这两 Concat Slice Elimination: 合并后又进行同样的拆分,可同时删除这两个 Op -详细示例如下所示:可参考上述规则,对于存在前后反义算子的情况,进行冗余节点的消除。 +详细示例如下所示:可参考上述规则,对于存在前后反义算子的情况,进行冗余节点的消除。 ![Op 前后反义](image.png) ====== 太笼统了,还是要介绍下图片描述得内容。 ### 公共子图优化 -在一个深度神经网络中,如果几个子图的类型、参数和输入均相同, 则将他们称做公共子图。 对于公共子图, 只需要计算其中一个子图的值, 其他子图的值可以通过赋值得到。这个过程就称作公共子图消除, 它是一种传统编译器中常用的优化手段, 经过迁移也可以应用到深度学习编译器中。 +在一个深度神经网络中,如果几个子图的类型、参数和输入均相同, 则将他们称做公共子图。对于公共子图, 只需要计算其中一个子图的值, 其他子图的值可以通过赋值得到。这个过程就称作公共子图消除, 它是一种传统编译器中常用的优化手段, 经过迁移也可以应用到深度学习编译器中。 Common Subexpression Elimination:当模型当中出现了公共子图,如一个输出是另外两个同类型同参数的 Op 的输入,则可进行删除其中一个 Op。 -基本思路是通过一个 MAP 表, 记录截止当前, 已处理过的同一种类型的 OP。 对于当前正在处理的 OP, 先查找该 MAP 表, 如果能找到其他和正在处理的 OP 类型相同的 OP, 则对他们进行遍历, 如果其中某个 OP 的输入和参数与当前正在处理的 OP 相同, 则它们为公共子表达式, 结果可以互相替代;如果所有 OP 都不能与当前正在处理的 OP 匹配, 则将当前 OP 复制一份返回。 +基本思路是通过一个 MAP 表, 记录截止当前, 已处理过的同一种类型的 OP。对于当前正在处理的 OP, 先查找该 MAP 表, 如果能找到其他和正在处理的 OP 类型相同的 OP, 则对他们进行遍历, 如果其中某个 OP 的输入和参数与当前正在处理的 OP 相同, 则它们为公共子表达式, 结果可以互相替代;如果所有 OP 都不能与当前正在处理的 OP 匹配, 则将当前 OP 复制一份返回。 ![公共子图](image/op_share_graph.png) @@ -209,7 +209,7 @@ def fuse_conv_bn(conv, bn): 2.Conv + Bias + Add:Conv Op 后跟着的 Add 可以融合到 Conv 里的 Bias 参数里面 -在"Conv + Bias + Add"的操作中,假设我们的卷积输出为 X,偏置值为 b,Add 操作的值为 a。那么,这个操作序列的输出结果为:Output = X + b + a。注意到,加法操作满足交换律和结合律,所以我们可以将偏置值 b 和 Add 操作的值 a 进行相加,得到一个新的偏置值 b' = b + a。那么,原本的操作序列就可以简化为 "Conv + Bias",其中 Bias 的值为 b'。 +在"Conv + Bias + Add"的操作中,假设卷积输出为 X,偏置值为 b,Add 操作的值为 a。那么,这个操作序列的输出结果为:Output = X + b + a。注意到,加法操作满足交换律和结合律,所以我们可以将偏置值 b 和 Add 操作的值 a 进行相加,得到一个新的偏置值 b' = b + a。那么,原本的操作序列就可以简化为 "Conv + Bias",其中 Bias 的值为 b'。 3.Conv + Scale + Act:Conv Op 后跟着的 Scale 可以融合到 Conv 里的 Weight 里面 diff --git a/04Inference/05Optimize/03Extend.md b/04Inference/05Optimize/03Extend.md index 60923d47..51fc87fe 100644 --- a/04Inference/05Optimize/03Extend.md +++ b/04Inference/05Optimize/03Extend.md @@ -3,7 +3,7 @@ # 通用图优化技术 ========== 一句话介绍本节要介绍的内容,下面这段话融合到里面的内容。 -========== 图片按规范命名,文档名字是01Optimizer.md, 那么图片是01Optimizer01.png,这种 +========== 图片按规范命名,文档名字是 01Optimizer.md, 那么图片是 01Optimizer01.png,这种 Basic: 基础优化涵盖了所有保留计算图语义的修改,如:O1 常量折叠、O2 冗余节点消除和 O3 有限数量的算子融合。 diff --git a/04Inference/06Kernel/01Introduction.md b/04Inference/06Kernel/01Introduction.md index 46736ac4..3cb3bf6e 100644 --- a/04Inference/06Kernel/01Introduction.md +++ b/04Inference/06Kernel/01Introduction.md @@ -2,7 +2,7 @@ # Kernel 层架构(DONE) -推理引擎的 Kernel 层通常是推理引擎中用于执行底层数学运算的组件。在神经网络模型推理过程中,需要对大量数据进行高效的数学运算,如矩阵乘法、卷积、池化等。Kernel 层就是实现这些运算的核心部分,它直接影响着推理引擎的速度和效率,因此本节将会重点介绍Kernel层相关的内容。 +推理引擎的 Kernel 层通常是推理引擎中用于执行底层数学运算的组件。在神经网络模型推理过程中,需要对大量数据进行高效的数学运算,如矩阵乘法、卷积、池化等。Kernel 层就是实现这些运算的核心部分,它直接影响着推理引擎的速度和效率,因此本节将会重点介绍 Kernel 层相关的内容。 ## Kernel 层介绍 @@ -68,17 +68,17 @@ Kernel 层包含了一系列的低级函数,它们直接在硬件上执行数 CPU 优化: -1. **NEON**: NEON 是 ARM 架构上的 SIMD(单指令多数据)扩展,用于提高多媒体处理和浮点运算的性能。推理引擎可以利用 NEON 指令集来优化 Kernel 层,特别是在移动设备和嵌入式设备上。 -2. **AVX**: AVX(Advanced Vector Extensions)是 Intel 处理器上的 SIMD 指令集,用于提高浮点运算和整数运算的性能。推理引擎可以利用 AVX 指令集来优化 Kernel 层,特别是在 Intel CPU 上。 -3. **Metal**: Metal 是苹果开发的低级图形和计算 API,用于优化在 Apple GPU 上的性能。推理引擎可以利用 Metal API 来优化 Kernel 层,特别是在 iOS 和 macOS 设备上。 -4. **TVM**: TVM(Tensor Virtual Machine)是一个开源的深度学习编译器框架,用于优化神经网络模型在各种硬件上的性能。它支持 CPU、GPU、TPU 和其他类型的硬件。 +1. **NEON**:NEON 是 ARM 架构上的 SIMD(单指令多数据)扩展,用于提高多媒体处理和浮点运算的性能。推理引擎可以利用 NEON 指令集来优化 Kernel 层,特别是在移动设备和嵌入式设备上。 +2. **AVX**:AVX(Advanced Vector Extensions)是 Intel 处理器上的 SIMD 指令集,用于提高浮点运算和整数运算的性能。推理引擎可以利用 AVX 指令集来优化 Kernel 层,特别是在 Intel CPU 上。 +3. **Metal**:Metal 是苹果开发的低级图形和计算 API,用于优化在 Apple GPU 上的性能。推理引擎可以利用 Metal API 来优化 Kernel 层,特别是在 iOS 和 macOS 设备上。 +4. **TVM**:TVM(Tensor Virtual Machine)是一个开源的深度学习编译器框架,用于优化神经网络模型在各种硬件上的性能。它支持 CPU、GPU、TPU 和其他类型的硬件。 GPU 优化: -1. **CUDA**: CUDA 是 NVIDIA 的并行计算平台和编程模型,用于在 NVIDIA GPU 上执行并行计算。推理引擎可以利用 CUDA 来优化 Kernel 层,特别是在大规模矩阵运算和卷积操作方面。 -2. **OpenCL**: OpenCL 是一个开放的标准,用于编写在异构系统上运行的程序。它允许开发者利用 CPU、GPU 和其他类型的处理器来加速计算密集型任务。推理引擎可以利用 OpenCL 来优化 Kernel 层,特别是在 GPU 上。 -3. **Vulkan**: Vulkan 是新一代的图形和计算 API,用于在各种 GPU 上执行并行计算。推理引擎可以利用 Vulkan API 来优化 Kernel 层,特别是在高性能计算和图形处理方面。 -4. **Tensor Cores**: Tensor Cores 是 NVIDIA GPU 上的一种特殊类型的核心,专门用于加速矩阵乘法和卷积操作。推理引擎可以利用 Tensor Cores 来优化 Kernel 层,特别是在执行大规模的矩阵运算时。 +1. **CUDA**:CUDA 是 NVIDIA 的并行计算平台和编程模型,用于在 NVIDIA GPU 上执行并行计算。推理引擎可以利用 CUDA 来优化 Kernel 层,特别是在大规模矩阵运算和卷积操作方面。 +2. **OpenCL**:OpenCL 是一个开放的标准,用于编写在异构系统上运行的程序。它允许开发者利用 CPU、GPU 和其他类型的处理器来加速计算密集型任务。推理引擎可以利用 OpenCL 来优化 Kernel 层,特别是在 GPU 上。 +3. **Vulkan**:Vulkan 是新一代的图形和计算 API,用于在各种 GPU 上执行并行计算。推理引擎可以利用 Vulkan API 来优化 Kernel 层,特别是在高性能计算和图形处理方面。 +4. **Tensor Cores**:Tensor Cores 是 NVIDIA GPU 上的一种特殊类型的核心,专门用于加速矩阵乘法和卷积操作。推理引擎可以利用 Tensor Cores 来优化 Kernel 层,特别是在执行大规模的矩阵运算时。 此外,封装的高性能算子库有: diff --git a/04Inference/06Kernel/02Conv.md b/04Inference/06Kernel/02Conv.md index 32dbd33f..d54fcf54 100644 --- a/04Inference/06Kernel/02Conv.md +++ b/04Inference/06Kernel/02Conv.md @@ -222,7 +222,7 @@ F_{out}^{w}=\left[\frac{F_{in_w}-k_{w}+2 p_{w} }s\right]+1 $$ ![卷积乘加过程](images/02Conv10.png) -=============== ZOMI 加一个Padding 的图 +=============== ZOMI 加一个 Padding 的图 2. **步长(Stride)**:步长是指卷积核在每一次卷积操作中滑动的距离。步长的大小可以影响输出数据的大小,也可以影响特征提取能力和计算复杂度。当步长增大时,输出数据的尺寸会减小,特征提取能力会变弱,但计算速度会加快。 @@ -484,7 +484,7 @@ for (int oh = 0; oh < OH; oh++) { - 优化卷积计算对于提高神经网络模型效率至关重要,包括算法、内存访问、汇编和调度优化。 -- 张量运算和内存布局(如NHWC与NCHW)对卷积性能有显著影响,是实现高效卷积计算的关键策略。 +- 张量运算和内存布局(如 NHWC 与 NCHW)对卷积性能有显著影响,是实现高效卷积计算的关键策略。 ## 本节视频 diff --git a/04Inference/06Kernel/03Im2col.md b/04Inference/06Kernel/03Im2col.md index 7ae8b692..2074b8fa 100644 --- a/04Inference/06Kernel/03Im2col.md +++ b/04Inference/06Kernel/03Im2col.md @@ -90,19 +90,19 @@ Im2Col 算法的核心是改变了数据在内存中的排列存储方式,将 - 将输出矩阵 $(OH*OW)×(OC)$ 在内存布局视角即为预期的输出张量 $N×OH×OW×OC$,或者使用 Col2Im 算法变为下一个算子输入 $N×OH×OW×OC$; -在权重数据的重排过程中,以上四个阶段在推理引擎中的执行方式和执行模块不一定在同一个阶段进行。其中 1,3,4 可能会在 Kernel 层执行,但 2 可能会在预编译阶段或者离线转换优化模块去执行。 +在权重数据的重排过程中,以上四个阶段在推理引擎中的执行方式和执行模块不一定在同一个阶段进行。其中 1,3,4 可能会在 Kernel 层执行,但 2 可能会在预编译阶段或者离线转换优化模块去执行。 而在 Input 数据的重排则会在正式计算时感知到数据流后进行。总的来说,不同的推理引擎在应用 Im2Col 算法时,输入数据的重排通常发生在以下阶段: -- **模型转换或图优化阶段**: 在这个阶段,推理引擎可能会分析模型的卷积层,并决定是否应用 Im2Col 算法。如果使用,引擎会相应地调整模型的结构,以便在执行卷积时进行数据重排。例如,TensorRT 在构建优化图时,会考虑是否将卷积层转换为 Im2Col 形式。 +- **模型转换或图优化阶段**:在这个阶段,推理引擎可能会分析模型的卷积层,并决定是否应用 Im2Col 算法。如果使用,引擎会相应地调整模型的结构,以便在执行卷积时进行数据重排。例如,TensorRT 在构建优化图时,会考虑是否将卷积层转换为 Im2Col 形式。 -- **推理初始化阶段**: 在执行推理之前,推理引擎会进行初始化,这可能就包括为 Im2Col 操作分配必要的内存空间,并准备执行数据重排的代码路径。 +- **推理初始化阶段**:在执行推理之前,推理引擎会进行初始化,这可能就包括为 Im2Col 操作分配必要的内存空间,并准备执行数据重排的代码路径。 -- **预处理阶段**: 在实际执行推理之前,输入数据需要经过预处理,以满足模型的输入要求。这可能包括缩放、归一化和其他数据转换。在某些情况下,Im2Col 的重排也可以看作是预处理的一部分,虽然它与卷积操作更相关。 +- **预处理阶段**:在实际执行推理之前,输入数据需要经过预处理,以满足模型的输入要求。这可能包括缩放、归一化和其他数据转换。在某些情况下,Im2Col 的重排也可以看作是预处理的一部分,虽然它与卷积操作更相关。 -- **卷积层的前向传播阶段**: 在执行卷积层的前向传播时,如果使用了 Im2Col 算法,推理引擎会在这一阶段对输入数据进行重排。即在卷积操作之前,输入特征图会被转换为一个二维矩阵,其中每一行对应于卷积核在输入特征图上的一个位置。这个重排操作是 Im2Col 算法的核心部分。 +- **卷积层的前向传播阶段**:在执行卷积层的前向传播时,如果使用了 Im2Col 算法,推理引擎会在这一阶段对输入数据进行重排。即在卷积操作之前,输入特征图会被转换为一个二维矩阵,其中每一行对应于卷积核在输入特征图上的一个位置。这个重排操作是 Im2Col 算法的核心部分。 -- **后处理阶段**: 在卷积操作完成后,如果需要,推理引擎可能会将数据从 Im2Col 格式转换回原始格式。这通常是在卷积层的输出被进一步处理或传递到下一个网络层之前完成的。 +- **后处理阶段**:在卷积操作完成后,如果需要,推理引擎可能会将数据从 Im2Col 格式转换回原始格式。这通常是在卷积层的输出被进一步处理或传递到下一个网络层之前完成的。 在深度学习框架中,Im2Col 通常是为了优化卷积操作而设计的,它通过将多次卷积操作转换为一次大矩阵乘法,从而可以利用现有的高性能线性代数库来加速计算。随着深度学习框架的发展,很多框架也实现了更加高效的卷积算法,比如 Winograd 算法或者直接使用 cuDNN 等专门的卷积计算库,这些库内部可能对 Im2Col 操作进行了进一步的优化。 @@ -124,19 +124,19 @@ Im2Col 是一种比较朴素的卷积优化算法,在没有精心处理的情 常见的空间组合优化方法有: -1. **分块卷积(Blocked Convolution)**: 将大卷积核分解为多个小卷积核,每个小卷积核单独计算,这样可以减少内存访问和提高缓存利用率。这种方法在处理大型图像或特征图时特别有效。 +1. **分块卷积(Blocked Convolution)**:将大卷积核分解为多个小卷积核,每个小卷积核单独计算,这样可以减少内存访问和提高缓存利用率。这种方法在处理大型图像或特征图时特别有效。 -2. **重叠数据块(Overlapping Blocks)**: 在进行 Im2Col 操作时,可以通过重叠数据块来减少边缘效应和填充(padding)的需求。这种方法可以减少计算量,但可能会增加内存访问的复杂性。 +2. **重叠数据块(Overlapping Blocks)**:在进行 Im2Col 操作时,可以通过重叠数据块来减少边缘效应和填充(padding)的需求。这种方法可以减少计算量,但可能会增加内存访问的复杂性。 -3. **稀疏卷积(Sparse Convolution)**: 对于稀疏数据,可以通过只对非零值进行卷积来减少计算量。这种方法在处理稀疏特征图时特别有效。 +3. **稀疏卷积(Sparse Convolution)**:对于稀疏数据,可以通过只对非零值进行卷积来减少计算量。这种方法在处理稀疏特征图时特别有效。 -4. **权重共享(Weight Sharing)**: 在卷积操作中,同一个卷积核会在不同的位置重复使用。通过在 Im2Col 操作中利用这一点,可以减少权重数据的重复加载和存储。 +4. **权重共享(Weight Sharing)**:在卷积操作中,同一个卷积核会在不同的位置重复使用。通过在 Im2Col 操作中利用这一点,可以减少权重数据的重复加载和存储。 -5. **张量化(Tensorization)**: 利用特定硬件(如 GPU)的特定指令来优化张量操作,例如使用 SIMD(单指令多数据)指令集。这种方法可以加速 Im2Col 操作中的矩阵乘法。 +5. **张量化(Tensorization)**:利用特定硬件(如 GPU)的特定指令来优化张量操作,例如使用 SIMD(单指令多数据)指令集。这种方法可以加速 Im2Col 操作中的矩阵乘法。 -6. **循环展开和软件流水线**: 这些是编译器优化技术,可以用于优化循环结构,减少循环开销和提高指令级并行性。在 Im2Col 操作中,循环展开可以减少循环迭代的开销,而软件流水线可以优化数据流和处理流程。 +6. **循环展开和软件流水线**:这些是编译器优化技术,可以用于优化循环结构,减少循环开销和提高指令级并行性。在 Im2Col 操作中,循环展开可以减少循环迭代的开销,而软件流水线可以优化数据流和处理流程。 -7. **内存复用**: 通过复用中间计算结果所占用的内存,可以减少总的内存使用。在 Im2Col 操作中,可以复用输入和输出数据的内存空间,以减少内存分配和释放的次数。 +7. **内存复用**:通过复用中间计算结果所占用的内存,可以减少总的内存使用。在 Im2Col 操作中,可以复用输入和输出数据的内存空间,以减少内存分配和释放的次数。 这些空间组合优化方法可以单独使用,也可以组合使用,以提高 Im2Col 操作的效率。在实际应用中,选择哪种优化策略取决于具体的模型结构、硬件平台和性能目标。随着深度学习框架和硬件的发展,许多框架已经采用了更加高效的卷积实现,如直接卷积(Direct Convolution)、Winograd 算法或利用专用硬件加速器,这些实现可能不再需要显式的 Im2Col 和 Col2Im 操作。 diff --git a/04Inference/06Kernel/04Winograd.md b/04Inference/06Kernel/04Winograd.md index 7fe7e281..2800a1ae 100644 --- a/04Inference/06Kernel/04Winograd.md +++ b/04Inference/06Kernel/04Winograd.md @@ -43,7 +43,7 @@ r_1 & = d_1 \times g_0 + d_2 \times g_1 + d_3 \times g_2 \end{align} $$ -具体的过程可以由下图了解到,在卷积的计算过程中,由于在卷积层的设计中,往往卷积的步幅(Stride) 的大小会小于卷积核的大小,所以最后转换的矩阵乘中往往有规律的分布着大量重复元素,比如这个一维卷积例子中矩阵乘输入矩阵第一行的 $d_1$、$d_2$ 和第二行中的 $d_1$、$d_2$,卷积转换成的矩阵乘法比一般矩阵乘法的问题域更小,这就让优化存在了可能。 +具体的过程可以由下图了解到,在卷积的计算过程中,由于在卷积层的设计中,往往卷积的步幅(Stride)的大小会小于卷积核的大小,所以最后转换的矩阵乘中往往有规律的分布着大量重复元素,比如这个一维卷积例子中矩阵乘输入矩阵第一行的 $d_1$、$d_2$ 和第二行中的 $d_1$、$d_2$,卷积转换成的矩阵乘法比一般矩阵乘法的问题域更小,这就让优化存在了可能。 ![Winograd01](images/04.Winograd01.png "Winograd01") @@ -69,7 +69,7 @@ $$ 其中,$m_1=(d_0-d_2)g_0$,$m_2=(d_1+d_2)\frac{g_0+g_1+g_2}{2}$,$m_3=(d_2-d_1)\frac{g_0-g_1+g_2}{2}$,$m_4=(d_1-d_3)g_2$。 -因为在推理阶段卷积核上的元素是固定的,所以上式 $m_1$,$m_2$,$m_3$,$m_4$ 的式子中和$g$相关的式子可以提前计算好,在预测阶段只需要计算一次,因此计算次数可以忽略。而在计算 $m_1$,$m_2$,$m_3$,$m_4$ 需要通过 4 次乘法操作与 4 次加法操作,然后基于计算好的$m_1$,$m_2$,$m_3$,$m_4$的值,需要通过使用 4 次加法操作得到结果,所以这里一共需要 4 次乘法操作和 8 次加法操作。由于乘法操作比加法操作消耗的时间多,因此 Winograd 的 4 次乘法和 8 次加法是要比一般的矩阵乘法的 6 次乘法和 4 次加法要快的。 +因为在推理阶段卷积核上的元素是固定的,所以上式 $m_1$,$m_2$,$m_3$,$m_4$ 的式子中和 $g$ 相关的式子可以提前计算好,在预测阶段只需要计算一次,因此计算次数可以忽略。而在计算 $m_1$,$m_2$,$m_3$,$m_4$ 需要通过 4 次乘法操作与 4 次加法操作,然后基于计算好的 $m_1$,$m_2$,$m_3$,$m_4$ 的值,需要通过使用 4 次加法操作得到结果,所以这里一共需要 4 次乘法操作和 8 次加法操作。由于乘法操作比加法操作消耗的时间多,因此 Winograd 的 4 次乘法和 8 次加法是要比一般的矩阵乘法的 6 次乘法和 4 次加法要快的。 而 Winograd 加速卷积计算的具体推导过程如下,由上面的式子可以得知: @@ -80,7 +80,7 @@ $$ \end{align} $$ -其中,因为 $m_1$ 与 $m_4$ 没有重复出现,所以令 $m_1 = d_0 \times g_0$,$m_4 = -d_3 \times g_2$,这样就可以约掉 $m_1$ 和 $m_4$,所以左边的式子只剩下两个变量,两个等式两个变量即可求出 $m_2$与$m_3$,在这个时候的 $m_1$、$m_2$、$m_3$、$m_4$ 是这样的: +其中,因为 $m_1$ 与 $m_4$ 没有重复出现,所以令 $m_1 = d_0 \times g_0$,$m_4 = -d_3 \times g_2$,这样就可以约掉 $m_1$ 和 $m_4$,所以左边的式子只剩下两个变量,两个等式两个变量即可求出 $m_2$ 与 $m_3$,在这个时候的 $m_1$、$m_2$、$m_3$、$m_4$ 是这样的: $$ \begin{align*} @@ -245,28 +245,28 @@ $$ 基于上文的介绍,Winograd 算法的实现可以细分为四个主要步骤: -1. 对输入卷积核的变换:$𝑈=𝐺𝑔𝐺^𝑇$,其中$G$表示为卷积核变换矩阵,$g$表示卷积核 -2. 对输入数据的变换:$𝑉=𝐵^𝑇 d𝐵$,其中$B$表示为输入数据的变换矩阵,$d$表示输入的特征图 +1. 对输入卷积核的变换:$𝑈=𝐺𝑔𝐺^𝑇$,其中 $G$ 表示为卷积核变换矩阵,$g$ 表示卷积核 +2. 对输入数据的变换:$𝑉=𝐵^𝑇 d𝐵$,其中 $B$ 表示为输入数据的变换矩阵,$d$ 表示输入的特征图 3. 对中间矩阵 M 的计算:$M = \sum U \odot V$ -4. 卷积结果的计算:$𝑌=𝐴^𝑇𝑀𝐴$,其中$A$表示输出变换矩阵 +4. 卷积结果的计算:$𝑌=𝐴^𝑇𝑀𝐴$,其中 $A$ 表示输出变换矩阵 Winograd 算法的工作流程可以用以下图示来说明: ![Winograd03](images/04.Winograd03.png "Winograd03") -以上文中 Winograd 加速二维卷积 $F(2 \times 2, 3 \times 3)$的计算为例子,可以具体了解 Winograd 的实现过程。 +以上文中 Winograd 加速二维卷积 $F(2 \times 2, 3 \times 3)$ 的计算为例子,可以具体了解 Winograd 的实现过程。 -如下图所示,在输入卷积核的转换过程中,首先通过 Winograd 算法中的卷积核变换矩阵 $G$ 和 $G^T$ 分别将 $3 \times 3$ 的卷积核权重转换为 $4 \times 4$ 的矩阵。然后,将该矩阵中相同位置的点(如下图中蓝色为位置 1 的点)进行重新排布(Relayout),形成一个输入通道数$IC \times$ 输出通道数$ OC$ 的矩阵,这一过程最终产生了$4 \times 4 = 16$个转换后的卷积核权重矩阵$U$。 +如下图所示,在输入卷积核的转换过程中,首先通过 Winograd 算法中的卷积核变换矩阵 $G$ 和 $G^T$ 分别将 $3 \times 3$ 的卷积核权重转换为 $4 \times 4$ 的矩阵。然后,将该矩阵中相同位置的点(如下图中蓝色为位置 1 的点)进行重新排布(Relayout),形成一个输入通道数 $IC \times$ 输出通道数 $ OC$ 的矩阵,这一过程最终产生了 $4 \times 4 = 16$ 个转换后的卷积核权重矩阵 $U$。 ![Winograd04](images/04.Winograd04.png "Winograd04") -如下图所示,在输入数据的转换过程中,首先将输入数据切分成$4 \times 4$的小块(tile)。接着,通过 Winograd 算法中输入数据的变换矩阵 $B$ 和 $B^T$ 将每个小块转换为 $4 \times 4$ 的矩阵形式。完成矩阵转换后,每个小块的数据按照与卷积核转换过程中类似的重新排布方法,转换成 16 个维度是小块数$nr\ tiles \times$ 输入通道数$IC$ 的输入数据矩阵$V$。 +如下图所示,在输入数据的转换过程中,首先将输入数据切分成 $4 \times 4$ 的小块(tile)。接着,通过 Winograd 算法中输入数据的变换矩阵 $B$ 和 $B^T$ 将每个小块转换为 $4 \times 4$ 的矩阵形式。完成矩阵转换后,每个小块的数据按照与卷积核转换过程中类似的重新排布方法,转换成 16 个维度是小块数 $nr\ tiles \times$ 输入通道数 $IC$ 的输入数据矩阵 $V$。 ![Winograd05](images/04.Winograd05.png "Winograd05") -如下图所示,将上述转换得到的卷积核权重矩阵$U$与输入数据矩阵$V$进行矩阵乘的操作,得到 16 个维度为小块数$nr\ tiles \times$ 输出通道数 $OC$ 的中间矩阵$M$。 +如下图所示,将上述转换得到的卷积核权重矩阵 $U$ 与输入数据矩阵 $V$ 进行矩阵乘的操作,得到 16 个维度为小块数 $nr\ tiles \times$ 输出通道数 $OC$ 的中间矩阵 $M$。 -随后,将相同位置的 16 个点重新排布成 $nr\ tiles \times OC$ 个维度为$4 \times 4$ 的矩阵。然后再使用 Winograd 算法中的输出变换矩阵 $A$ 和 $A^T$ 将这些 $4 \times 4$ 的矩阵转换为 $2 \times 2$ 的输出矩阵,最后将这些矩阵写回输出矩阵中就可以得到 Winograd 卷积的最终结果$Y$。 +随后,将相同位置的 16 个点重新排布成 $nr\ tiles \times OC$ 个维度为 $4 \times 4$ 的矩阵。然后再使用 Winograd 算法中的输出变换矩阵 $A$ 和 $A^T$ 将这些 $4 \times 4$ 的矩阵转换为 $2 \times 2$ 的输出矩阵,最后将这些矩阵写回输出矩阵中就可以得到 Winograd 卷积的最终结果 $Y$。 ![Winograd06](images/04.Winograd06.png "Winograd06") @@ -278,9 +278,9 @@ Winograd 算法的工作流程可以用以下图示来说明: Winograd 算法虽然通过减少乘法次数来提高计算速度,但加法运算的数量却相应增加,同时还需要额外的转换计算和存储转换矩阵。随着卷积核和分块尺寸的增大,加法运算、转换计算和存储的开销也随之增加。此外,分块尺寸越大,转换矩阵也越大,计算精度的损失也会进一步加剧。因此,Winograd 算法仅适用于较小的卷积核和分块尺寸。在实际工程应用中,Winograd 算法通常只用于处理一些特定的 $3 \times 3$ 卷积,而 $1 \times 1$ 和 $7 \times 7$ 、 $5 \times 5$ 的卷积则不会采用 Winograd 这个 kernel。因此,在 runtime 中需要根据具体情况进行决策,选择合适的 kernel。 -在实际应用中,通常会将所有可以固定的数据在网络运行前预先确定。在算法程序的设计中,希望尽可能提前计算出可以固定的数据,因此会有一个预编译阶段或离线模块转换阶段,以便提前计算出一些可预知的结果。在推理引擎中,主要处理的是一些常见或通用的算法问题,以及一些通用的网络模型结构。对于一些特定的网络模型结构,如果$G$是固定的,那么可以将特定网络的$G$提前计算出来,这样在下次运行时,就不需要重新计算。例如,在设计基于 Winograd 算法的特定网络结构时,如果 $G$ 和 $g$ 是固定的,那么 $U=GgG^T$ 可以在网络运行前预先确定。 +在实际应用中,通常会将所有可以固定的数据在网络运行前预先确定。在算法程序的设计中,希望尽可能提前计算出可以固定的数据,因此会有一个预编译阶段或离线模块转换阶段,以便提前计算出一些可预知的结果。在推理引擎中,主要处理的是一些常见或通用的算法问题,以及一些通用的网络模型结构。对于一些特定的网络模型结构,如果 $G$ 是固定的,那么可以将特定网络的 $G$ 提前计算出来,这样在下次运行时,就不需要重新计算。例如,在设计基于 Winograd 算法的特定网络结构时,如果 $G$ 和 $g$ 是固定的,那么 $U=GgG^T$ 可以在网络运行前预先确定。 -另一个想法是将 Winograd 算法与空间组织算法结合起来,充分利用局部性和算法分析的优化,将卷积计算通过空间组合优化算法中的拆分方法,将输入拆分成若干个小规模卷积。例如,可以拆分成每个小卷积输出$4 \times 4$个数据的卷积。 +另一个想法是将 Winograd 算法与空间组织算法结合起来,充分利用局部性和算法分析的优化,将卷积计算通过空间组合优化算法中的拆分方法,将输入拆分成若干个小规模卷积。例如,可以拆分成每个小卷积输出 $4 \times 4$ 个数据的卷积。 ## 小结与思考 diff --git a/04Inference/06Kernel/05Qnnpack.md b/04Inference/06Kernel/05Qnnpack.md index 242a2078..2be9315a 100644 --- a/04Inference/06Kernel/05Qnnpack.md +++ b/04Inference/06Kernel/05Qnnpack.md @@ -4,8 +4,8 @@ QNNPACK(Quantized Neural Networks PACKage 是 Marat Dukhan (Meta) 开发的专门用于量化神经网络计算的加速库,其卓越的性能表现一经开源就击败了几乎全部已公开的加速算法。到目前为止,QNNPACK 仍然是已公开的,用于移动端(手机)的,性能最优的量化神经网络加速库。本节将会深入介绍 QNNPACK 算法的实现过程。 -======== 需要继续优化,图片请引用 PPT 里面的图片,不要网上找,有好几个图片PPT 都已经重绘优化过的。 -======== 具体的计算公式和计算内容详细展开请参考连接,进行补充哈,特别是 Repacking 和 Indirection部分: +======== 需要继续优化,图片请引用 PPT 里面的图片,不要网上找,有好几个图片 PPT 都已经重绘优化过的。 +======== 具体的计算公式和计算内容详细展开请参考连接,进行补充哈,特别是 Repacking 和 Indirection 部分: https://aijishu.com/a/1060000000116424 https://zhuanlan.zhihu.com/p/81026071 https://zhenhuaw.me/blog/2019/reveal-qnnpack-implementation.html @@ -178,7 +178,7 @@ $$ KH × (M + 2(KW-1)) × IC $$ 图中将平面缓冲区展示为三维的形式(引入 IC 维度),意在说明间接缓冲区的每个指针可索引 IC 个输入元素,而每个间接缓冲区索引的内容即为与权重对应的输入内存区域。 -间接缓冲区如下图中部下方的排布图所示。 A、B、C、D 四个缓冲区内部相同空间位置的指针被组织到了一起并横向排布。值得注意的是,图例中 Stride 为 1,当 Stride 不为 1 时,重新组织后 A、B、C、D 相同空间的坐标(对应于在输入的坐标)不一定是连续的,相邻的空间位置横向坐标相差 strdie 大小。 +间接缓冲区如下图中部下方的排布图所示。A、B、C、D 四个缓冲区内部相同空间位置的指针被组织到了一起并横向排布。值得注意的是,图例中 Stride 为 1,当 Stride 不为 1 时,重新组织后 A、B、C、D 相同空间的坐标(对应于在输入的坐标)不一定是连续的,相邻的空间位置横向坐标相差 strdie 大小。 现在来分析如何使用间接缓冲区完成计算。间接缓冲区使得可以通过指针模拟出对输入的访存。在实际运行计算尺寸为 M × N 的计算核时,会有 M 个智障扫描输入。M 个指针每次从间接缓冲区中取出 M 个地址,即对应于 M × IC 的输入内存。指针以 M × S 的形式运行,其中 S 在 IC 维度上运动。此部分输入扫描完毕后,这 M 个指针从间接缓冲区中继续取出相应部分的指针,继续对下一轮 M × IC 输入内存进行遍历,每次计算出输出部分的大小为 1/(KH × KW)。当这个过程运行 KH × KW 次后即得到了 M × N 的输出。 diff --git a/05Framework/01Foundation/03History.md b/05Framework/01Foundation/03History.md index 8dd96628..82fd332b 100644 --- a/05Framework/01Foundation/03History.md +++ b/05Framework/01Foundation/03History.md @@ -184,13 +184,13 @@ OpenAI 于 2020 年 5 月发布 GPT-3 模型,包含 1750 亿参数,数据集 ## 小结与思考 -- AI框架作为智能经济时代的基础设施,其发展经历了从萌芽到成长、爆发和深化的四个阶段,目前正向着全场景支持、大模型、分布式AI、超大规模AI、安全可信AI等技术特性深化探索。 +- AI 框架作为智能经济时代的基础设施,其发展经历了从萌芽到成长、爆发和深化的四个阶段,目前正向着全场景支持、大模型、分布式 AI、超大规模 AI、安全可信 AI 等技术特性深化探索。 -- AI框架的核心目标是提供灵活易用的编程模型和高效可扩展的计算能力,以支持深度神经网络的训练和推理,同时兼顾可编程性和性能。 +- AI 框架的核心目标是提供灵活易用的编程模型和高效可扩展的计算能力,以支持深度神经网络的训练和推理,同时兼顾可编程性和性能。 -- 随着AI应用场景的扩展和新趋势的出现,AI框架正面临支持全场景跨平台设备部署、强化大规模分布式AI支持、与科学计算深度融合交叉等多样化挑战。 +- 随着 AI 应用场景的扩展和新趋势的出现,AI 框架正面临支持全场景跨平台设备部署、强化大规模分布式 AI 支持、与科学计算深度融合交叉等多样化挑战。 -- 未来AI框架将注重前端便捷性与后端高效性的统一,着力强化对超大规模AI的支持,并进一步探索与科学计算的深度融合,以适应不断涌现的新需求和场景。 +- 未来 AI 框架将注重前端便捷性与后端高效性的统一,着力强化对超大规模 AI 的支持,并进一步探索与科学计算的深度融合,以适应不断涌现的新需求和场景。 ## 本节视频 diff --git a/05Framework/01Foundation/04Programing.md b/05Framework/01Foundation/04Programing.md index 3f1690b4..7ab26909 100644 --- a/05Framework/01Foundation/04Programing.md +++ b/05Framework/01Foundation/04Programing.md @@ -184,7 +184,7 @@ N, D_in, H, D_out = 64, 1000, 100, 10 x = tf.placeholder(tf.float32, shape=(None, D_in)) y = tf.placeholder(tf.float32, shape=(None, D_out)) -# 创建变量,并且随机初始化。 +# 创建变量,并且随机初始化。 # 在 TensorFlow 里,变量的生命周期是整个 session,因此适合用它来保存模型的参数。 w1 = tf.Variable(tf.random_normal((D_in, H))) w2 = tf.Variable(tf.random_normal((H, D_out))) @@ -238,11 +238,11 @@ with tf.Session() as sess: ## 小结与思考 -- 在AI框架中,主要使用的编程范式为声明式编程和命令式编程,这两种范式决定了开发者对程序执行的不同视角和方法。 +- 在 AI 框架中,主要使用的编程范式为声明式编程和命令式编程,这两种范式决定了开发者对程序执行的不同视角和方法。 -- 命令式编程注重如何(How)执行任务,强调通过一系列步骤和指令来控制计算机的操作过程,如PyTorch框架采用此方式,便于调试且灵活性高,但缺少编译期优化。 +- 命令式编程注重如何(How)执行任务,强调通过一系列步骤和指令来控制计算机的操作过程,如 PyTorch 框架采用此方式,便于调试且灵活性高,但缺少编译期优化。 -- 声明式编程关注结果(What),由机器自行确定执行过程,如TensorFlow 1.X框架采用此方式,利于编译优化和性能提升,但可能牺牲易用性和灵活性。 +- 声明式编程关注结果(What),由机器自行确定执行过程,如 TensorFlow 1.X 框架采用此方式,利于编译优化和性能提升,但可能牺牲易用性和灵活性。 ## 本节视频 diff --git a/05Framework/01Foundation/README.md b/05Framework/01Foundation/README.md index 771db63a..1579ac08 100644 --- a/05Framework/01Foundation/README.md +++ b/05Framework/01Foundation/README.md @@ -15,7 +15,7 @@ AI 框架基础内容介绍,从 AI 框架基础开始去介绍为什么我们 | 01 基本介绍| [文章](./01Introduction.md), [PPT](./01Introduction.pdf), [视频](https://www.bilibili.com/video/BV1he4y1z7oD) | | 02 AI 框架的作用| [文章](./02Fundamentals.md), [PPT](./02Fundamentals.pdf), [视频](https://www.bilibili.com/video/BV1fd4y1q7qk) | | 03 AI 框架之争(框架发展)| [文章](./03History.md), [PPT](./03History.pdf), [视频](https://www.bilibili.com/video/BV1C8411x7Kn) | -| 04 编程范式(声明式&命令式) | [文章](./04Programing.md), [PPT](./04Programing.pdf), [视频](https://www.bilibili.com/video/BV1gR4y1o7WT) | +| 04 编程范式(声明式&命令式)| [文章](./04Programing.md), [PPT](./04Programing.pdf), [视频](https://www.bilibili.com/video/BV1gR4y1o7WT) | ## 备注 diff --git a/05Framework/02AutoDiff/01Introduction.md b/05Framework/02AutoDiff/01Introduction.md index b2e81d14..96f37311 100644 --- a/05Framework/02AutoDiff/01Introduction.md +++ b/05Framework/02AutoDiff/01Introduction.md @@ -10,7 +10,7 @@ 在接下来的内容,主要是了解计算机实现微分的基本概念,其实微分的实现方式分为很多种,有数字微分,符号微分,自动微分。函数的微分是指对函数的局部变化的一种线性描述。微分可以近似地描述当函数自变量的取值作足够小的改变时,函数的值是怎样改变的。 -微分在数学中的定义:由 $y$ 是 $x$ 的函数 $(y=f(x))$。从简单的 $x-y$ 座标系来看,自变数 $x$ 有微小的变化量时 $(d/dx)$,应变数 $y$ 也会跟着变动,但 $x$ 跟 $y$ 的变化量都是极小的。当 $x$ 有极小的变化量时,我们称对 $x$ 微分。微分主要用于线性函数的改变量,这是微积分的基本概念之一。 +微分在数学中的定义:由 $y$ 是 $x$ 的函数 $(y=f(x))$。从简单的 $x-y$ 座标系来看,自变数 $x$ 有微小的变化量时 $(d/dx)$,应变数 $y$ 也会跟着变动,但 $x$ 跟 $y$ 的变化量都是极小的。当 $x$ 有极小的变化量时,我们称对 $x$ 微分。微分主要用于线性函数的改变量,这是微积分的基本概念之一。 在具体实现自动微分的过程中,主要有 2 种实现模式,前向和向后微分。前向微分和后向微分为了在数学上方便表达,会引入一个亚克比原理,或者叫做亚克比矩阵。 diff --git a/05Framework/02AutoDiff/02BaseConcept.md b/05Framework/02AutoDiff/02BaseConcept.md index 9a69ae7c..cf1adae2 100644 --- a/05Framework/02AutoDiff/02BaseConcept.md +++ b/05Framework/02AutoDiff/02BaseConcept.md @@ -200,7 +200,7 @@ $$ w_0=x \\ w_1=h(w_0) \\ w_2=g(w_1) \\ w_3=f(w_2)=y $$ ## 小结与思考 -- 自动微分(AD)是一种高效准确的计算机程序求导技术,广泛应用于多个领域,包括机器学习,与编程语言、计算框架紧密相关,是AI框架的核心功能。 +- 自动微分(AD)是一种高效准确的计算机程序求导技术,广泛应用于多个领域,包括机器学习,与编程语言、计算框架紧密相关,是 AI 框架的核心功能。 - 计算机求导方法包括手动求解法、数值微分法、符号微分法和自动微分法,每种方法都有其适用场景和优缺点。 diff --git a/05Framework/02AutoDiff/03GradMode.md b/05Framework/02AutoDiff/03GradMode.md index 731203fb..5fc116b0 100644 --- a/05Framework/02AutoDiff/03GradMode.md +++ b/05Framework/02AutoDiff/03GradMode.md @@ -293,7 +293,7 @@ $$ - 前向微分从输入向输出逐步累积梯度,适用于输入参数较少的情况,实现简单但可能需要多次计算来获取所有输入的导数。 -- 后向微分从输出反向累积梯度至输入,适合多参数优化,时间复杂度低,但需要额外内存记录计算过程,是大多数AI框架优先采用的模式。 +- 后向微分从输出反向累积梯度至输入,适合多参数优化,时间复杂度低,但需要额外内存记录计算过程,是大多数 AI 框架优先采用的模式。 ## 本节视频 diff --git a/05Framework/02AutoDiff/04Implement.md b/05Framework/02AutoDiff/04Implement.md index ccd01b78..4cfbee43 100644 --- a/05Framework/02AutoDiff/04Implement.md +++ b/05Framework/02AutoDiff/04Implement.md @@ -104,7 +104,7 @@ t5 = ADSub(t4, t2) 在具有多态特性的现代编程语言中,运算符重载提供了实现自动微分的最直接方式,利用了编程语言的第一特性(first class feature),重新定义了微分基本操作语义的能力。 -在 C++ 中使用运算符重载实现的流行工具是 ADOL-C(Walther 和 Griewank,2012)。 ADOL-C 要求对变量使用启用 AD 的类型,并在 Tape 数据结构中记录变量的算术运算,随后可以在反向模式 AD 计算期间“回放”。 Mxyzptlk 库 (Michelotti, 1990) 是 C++ 能够通过前向传播计算任意阶偏导数的另一个例子。 FADBAD++ 库(Bendtsen 和 Stauning,1996 年)使用模板和运算符重载为 C++ 实现自动微分。对于 Python 语言来说,autograd 提供正向和反向模式自动微分,支持高阶导数。 +在 C++ 中使用运算符重载实现的流行工具是 ADOL-C(Walther 和 Griewank,2012)。ADOL-C 要求对变量使用启用 AD 的类型,并在 Tape 数据结构中记录变量的算术运算,随后可以在反向模式 AD 计算期间“回放”。Mxyzptlk 库 (Michelotti, 1990) 是 C++ 能够通过前向传播计算任意阶偏导数的另一个例子。FADBAD++ 库(Bendtsen 和 Stauning,1996 年)使用模板和运算符重载为 C++ 实现自动微分。对于 Python 语言来说,autograd 提供正向和反向模式自动微分,支持高阶导数。 在机器学习 ML 或者深度学习 DL 领域,目前 AI 框架中使用操作符重载的一个典型代表是 Pytroch,其中使用数据结构 Tape 来记录计算流程,在反向模式求解梯度的过程中进行 replay Operator。 @@ -175,9 +175,9 @@ def grad(l, results): 源码转换的实现提供了对编程语言的扩展,可自动将算法分解为支持自动微分的基本操作。通常作为预处理器执行,以将扩展语言的输入转换为原始语言。简单来说就是利用源语言来实现领域扩展语言 DSL 的操作方式。 -源代码转换的经典实例包括 Fortran 预处理器 GRESS(Horwedel 等人,1988 年)和 PADRE2(Kubo 和 Iri,1990 年),在编译之前将启用 AD 的 Fortran 变体转换为标准 Fortran。类似地,ADIFOR 工具 (Bischof et al., 1996) 给定一个 Fortran 源代码,生成一个增强代码,其中除了原始结果之外还计算所有指定的偏导数。对于以 ANSI C 编码的过程,ADIC 工具(Bischof 等人,1997)在指定因变量和自变量之后将 AD 实现为源代码转换。 Tapenade(Pascual 和 Hasco¨et,2008 年;Hasco¨et 和 Pascual,2013 年)是过去 10 年终 SCT 的流行工具,它为 Fortran 和 C 程序实现正向和反向模式 AD。 +源代码转换的经典实例包括 Fortran 预处理器 GRESS(Horwedel 等人,1988 年)和 PADRE2(Kubo 和 Iri,1990 年),在编译之前将启用 AD 的 Fortran 变体转换为标准 Fortran。类似地,ADIFOR 工具 (Bischof et al., 1996) 给定一个 Fortran 源代码,生成一个增强代码,其中除了原始结果之外还计算所有指定的偏导数。对于以 ANSI C 编码的过程,ADIC 工具(Bischof 等人,1997)在指定因变量和自变量之后将 AD 实现为源代码转换。Tapenade(Pascual 和 Hasco¨et,2008 年;Hasco¨et 和 Pascual,2013 年)是过去 10 年终 SCT 的流行工具,它为 Fortran 和 C 程序实现正向和反向模式 AD。 -除了通过源代码转换进行语言扩展外,还有一些实现通过专用编译器或解释器引入了具有紧密集成的 AD 功能的新语言。一些最早的 AD 工具,例如 SLANG (Adamson and Winant, 1969) 和 PROSE (Pfeiffer, 1987) 属于这一类。 NAGWare Fortran 编译器 (Naumann and Riehme, 2005) 是一个较新的示例,其中使用与 AD 相关的扩展会在编译时触发衍生代码的自动生成。 +除了通过源代码转换进行语言扩展外,还有一些实现通过专用编译器或解释器引入了具有紧密集成的 AD 功能的新语言。一些最早的 AD 工具,例如 SLANG (Adamson and Winant, 1969) 和 PROSE (Pfeiffer, 1987) 属于这一类。NAGWare Fortran 编译器 (Naumann and Riehme, 2005) 是一个较新的示例,其中使用与 AD 相关的扩展会在编译时触发衍生代码的自动生成。 作为基于解释器的实现的一个例子,代数建模语言 AMPL (Fourer et al., 2002) 可以用数学符号表示目标和约束,系统从中推导出活动变量并安排必要的 AD 计算。此类别中的其他示例包括基于类似 Algol 的 DIFALG 语言的 FM/FAD 包 (Mazourik, 1991),以及类似于 Pascal 的面向对象的 COZY 语言 (Berz et al., 1996)。 @@ -199,7 +199,7 @@ def grad(l, results): 源码转换法的**优点**可以总结如下: -- 支持更多的数据类型(原生和用户自定义的数据类型) + 原生语言操作(基本数学运算操作和控制流操作); +- 支持更多的数据类型(原生和用户自定义的数据类型)+ 原生语言操作(基本数学运算操作和控制流操作); - 高阶微分中实现容易,不用每次使用 Tape 来记录高阶的微分中产生的大量变量,而是统一通过编译器进行额外变量优化和重计算等优化; - 进一步提升性能,没有产生额外的 tape 数据结构和 tape 读写操作,除了利于实现高阶微分以外,还能够对计算表达式进行统一的编译优化。 diff --git a/05Framework/02AutoDiff/05ForwardMode.md b/05Framework/02AutoDiff/05ForwardMode.md index 7e3a1bab..45e33903 100644 --- a/05Framework/02AutoDiff/05ForwardMode.md +++ b/05Framework/02AutoDiff/05ForwardMode.md @@ -187,9 +187,9 @@ print(grad[0]) ## 小结与思考 -- 介绍了前向自动微分(Forward Automatic Differentiation, FAD)的实现原理和过程,通过Python语言的操作符重载技术,创建了ADTangent类来自动计算导数,并通过实例代码演示了如何使用该类来计算给定函数的导数。 +- 介绍了前向自动微分(Forward Automatic Differentiation, FAD)的实现原理和过程,通过 Python 语言的操作符重载技术,创建了 ADTangent 类来自动计算导数,并通过实例代码演示了如何使用该类来计算给定函数的导数。 -- 通过具体的Python代码示例,展示了如何利用ADTangent类进行操作符重载,实现正向自动微分,并与PyTorch和MindSpore等深度学习框架的自动微分结果进行了对比,验证了实现的正确性。 +- 通过具体的 Python 代码示例,展示了如何利用 ADTangent 类进行操作符重载,实现正向自动微分,并与 PyTorch 和 MindSpore 等深度学习框架的自动微分结果进行了对比,验证了实现的正确性。 ## 本节视频 diff --git a/05Framework/02AutoDiff/06ReversedMode.ipynb b/05Framework/02AutoDiff/06ReversedMode.ipynb index d9c6c373..3d4edf0e 100644 --- a/05Framework/02AutoDiff/06ReversedMode.ipynb +++ b/05Framework/02AutoDiff/06ReversedMode.ipynb @@ -26,7 +26,7 @@ "\n", "在具有多态特性的现代编程语言中,运算符重载提供了实现自动微分的最直接方式,利用了编程语言的第一特性(first class feature),重新定义了微分基本操作语义的能力。\n", "\n", - "在 C++ 中使用运算符重载实现的流行工具是 ADOL-C(Walther 和 Griewank,2012)。 ADOL-C 要求对变量使用启用 AD 的类型,并在 Tape 数据结构中记录变量的算术运算,随后可以在反向模式 AD 计算期间“回放”。 Mxyzptlk 库 (Michelotti, 1990) 是 C++ 能够通过前向传播计算任意阶偏导数的另一个例子。 FADBAD++ 库(Bendtsen 和 Stauning,1996 年)使用模板和运算符重载为 C++ 实现自动微分。对于 Python 语言来说,autograd 提供正向和反向模式自动微分,支持高阶导数。在机器学习 ML 或者深度学习 DL 领域,目前 AI 框架中使用操作符重载 OO 的一个典型代表是 Pytroch,其中使用数据结构 Tape 来记录计算流程,在反向模式求解梯度的过程中进行 replay Operator。\n", + "在 C++ 中使用运算符重载实现的流行工具是 ADOL-C(Walther 和 Griewank,2012)。ADOL-C 要求对变量使用启用 AD 的类型,并在 Tape 数据结构中记录变量的算术运算,随后可以在反向模式 AD 计算期间“回放”。Mxyzptlk 库 (Michelotti, 1990) 是 C++ 能够通过前向传播计算任意阶偏导数的另一个例子。FADBAD++ 库(Bendtsen 和 Stauning,1996 年)使用模板和运算符重载为 C++ 实现自动微分。对于 Python 语言来说,autograd 提供正向和反向模式自动微分,支持高阶导数。在机器学习 ML 或者深度学习 DL 领域,目前 AI 框架中使用操作符重载 OO 的一个典型代表是 Pytroch,其中使用数据结构 Tape 来记录计算流程,在反向模式求解梯度的过程中进行 replay Operator。\n", "\n", "下面总结一下操作符重载的一个基本流程:\n", "\n", diff --git a/05Framework/02AutoDiff/06ReversedMode.md b/05Framework/02AutoDiff/06ReversedMode.md index 6cbf8be5..60b5a6ae 100644 --- a/05Framework/02AutoDiff/06ReversedMode.md +++ b/05Framework/02AutoDiff/06ReversedMode.md @@ -14,7 +14,7 @@ 在具有多态特性的现代编程语言中,运算符重载提供了实现自动微分的最直接方式,利用了编程语言的第一特性(first class feature),重新定义了微分基本操作语义的能力。 -在 C++ 中使用运算符重载实现的流行工具是 ADOL-C(Walther 和 Griewank,2012)。 ADOL-C 要求对变量使用启用 AD 的类型,并在 Tape 数据结构中记录变量的算术运算,随后可以在反向模式 AD 计算期间“回放”。 Mxyzptlk 库 (Michelotti, 1990) 是 C++ 能够通过前向传播计算任意阶偏导数的另一个例子。 +在 C++ 中使用运算符重载实现的流行工具是 ADOL-C(Walther 和 Griewank,2012)。ADOL-C 要求对变量使用启用 AD 的类型,并在 Tape 数据结构中记录变量的算术运算,随后可以在反向模式 AD 计算期间“回放”。Mxyzptlk 库 (Michelotti, 1990) 是 C++ 能够通过前向传播计算任意阶偏导数的另一个例子。 FADBAD++ 库(Bendtsen 和 Stauning,1996 年)使用模板和运算符重载为 C++ 实现自动微分。对于 Python 语言来说,autograd 提供正向和反向模式自动微分,支持高阶导数。在机器学习 ML 或者深度学习 DL 领域,目前 AI 框架中使用操作符重载 OO 的一个典型代表是 PyTroch,其中使用数据结构 Tape 来记录计算流程,在反向模式求解梯度的过程中进行 replay Operator。 @@ -336,9 +336,9 @@ print("dy", dy) ## 小结与思考 -- 通过Python语言的操作符重载技术,展示了如何实现一个基础的反向自动微分(AD)机制,模仿了PyTroch等深度学习框架中的自动微分系统。 +- 通过 Python 语言的操作符重载技术,展示了如何实现一个基础的反向自动微分(AD)机制,模仿了 PyTroch 等深度学习框架中的自动微分系统。 -- 实现中定义了Variable类来跟踪计算梯度,并通过Tape数据结构记录计算过程,最终通过反向传播计算出梯度,验证了反向操作符重载实现自动微分的基本原理和效果。 +- 实现中定义了 Variable 类来跟踪计算梯度,并通过 Tape 数据结构记录计算过程,最终通过反向传播计算出梯度,验证了反向操作符重载实现自动微分的基本原理和效果。 ## 本节视频 diff --git a/05Framework/02AutoDiff/07Challenge.md b/05Framework/02AutoDiff/07Challenge.md index ef0afdc1..99af88c9 100644 --- a/05Framework/02AutoDiff/07Challenge.md +++ b/05Framework/02AutoDiff/07Challenge.md @@ -183,7 +183,7 @@ $$ \frac{\partial^{2} y }{\partial x^{2}} = \frac{\mathrm{d}}{\mathrm{d} y}\left - 自动微分的未来发展可能聚焦于可微编程,将自动微分技术与语言设计、编译器/解释器等工具链深度融合,实现高级编程语言中微分作为一等特性。 -- 可微编程框架通常分为静态图和动态图方法,静态图方法如TensorFlow等有助于编译器优化,但交互性较差;动态图方法如PyTorch等易于编写和推理,但可能带来解释器开销和可扩展性降低的问题。 +- 可微编程框架通常分为静态图和动态图方法,静态图方法如 TensorFlow 等有助于编译器优化,但交互性较差;动态图方法如 PyTorch 等易于编写和推理,但可能带来解释器开销和可扩展性降低的问题。 ## 本节视频 diff --git a/05Framework/03DataFlow/02Computegraph.md b/05Framework/03DataFlow/02Computegraph.md index c583f543..aac7aa05 100644 --- a/05Framework/03DataFlow/02Computegraph.md +++ b/05Framework/03DataFlow/02Computegraph.md @@ -231,11 +231,11 @@ tensor([[4.5000]]) ## 小结与思考 -- 计算图是AI框架中用于抽象和表达神经网络计算的关键数据结构,它将复杂的神经网络模型表示为一系列有向节点和边,便于自动微分和优化执行。 +- 计算图是 AI 框架中用于抽象和表达神经网络计算的关键数据结构,它将复杂的神经网络模型表示为一系列有向节点和边,便于自动微分和优化执行。 - 计算图由标量、向量、矩阵和张量等基本数据结构组成,并通过张量操作来执行数值计算,其中张量是多维数组的推广,适用于表达和处理数据并行性。 -- PyTorch中的计算图是动态的,正向传播即时执行,反向传播后计算图销毁,支持通过自定义Function来扩展新的操作,结合正向和反向逻辑实现自动微分。 +- PyTorch 中的计算图是动态的,正向传播即时执行,反向传播后计算图销毁,支持通过自定义 Function 来扩展新的操作,结合正向和反向逻辑实现自动微分。 ## 视频 diff --git a/05Framework/03DataFlow/03Atuodiff.md b/05Framework/03DataFlow/03Atuodiff.md index 0c1ab911..b0ad52a7 100644 --- a/05Framework/03DataFlow/03Atuodiff.md +++ b/05Framework/03DataFlow/03Atuodiff.md @@ -26,7 +26,7 @@ 如图所示,在训练神经网络时,在初始化模型参数后,我们交替使用前向传播和反向传播,利用反向传播给出的梯度来更新模型参数。 -> 注意,反向传播重复利用前向传播中存储的中间值,以避免重复计算。带来的影响之一是我们需要保留中间值,直到反向传播完成。这也是训练比单纯的预测需要更多的内存(显存)的原因之一。 此外,这些中间值的大小与网络层的数量和批量的大小大致成正比。 +> 注意,反向传播重复利用前向传播中存储的中间值,以避免重复计算。带来的影响之一是我们需要保留中间值,直到反向传播完成。这也是训练比单纯的预测需要更多的内存(显存)的原因之一。此外,这些中间值的大小与网络层的数量和批量的大小大致成正比。 ![三维张量示例](images/graph_framework02.png) @@ -240,11 +240,11 @@ loss.backward() 调用后,依次发生以下计算过程: ## 小结与思考 -- 自动微分是AI框架实现神经网络训练的关键技术,它通过计算图来表达和自动求导,使得可以高效地计算损失函数对网络参数的梯度。 +- 自动微分是 AI 框架实现神经网络训练的关键技术,它通过计算图来表达和自动求导,使得可以高效地计算损失函数对网络参数的梯度。 - 计算图将神经网络的前向计算过程表达为一系列节点和边,其中节点表示计算操作,边表示数据流,自动微分利用这些计算图来实现反向传播算法。 -- AI框架中的自动微分通常采用动态计算图或静态计算图的方式,动态图易于调试和支持灵活的网络结构调整,而静态图在执行效率和内存优化方面表现更优。 +- AI 框架中的自动微分通常采用动态计算图或静态计算图的方式,动态图易于调试和支持灵活的网络结构调整,而静态图在执行效率和内存优化方面表现更优。 ## 视频 diff --git a/05Framework/03DataFlow/04Dispatch.md b/05Framework/03DataFlow/04Dispatch.md index 9316d76a..b046f688 100644 --- a/05Framework/03DataFlow/04Dispatch.md +++ b/05Framework/03DataFlow/04Dispatch.md @@ -186,7 +186,7 @@ Kernel 主要是算子的计算模块,但是别忘记了在深度学习中, - 图执行方式包括单算子执行、整图下沉执行和图切分到多设备的并行执行,以适应不同的硬件架构和计算规模需求。 -- PyTorch中的算子执行涉及两次调度,首先是选择具体实现函数,然后是选择具体的Kernel执行操作,整个过程由Autograd模块支持自动求导。 +- PyTorch 中的算子执行涉及两次调度,首先是选择具体实现函数,然后是选择具体的 Kernel 执行操作,整个过程由 Autograd 模块支持自动求导。 ## 视频 diff --git a/05Framework/03DataFlow/05ControlFlow.md b/05Framework/03DataFlow/05ControlFlow.md index c5efc4cb..0a301d90 100644 --- a/05Framework/03DataFlow/05ControlFlow.md +++ b/05Framework/03DataFlow/05ControlFlow.md @@ -203,9 +203,9 @@ execution frame:{ - 计算图的控制流实现对于构建复杂神经网络结构至关重要,涉及动态图和静态图两种模式,以及复用宿主语言、支持控制流原语和源码解析三种设计思路。 -- 动态图方法(如PyTorch)利用Python控制流的易用性高但运行时开销大,而静态图(如TensorFlow)执行效率高但编程复杂度增加。 +- 动态图方法(如 PyTorch)利用 Python 控制流的易用性高但运行时开销大,而静态图(如 TensorFlow)执行效率高但编程复杂度增加。 -- 源码解析方法(如MindSpore)在易用性和执行效率之间取得平衡,但对高级语言的控制流有一定约束。 +- 源码解析方法(如 MindSpore)在易用性和执行效率之间取得平衡,但对高级语言的控制流有一定约束。 ## 视频 diff --git a/05Framework/03DataFlow/06StaticGraph.md b/05Framework/03DataFlow/06StaticGraph.md index d4860b50..b9dfb80a 100644 --- a/05Framework/03DataFlow/06StaticGraph.md +++ b/05Framework/03DataFlow/06StaticGraph.md @@ -4,7 +4,7 @@ 从 TensorFlow、PyTorch,到 PaddlePaddle、MindSpore、MegEngine,主流的 AI 框架动静态图转换,经历了动静分离、动静结合到动静统一的发展过程。兼顾动态图易用性和静态图执行性能高效两方面优势,均具备动态图转静态图的功能,支持使用动态图编写代码,框架自动转换为静态图网络结构执行计算。 -短短七八年时间,动静态图互相转换的技术在 AI 系统领域发展迅速,大大提升了 AI 算法/模型的开发效率,提高了 AI 产品应用的便利性,实现了计算效率和灵活性的平衡。 +短短七八年时间,动静态图互相转换的技术在 AI 系统领域发展迅速,大大提升了 AI 算法/模型的开发效率,提高了 AI 产品应用的便利性,实现了计算效率和灵活性的平衡。 ![控制流表达](images/framework_trend02.png) @@ -140,11 +140,11 @@ def foo2(s: Tensor) -> Tensor: ## 小结与思考 -- 动态图与静态图的转换技术发展迅速,AI框架通过动静结合的方式,实现了动态图的易用性与静态图的高执行性能的平衡。 +- 动态图与静态图的转换技术发展迅速,AI 框架通过动静结合的方式,实现了动态图的易用性与静态图的高执行性能的平衡。 -- 动态图到静态图的转换主要基于追踪(Tracing)和源代码解析(Parsing)两种方式,Tracing通过记录算子调用序列构建静态图,而源代码解析通过AST转换为静态图表示。 +- 动态图到静态图的转换主要基于追踪(Tracing)和源代码解析(Parsing)两种方式,Tracing 通过记录算子调用序列构建静态图,而源代码解析通过 AST 转换为静态图表示。 -- 动静转换技术面临的挑战包括控制流的准确表示、类型推断的复杂性以及硬件和后端实现的限制,但这些技术的发展极大地提升了AI算法的开发效率和产品的便利性。 +- 动静转换技术面临的挑战包括控制流的准确表示、类型推断的复杂性以及硬件和后端实现的限制,但这些技术的发展极大地提升了 AI 算法的开发效率和产品的便利性。 ## 视频 diff --git a/05Framework/03DataFlow/07Future.md b/05Framework/03DataFlow/07Future.md index a403e3bb..b19fedfb 100644 --- a/05Framework/03DataFlow/07Future.md +++ b/05Framework/03DataFlow/07Future.md @@ -75,7 +75,7 @@ AI 框架的设计很自然地沿用了张量和张量操作,将其作为构 ## 小结与思考 -- 计算图作为AI框架的核心,提供了统一的数据结构和执行逻辑的表示方法,使得神经网络模型的构建、优化和执行更加高效和灵活,同时带来了自动微分、内存优化和算子调度等优势。 +- 计算图作为 AI 框架的核心,提供了统一的数据结构和执行逻辑的表示方法,使得神经网络模型的构建、优化和执行更加高效和灵活,同时带来了自动微分、内存优化和算子调度等优势。 - 面向未来,计算图面临着图表示、大数据融合、部署推理和科学计算等新挑战和应用场景,需要进一步发展以满足更复杂的数据处理需求和多尺度问题的求解。 diff --git a/05Framework/04Parallel/02DataParallel.md b/05Framework/04Parallel/02DataParallel.md index af25ffbf..1ce463c7 100644 --- a/05Framework/04Parallel/02DataParallel.md +++ b/05Framework/04Parallel/02DataParallel.md @@ -1,6 +1,6 @@ -# 数据并行 +# 数据并行(DONE) **数据并行**是一种广泛应用于分布式 AI 系统中的技术,旨在通过将数据集划分为多个子集并在不同计算节点上并行处理这些子集,以提高计算效率和速度。在大规模机器学习和深度学习训练过程中,数据并行可以显著加快模型训练速度,减少训练时间,提升模型性能。大部分的数据并行模型中,每个计算节点都会接收到完整的模型副本,但处理不同的数据子集。通过这种方法,计算任务可以被分摊到多个节点上,从而显著提高处理速度和效率。 @@ -574,11 +574,11 @@ torchrun --nnodes=MIN_SIZE:MAX_SIZE \ - 数据并行技术通过在多个计算节点上分割数据集和并行处理来提升神经网络模型训练的效率。 -- 分布式数据并行(DDP)是数据并行的高级形式,使用多进程方法避免Python GIL限制,支持跨机器扩展,并通过Ring AllReduce算法优化通信。 +- 分布式数据并行(DDP)是数据并行的高级形式,使用多进程方法避免 Python GIL 限制,支持跨机器扩展,并通过 Ring AllReduce 算法优化通信。 -- PyTorch的 DDP 使得在多个NPU上实现分布式训练变得简单,通过自动梯度聚合和参数同步,提高了训练的可扩展性和效率。 +- PyTorch 的 DDP 使得在多个 NPU 上实现分布式训练变得简单,通过自动梯度聚合和参数同步,提高了训练的可扩展性和效率。 -- 弹性数据并行通过动态资源管理、检查点保存和自动故障恢复,增强了分布式训练的鲁棒性和灵活性,PyTorch的Torchelastic组件支持这些特性,使得训练作业能适应节点变化和故障情况。 +- 弹性数据并行通过动态资源管理、检查点保存和自动故障恢复,增强了分布式训练的鲁棒性和灵活性,PyTorch 的 Torchelastic 组件支持这些特性,使得训练作业能适应节点变化和故障情况。 ## 本节视频 diff --git a/05Framework/04Parallel/03ZeRODP.md b/05Framework/04Parallel/03ZeRODP.md index 9d09d4c4..1a4daa38 100644 --- a/05Framework/04Parallel/03ZeRODP.md +++ b/05Framework/04Parallel/03ZeRODP.md @@ -1,6 +1,6 @@ -# 数据并行 +# 数据并行(DONE) 上一节内容介绍了通用的数据并行和分布式数据并行,主要是对神经网络模型的输入数据 mini-batch 进行分布式处理。并且讨论了同步数据并行和异步数据并行的差异点,深入到 PyTorch AI 框架的弹性数据并行是如何实现与处理的。在本节内容中,将会重点关注 AI 框架中如何实现针对权重数据、优化器数据和梯度数据进行分布式并行,并在 PyTorch 框架的具体实现方案。 @@ -16,11 +16,11 @@ - **FP16**:同样是 IEEE 754 标准下的半精度浮点格式。随着深度学习的发展,FP16 逐渐取代了 FP32 的地位。因为相较于 FP32,更低的精度并不会对神经网络的性能产生重大影响。额外的精度不会带来任何好处,反而会更慢、占用更多内存并降低通信速度。FP16 通常用于混合精度训练(MindSpore/PyTorch)。也用于训练后量化,以加快推理速度。其他用于量化的格式还有整数 INT8(8 位整数)、INT4(4 位)甚至 INT1(二进制值)。 -- **BF16**:谷歌最初开发的另一种 16 位格式被称为 "Brain Floating Point Format",简称 "bfloat16"。这个名字源于 "谷歌大脑"(谷歌 Brain),谷歌大脑是谷歌的一个人工智能研究小组,这种格式就是在这个小组构思出来的。最开始是被使用在谷歌芯片 TPU 中,后被广泛使用在 GPU、NPU 等AI芯片中。由于具有更多的指数位,常被用于处理 FP16 的溢出问题。 +- **BF16**:谷歌最初开发的另一种 16 位格式被称为 "Brain Floating Point Format",简称 "bfloat16"。这个名字源于 "谷歌大脑"(谷歌 Brain),谷歌大脑是谷歌的一个人工智能研究小组,这种格式就是在这个小组构思出来的。最开始是被使用在谷歌芯片 TPU 中,后被广泛使用在 GPU、NPU 等 AI 芯片中。由于具有更多的指数位,常被用于处理 FP16 的溢出问题。 - **FP32**:这种格式在很长一段时间内都是深度学习的主力,它是 IEEE 754 标准下的单精度浮点数。长期以来,它一直是神经网络计算的标准类型。长期以来,神经网络中的权重、激活和其他值都默认用 FP32 表示。 -- **TF32**:这是一种十分特殊的格式(无需显示设置,而是自动执行),它将 FP32 数据截断为 TF32 进行计算,然后再转换回 FP32。这一创新的最大优势在于编译器只需在AI 编译层提供支持。其他代码部分则可以继续使用动态范围相同但精度较高的 FP32,无需进行修改。TF32 的快速插入性使得利用 AI 计算加速核心的速度成为可能,而无需过多修改现有代码。 +- **TF32**:这是一种十分特殊的格式(无需显示设置,而是自动执行),它将 FP32 数据截断为 TF32 进行计算,然后再转换回 FP32。这一创新的最大优势在于编译器只需在 AI 编译层提供支持。其他代码部分则可以继续使用动态范围相同但精度较高的 FP32,无需进行修改。TF32 的快速插入性使得利用 AI 计算加速核心的速度成为可能,而无需过多修改现有代码。 TF32 采用与 FP16 相同的 10 位尾数,这满足了人工智能工作负载的精度要求,并且使用了与 FP32 相同的 8 位指数,因此具有相同的数值范围。从技术上讲,它可以视为一种 19 位格式,也可以视为扩展精度的 BF16。 @@ -37,9 +37,9 @@ torch.backends.npu.matmul.allow_16 = True 然而,在深度学习中,所面临的实际上是一个高维函数拟合(或近似)的优化问题,因此并不需要过于精确的数值表示,且使用低精度将会带来显著的计算速度提升。 -在深度学习中,使用 FP16 训练有时会出现下溢出的问题:FP16 的有效的动态范围约为${5.96e}^{-8} \sim 65504$,在训练后期,例如激活函数的梯度会非常小,甚至在梯度乘以学习率后,值会更加小。由于 FP16 的精度范围有限,过小的梯度可能导致更新无效,这个时候就需要使用混精度训练。 +在深度学习中,使用 FP16 训练有时会出现下溢出的问题:FP16 的有效的动态范围约为 ${5.96e}^{-8} \sim 65504$,在训练后期,例如激活函数的梯度会非常小,甚至在梯度乘以学习率后,值会更加小。由于 FP16 的精度范围有限,过小的梯度可能导致更新无效,这个时候就需要使用混精度训练。 -混精度训练可以分为两个部分:**半精度** 和 **权重备份**,如图所示,这里使用 FP16 和 FP32 来举例。在训练开始时,准备两套模型状态,其中一套为 FP32 类型(优化器状态和模型参数),另一套为 FP16 类型(模型参数),在前向传播、反向传播时,都使用 FP16 类型的模型参数进行计算;而在参数更新时,将梯度成与学习率$\eta$相乘,更新到 FP32 类型的模型状态上,在新一轮的训练中,再次将 FP32 类型的模型拷贝为 FP16 类型的模型。这个过程就是**混精度训练**。 +混精度训练可以分为两个部分:**半精度** 和 **权重备份**,如图所示,这里使用 FP16 和 FP32 来举例。在训练开始时,准备两套模型状态,其中一套为 FP32 类型(优化器状态和模型参数),另一套为 FP16 类型(模型参数),在前向传播、反向传播时,都使用 FP16 类型的模型参数进行计算;而在参数更新时,将梯度成与学习率 $\eta$ 相乘,更新到 FP32 类型的模型状态上,在新一轮的训练中,再次将 FP32 类型的模型拷贝为 FP16 类型的模型。这个过程就是**混精度训练**。 ![混精度训练](images/02DataParallel08.png) @@ -92,7 +92,7 @@ scaler.update() 其中这种损失缩放的方式是动态的,每当梯度溢出时候减少损失缩放规模,并且间歇性地尝试增加损失规模,从而实现在不引起溢出的情况下使用最高损失缩放因子,更好地恢复精度。 -**动态损失缩放**的算法会从比较高的缩放因子开始(如$2^{24}$),然后开始进行训练,并在迭代中检查数是否会溢出(Infs/Nans);如果没有梯度溢出,则不调整缩放因子,继续进行迭代;如果检测到梯度溢出,则缩放因子会减半,重新确认梯度更新情况,直到参数不出现在溢出的范围内;在训练的后期,loss 已经趋近收敛稳定,梯度更新的幅度往往小了,这个时候可以允许更高的损失缩放因子来再次防止数据下溢。 +**动态损失缩放**的算法会从比较高的缩放因子开始(如 $2^{24}$),然后开始进行训练,并在迭代中检查数是否会溢出(Infs/Nans);如果没有梯度溢出,则不调整缩放因子,继续进行迭代;如果检测到梯度溢出,则缩放因子会减半,重新确认梯度更新情况,直到参数不出现在溢出的范围内;在训练的后期,loss 已经趋近收敛稳定,梯度更新的幅度往往小了,这个时候可以允许更高的损失缩放因子来再次防止数据下溢。 ### 内存消耗估算 @@ -110,11 +110,11 @@ scaler.update() - 激活值(Activation):在反向传播过程中使用链式法则计算梯度时会用到。有了它算梯度会更快,但它不是必须存储的,因为可以通过重新前向传播来计算; -- 临时存储(Temporary Buffers): 例如把梯度发送到某个NPU上进行 All-Reduce 时产生的存储; +- 临时存储(Temporary Buffers): 例如把梯度发送到某个 NPU 上进行 All-Reduce 时产生的存储; - 碎片化的存储空间(Unusable Fragment Memory):虽然总存储空间是够的,但是如果取不到连续的存储空间相关的请求也会失败。对这类空间浪费可以通过内存整理来解决。 -拿 FP32 与 FP16 的混合精度训练举例,假设模型的参数量是$\Phi$,那么模型状态所消耗的空间为: +拿 FP32 与 FP16 的混合精度训练举例,假设模型的参数量是 $\Phi$,那么模型状态所消耗的空间为:
| Model States | Size (Byte) | @@ -131,13 +131,13 @@ scaler.update() 接下来基于 Transformer 的架构进行具体分析,因为所有参数超过 10 亿的 SOTA 模型都遵循这一架构。分析假设使用 Adam 优化器进行混合精度训练,因为此优化器是训练基于 Transformer 的大模型较为常用。 -**模型状态**:模型状态由优化器状态、梯度和参数组成。基于 Transformer 的模型中的参数总数主要取决于隐藏维度 ($hd$) 和 Transformer 层数($nl$)。Transformer 块中的几乎所有参数都来自每个块内的四个线性层,其大小分别为:($hd$,$3hd$)、($hd$,$hd$)、($hd$,$4hd$) 和($4hd$,$hd$)。因此,基于 Transformer 的模型中的总参数可以近似为: +**模型状态**:模型状态由优化器状态、梯度和参数组成。基于 Transformer 的模型中的参数总数主要取决于隐藏维度 ($hd$)和 Transformer 层数($nl$)。Transformer 块中的几乎所有参数都来自每个块内的四个线性层,其大小分别为:($hd$,$3hd$)、($hd$,$hd$)、($hd$,$4hd$)和($4hd$,$hd$)。因此,基于 Transformer 的模型中的总参数可以近似为: $$ 12 × nl × hd^2 $$ -**剩余状态**:剩余状态主要是指激活内存,它取决于模型结构、批量大小($bsz$)和序列长度($seq$),而且可能相当大。不过激活所需的内存可以通过激活检查点(activation checkpointing)大大减少,假设$ci$是两个激活检查点之间的 Transformer 块数,$bsz × seq × hd$是每个 Transformer 块的输入大小,激活检查点所需的内存估计为: +**剩余状态**:剩余状态主要是指激活内存,它取决于模型结构、批量大小($bsz$)和序列长度($seq$),而且可能相当大。不过激活所需的内存可以通过激活检查点(activation checkpointing)大大减少,假设 $ci$ 是两个激活检查点之间的 Transformer 块数,$bsz × seq × hd$ 是每个 Transformer 块的输入大小,激活检查点所需的内存估计为: $$ 2 × bsz × seq × hd × nl / ci @@ -149,7 +149,7 @@ $$ bsz × seq × ci × (16 × hd + 2 × attn\_heads × seq) $$ -**模型状态工作内存**:模型状态工作内存是指在将所有模型状态卸载到 CPU 或 NVMe 之后,对模型中最大的单个算子执行前向或后向传播所需的 NPU 内存最小量。这大约是由模型中该算子的参数和梯度的大小决定的,因为必须至少有足够的内存来保存向后传播的参数及其梯度。Transformer 的最大的算子是将隐藏状态从$h$转换为$4h$的线性层。该线性层的参数和梯度的大小为: +**模型状态工作内存**:模型状态工作内存是指在将所有模型状态卸载到 CPU 或 NVMe 之后,对模型中最大的单个算子执行前向或后向传播所需的 NPU 内存最小量。这大约是由模型中该算子的参数和梯度的大小决定的,因为必须至少有足够的内存来保存向后传播的参数及其梯度。Transformer 的最大的算子是将隐藏状态从 $h$ 转换为 $4h$ 的线性层。该线性层的参数和梯度的大小为: $$ 4 × hd × 4hd @@ -173,39 +173,39 @@ ZeRO 有两套优化方案: ### ZeRO-DP -ZeRO-DP 对模型状态进行切分,具体来说,每个NPU都只会会存储$\frac{1}{N_d}$的模型状态(其中$N_d$为并行度),在需要时通过集合通信 All-Gather 获取参数。ZeRO-DP 保留了数据并行训练(DP)的高效率,同时实现了模型并行(MP)的内存效率优势。 +ZeRO-DP 对模型状态进行切分,具体来说,每个 NPU 都只会会存储 $\frac{1}{N_d}$ 的模型状态(其中 $N_d$ 为并行度),在需要时通过集合通信 All-Gather 获取参数。ZeRO-DP 保留了数据并行训练(DP)的高效率,同时实现了模型并行(MP)的内存效率优势。 由于数据并行的模型状态在所有数据并行进程中冗余存储,因此内存效率低下,但数据并行具有更高的计算粒度和更低的通信量,从而具有更高的训练效率。模型并行的通信开销很大,因此可扩展性比数据并行低,但模型并行对模型状态进行分区,获得了较高的内存效率。 -ZeRO-DP 对模型状态进行分区而不是复制它们,并使用动态通信调度最小化通信量。通过这样做,ZeRO-DP 随着数据并行程度的增加线性减少模型在每块NPU的内存占用,同时保持通信量接近默认数据并行的通信量,从而保持效率。ZeRO-DP 有三个主要优化阶段,分别对应于优化器状态、梯度和参数的划分,在累积启用时: +ZeRO-DP 对模型状态进行分区而不是复制它们,并使用动态通信调度最小化通信量。通过这样做,ZeRO-DP 随着数据并行程度的增加线性减少模型在每块 NPU 的内存占用,同时保持通信量接近默认数据并行的通信量,从而保持效率。ZeRO-DP 有三个主要优化阶段,分别对应于优化器状态、梯度和参数的划分,在累积启用时: -1. **优化状态分区**(Partition optimizer states,$P_{os}$):又称为 ZeRO-1,将优化器状态按并行度均匀分区,每个进程只需存储$\frac{1}{N_d}$的优化器状态(其中$N_d$为并行度)。这可将内存消耗减少到 1 / 4,且无额外通信开销。 +1. **优化状态分区**(Partition optimizer states,$P_{os}$):又称为 ZeRO-1,将优化器状态按并行度均匀分区,每个进程只需存储 $\frac{1}{N_d}$ 的优化器状态(其中 $N_d$ 为并行度)。这可将内存消耗减少到 1 / 4,且无额外通信开销。 2. **添加梯度分区**(Partition gradients,$P_{os+g}$):又称为 ZeRO-2,在优化器状态分区的基础上,对梯度也进行分区。每个进程只需存储用于更新自身参数分区所需的梯度。这可减少 8 倍的内存消耗,且无额外通信开销。 -3. **添加参数分区**(Partition parameters,$P_{os+g+p}$):又称为 ZeRO-3,在优化器状态和梯度分区的基础上,对参数也进行分区。每个进程只存储自身的参数分区,在前向反向传播时需要从其他进程收集所需的参数分区。这会使通信量增加约 50%,但可以实现与并行度$N_d$成正比的内存减少。 +3. **添加参数分区**(Partition parameters,$P_{os+g+p}$):又称为 ZeRO-3,在优化器状态和梯度分区的基础上,对参数也进行分区。每个进程只存储自身的参数分区,在前向反向传播时需要从其他进程收集所需的参数分区。这会使通信量增加约 50%,但可以实现与并行度 $N_d$ 成正比的内存减少。 ![数据并行](images/02DataParallel09.png) -通过这三个阶段的优化,ZeRO-DP 最终能够在保持数据并行高效的同时,将每个NPU的内存消耗降低至$\frac{1}{N_d}$的水平,使得利用少量硬件资源训练万亿参数等超大模型成为可能,接下来进行每个阶段的详细介绍。这里假设模型使用混精度训练,模型参数量为 4$\Psi$。 +通过这三个阶段的优化,ZeRO-DP 最终能够在保持数据并行高效的同时,将每个 NPU 的内存消耗降低至 $\frac{1}{N_d}$ 的水平,使得利用少量硬件资源训练万亿参数等超大模型成为可能,接下来进行每个阶段的详细介绍。这里假设模型使用混精度训练,模型参数量为 4$\Psi$。 #### ZeRO-1 内存分析 -根据之前的介绍知道,优化器状态是训练过程中NPU内存中的主要保存内容,但只有在参数更新的时候会被用到。ZeRO-1 的核心思想是将优化器状态分布到多个NPU上,减少每个NPU所需的显存,在需要参数更新时进行聚合。 +根据之前的介绍知道,优化器状态是训练过程中 NPU 内存中的主要保存内容,但只有在参数更新的时候会被用到。ZeRO-1 的核心思想是将优化器状态分布到多个 NPU 上,减少每个 NPU 所需的显存,在需要参数更新时进行聚合。 ![数据并行](images/02DataParallel10.png) -1. **数据分片(a)**:从优化器状态分片开始,将优化器状态分成 N 份,每个NPU保存一份分片,并将训练批次数据(batch data)分成 N 份,每个NPU处理一份数据。 +1. **数据分片(a)**:从优化器状态分片开始,将优化器状态分成 N 份,每个 NPU 保存一份分片,并将训练批次数据(batch data)分成 N 份,每个 NPU 处理一份数据。 -2. **前向与后向计算(b)**:每个NPU执行一步前向(forward)和后向(backward)计算,得到局部梯度$G_i$。 +2. **前向与后向计算(b)**:每个 NPU 执行一步前向(forward)和后向(backward)计算,得到局部梯度 $G_i$。 -3. **梯度聚合(b)**:对各个NPU上的局部梯度$G_i$执行 All-Reduce 操作,得到完整梯度$G$。这一步的单个NPU的通信量为$2\Phi$。 +3. **梯度聚合(b)**:对各个 NPU 上的局部梯度 $G_i$ 执行 All-Reduce 操作,得到完整梯度 $G$。这一步的单个 NPU 的通信量为 $2\Phi$。 -4. **权重更新(c)**:使用完整梯度$G$和优化器状态更新权重$W$。每个NPU保存一部分权重$W$,并通过 All-Gather 操作从其他NPU获取更新后的部分权重,完成权重更新。此时的单个NPU通信量为$\Phi$。 +4. **权重更新(c)**:使用完整梯度 $G$ 和优化器状态更新权重 $W$。每个 NPU 保存一部分权重 $W$,并通过 All-Gather 操作从其他 NPU 获取更新后的部分权重,完成权重更新。此时的单个 NPU 通信量为 $\Phi$。 -在$P_{os}$阶段,将 Adam 优化器状态根据数据并行维度$N_d$分成等份。每个NPU只需存储和更新总优化器状态的$1/N_d$,并更新对应参数。 +在 $P_{os}$ 阶段,将 Adam 优化器状态根据数据并行维度 $N_d$ 分成等份。每个 NPU 只需存储和更新总优化器状态的 $1/N_d$,并更新对应参数。 -通过分片和聚合操作,显存占用从$4\Psi + K\Psi$降低到$4\Psi + K\Psi / N_d$。当$N_d$很大时,显存占用接近于$4\Psi$,带来约 4 倍显存节约。 +通过分片和聚合操作,显存占用从 $4\Psi + K\Psi$ 降低到 $4\Psi + K\Psi / N_d$。当 $N_d$ 很大时,显存占用接近于 $4\Psi$,带来约 4 倍显存节约。 #### ZeRO-2 内存分析 @@ -213,19 +213,19 @@ ZeRO-2 在 ZeRO-1 的基础上进一步优化,通过对梯度(Grad)也进 ![数据并行](images/02DataParallel11.png) -1. **数据分片**:从优化器状态和梯度分片开始,将优化器状态和梯度分成 N 份,每个NPU保存一份分片,并将训练批次数据(batch data)分成 N 份,每个NPU处理一份数据。 +1. **数据分片**:从优化器状态和梯度分片开始,将优化器状态和梯度分成 N 份,每个 NPU 保存一份分片,并将训练批次数据(batch data)分成 N 份,每个 NPU 处理一份数据。 -2. **前向与后向计算**:每个NPU执行一步前向(forward)和后向(backward)计算,得到局部梯度$G_i$。 +2. **前向与后向计算**:每个 NPU 执行一步前向(forward)和后向(backward)计算,得到局部梯度 $G_i$。 -3. **梯度分片与聚合**:对各块NPU上的局部梯度$G_i$执行 Reduce-Scatter 操作,确保每个NPU只维护自己负责的梯度部分。比如,NPU 1 负责维护梯度$G_1$,其他NPU只需要将$G_1$对应位置的梯度发送给NPU 1。聚合完毕后,NPU 1 释放无用的显存部分,单卡通信量为$\Phi$。 +3. **梯度分片与聚合**:对各块 NPU 上的局部梯度 $G_i$ 执行 Reduce-Scatter 操作,确保每个 NPU 只维护自己负责的梯度部分。比如,NPU 1 负责维护梯度 $G_1$,其他 NPU 只需要将 $G_1$ 对应位置的梯度发送给 NPU 1。聚合完毕后,NPU 1 释放无用的显存部分,单卡通信量为 $\Phi$。 -4. **权重更新**:每个NPU使用自身维护的梯度$G_i$和优化器状态$O_i$更新相应的权重$W_i$。 +4. **权重更新**:每个 NPU 使用自身维护的梯度 $G_i$ 和优化器状态 $O_i$ 更新相应的权重 $W_i$。 -5. **权重聚合**:对权重$W_i$执行 All-Gather 操作,将其他NPU的权重$W_i$同步至完整权重$W$,单卡通信量为$\Phi$。 +5. **权重聚合**:对权重 $W_i$ 执行 All-Gather 操作,将其他 NPU 的权重 $W_i$ 同步至完整权重 $W$,单卡通信量为 $\Phi$。 -在$P_{os+g}$阶段,梯度与优化器强相关,因此优化器可以更新其独立的梯度参数。更新梯度参数时使用 Reduce-Scatter 操作,梯度参数更新后立即释放。具体实现中使用分桶(Bucket)技术,将梯度分到不同的桶中并在桶上进行 Reduce-Scatter 操作。 +在 $P_{os+g}$ 阶段,梯度与优化器强相关,因此优化器可以更新其独立的梯度参数。更新梯度参数时使用 Reduce-Scatter 操作,梯度参数更新后立即释放。具体实现中使用分桶(Bucket)技术,将梯度分到不同的桶中并在桶上进行 Reduce-Scatter 操作。 -通过移除梯度和优化器状态冗余,显存占用从$4\Psi + K\Psi$降低到$2\Psi + K\Psi / N_d$。当$N_d$很大时,显存占用接近于$2\Psi$,带来约 8 倍显存节约。 +通过移除梯度和优化器状态冗余,显存占用从 $4\Psi + K\Psi$ 降低到 $2\Psi + K\Psi / N_d$。当 $N_d$ 很大时,显存占用接近于 $2\Psi$,带来约 8 倍显存节约。 #### ZeRO-3 内存分析 @@ -233,25 +233,25 @@ ZeRO-3 在 ZeRO-1 和 ZeRO-2 的基础上进一步优化,通过对优化器状 ![数据并行](images/02DataParallel12.png) -1. **数据分片**:对优化器状态、梯度和权重进行全面切分,每个NPU保存一份分片,将训练批次数据(batch data)分成 N 份,每块NPU处理一份数据。 +1. **数据分片**:对优化器状态、梯度和权重进行全面切分,每个 NPU 保存一份分片,将训练批次数据(batch data)分成 N 份,每块 NPU 处理一份数据。 -2. **前向计算**:在前向计算过程中,对权重$W$执行 All-Gather 操作,从各NPU获取分布在不同NPU上的权重,得到完整的权重$W$。不属于自身的权重$W_{others}$被抛弃,单卡通信量为$\Phi$。 +2. **前向计算**:在前向计算过程中,对权重 $W$ 执行 All-Gather 操作,从各 NPU 获取分布在不同 NPU 上的权重,得到完整的权重 $W$。不属于自身的权重 $W_{others}$ 被抛弃,单卡通信量为 $\Phi$。 -3. **后向计算**:在后向计算过程中,再次对权重$W$执行 All-Gather 操作,取回完整权重,并抛弃不属于自身的部分$W_{others}$,单卡通信量为$\Phi$。 +3. **后向计算**:在后向计算过程中,再次对权重 $W$ 执行 All-Gather 操作,取回完整权重,并抛弃不属于自身的部分 $W_{others}$,单卡通信量为 $\Phi$。 -4. **梯度聚合**:后向计算得到各自的梯度$G_i$后,对梯度$G_i$执行 Reduce-Scatter 操作,从其他NPU聚合自身维护的梯度$G_i$。聚合操作结束后,立刻抛弃不是自己维护的梯度,单卡通信量为$\Phi$。 +4. **梯度聚合**:后向计算得到各自的梯度 $G_i$ 后,对梯度 $G_i$ 执行 Reduce-Scatter 操作,从其他 NPU 聚合自身维护的梯度 $G_i$。聚合操作结束后,立刻抛弃不是自己维护的梯度,单卡通信量为 $\Phi$。 -5. **权重更新**:每块NPU只保存其权重参数$W_i$,由于只维护部分参数$W_i$,因此无需对$W_i$执行 All-Reduce 操作。 +5. **权重更新**:每块 NPU 只保存其权重参数 $W_i$,由于只维护部分参数 $W_i$,因此无需对 $W_i$ 执行 All-Reduce 操作。 -在$P_{os+g+p}$阶段,优化器状态、梯度和权重均进行划分。在前向和后向计算过程中,通过广播从其他NPU中获取参数,减少每块NPU中的显存占用。通过移除梯度、优化器状态和权重的冗余,将显存占用从$4\Psi + K\Psi$降低到$(4\Psi + K\Psi)/ N_d$。 +在 $P_{os+g+p}$ 阶段,优化器状态、梯度和权重均进行划分。在前向和后向计算过程中,通过广播从其他 NPU 中获取参数,减少每块 NPU 中的显存占用。通过移除梯度、优化器状态和权重的冗余,将显存占用从 $4\Psi + K\Psi$ 降低到 $(4\Psi + K\Psi)/ N_d$。 -这种方法通过增加通信开销,以通信换显存,使得显存占用与$N_d$成正比。显存占用的优化带来了 1.5 倍单卡通信量的增加。 +这种方法通过增加通信开销,以通信换显存,使得显存占用与 $N_d$ 成正比。显存占用的优化带来了 1.5 倍单卡通信量的增加。 ### ZeRO-R 除了优化模型状态(优化器状态、梯度和参数)的内存利用率,ZeRO 还专门针对剩余状态(如激活数据、临时缓冲区和内存碎片等)进行了优化,以进一步减少内存开销。ZeRO-R 对剩余状态进行了切分和优化,主要包括以下几个策略: -1. **分区激活检查点**(Partitioned Activation Checkpointing,$P_{a}$):解决了模型并行时激活内存冗余的问题。在模型并行中,每个NPU需要保存完整的输入激活数据才能计算自己分到的模型部分。ZeRO-R 将激活检查点按模型并行度$N_m$进行分区,每个NPU只需存储$\frac{1}{N_m}$的激活检查点。在需要时通过 All-Gather 操作重构出完整激活数据,从而按$N_m$的比例减少激活内存。在极端情况下,当模型规模很大时,ZeRO-R 甚至可以将分区后的激活检查点卸载到 CPU 内存($P_{a+cpu}$),再次降低NPU内存占用,代价是额外的Host-Device通信开销。该策略在大模型训练时会自动开启,以保证足够的NPU内存用于计算。 +1. **分区激活检查点**(Partitioned Activation Checkpointing,$P_{a}$):解决了模型并行时激活内存冗余的问题。在模型并行中,每个 NPU 需要保存完整的输入激活数据才能计算自己分到的模型部分。ZeRO-R 将激活检查点按模型并行度 $N_m$ 进行分区,每个 NPU 只需存储 $\frac{1}{N_m}$ 的激活检查点。在需要时通过 All-Gather 操作重构出完整激活数据,从而按 $N_m$ 的比例减少激活内存。在极端情况下,当模型规模很大时,ZeRO-R 甚至可以将分区后的激活检查点卸载到 CPU 内存($P_{a+cpu}$),再次降低 NPU 内存占用,代价是额外的 Host-Device 通信开销。该策略在大模型训练时会自动开启,以保证足够的 NPU 内存用于计算。 2. **恒定大小的缓冲区**(Constant Size Buffer,$C_{b}$):一些操作如 All-reduce 需要将张量拼成连续的临时缓冲区,使用恒定大小的缓冲区来避免临时缓冲区随着模型大小的增加而爆炸,同时使它们足够大以保持效率。 @@ -290,7 +290,7 @@ loss.backward() optim.step() ``` -首先设置当前的 NPU,然后将 `my_module` 包装成一个 FSDP 模块。这会将模型的参数、梯度和优化器状态在多个 NPU之间进行分片,从而减少每个NPU 上的内存占用。 +首先设置当前的 NPU,然后将 `my_module` 包装成一个 FSDP 模块。这会将模型的参数、梯度和优化器状态在多个 NPU 之间进行分片,从而减少每个 NPU 上的内存占用。 再来看一个更详细的例子,使用 `fsdp_main` 函数,用于分布式训练 T5 模型。函数通过 `setup_model` 函数加载模型和分词器,并设置分布式训练的相关环境变量,包括 `local_rank`、`rank` 和 `world_size`。 @@ -302,7 +302,7 @@ optim.step() 为了支持混精度训练(bf16),如果当前 CUDA 版本和 NCCL 版本支持 bf16,并且 CUDA 版本大于或等于 11.0,那么 `bf16_ready` 变量为 `True`,并将 `mp_policy` 设为 `bfSixteen`。否则,`mp_policy` 设为 `None`,默认使用 fp32(单精度)训练。 -在模型仍在 CPU 上时,代码将模型传入 `FSDP`(Fully Sharded Data Parallel)模块,配置自动包装策略、混合精度策略(`mixed_precision`),以及当前NPU ID(`device_id`)。这一步是将模型转换为分布式训练的模式,以便在多个NPU之间分片和并行计算。 +在模型仍在 CPU 上时,代码将模型传入 `FSDP`(Fully Sharded Data Parallel)模块,配置自动包装策略、混合精度策略(`mixed_precision`),以及当前 NPU ID(`device_id`)。这一步是将模型转换为分布式训练的模式,以便在多个 NPU 之间分片和并行计算。 ```python def fsdp_main(args): @@ -359,32 +359,32 @@ def fsdp_main(args): ### ZeRO-DP -最先进的 All-Reduce 实现采用两步法,第一步是 Reduce-Scatte 操作,一个是 All-Gather 操作,每个流程的总数据移动量为$\Psi$个元素(对于$\Psi$个元素的数据)。因此,标准 DP 在每个训练步骤中会产生 2$\Psi$次数据移动。 +最先进的 All-Reduce 实现采用两步法,第一步是 Reduce-Scatte 操作,一个是 All-Gather 操作,每个流程的总数据移动量为 $\Psi$ 个元素(对于 $\Psi$ 个元素的数据)。因此,标准 DP 在每个训练步骤中会产生 2$\Psi$ 次数据移动。 -通过梯度分区($P_{os+g}$),每个进程只存储更新相应参数分区所需的梯度部分。因此,ZeRO 只需要对梯度先进行 Reduce-Scatte 操作,产生的通信量为$\Psi$。在每个进程更新完自己负责的参数分区后,会执行一次 All-Gather,从所有数据并行进程中收集所有更新的参数。这也会产生$\Psi$的通信量。因此,每个训练步骤的总通信量为$\Psi$+$\Psi$= 2$\Psi$,与标准 DP 相同。 +通过梯度分区($P_{os+g}$),每个进程只存储更新相应参数分区所需的梯度部分。因此,ZeRO 只需要对梯度先进行 Reduce-Scatte 操作,产生的通信量为 $\Psi$。在每个进程更新完自己负责的参数分区后,会执行一次 All-Gather,从所有数据并行进程中收集所有更新的参数。这也会产生 $\Psi$ 的通信量。因此,每个训练步骤的总通信量为 $\Psi$+$\Psi$= 2$\Psi$,与标准 DP 相同。 在参数分区($P_{os+g+p}$)后,每个数据并行进程只存储其更新的参数。因此,在前向传播过程中,它需要接收所有其他分区的参数。不过,这可以通过流水线操作来避免内存开销——在对模型中与特定分区对应的部分进行前向传播计算之前,负责该分区的数据并行进程可以向所有数据并行进程广播权重。一旦该分区的前向传播计算完成,参数就可以被丢弃。 -因此,总通信量为$\Psi × N_d / N_d = \Psi$。通过在整个前向传播中通过 All-gather 传播参数已重新获取参数,并在使用完参数后将其丢弃。而在后向传播时,需要以相反的顺序再次进行参数获取。参数的通信为 2$\Psi$,在参数更新时只需要执行一个 Reduce-Scatte 操作,通信量为$\Psi$,因此总通信量是 3$\Psi$,是标准 DP 的 1.5 倍。 +因此,总通信量为 $\Psi × N_d / N_d = \Psi$。通过在整个前向传播中通过 All-gather 传播参数已重新获取参数,并在使用完参数后将其丢弃。而在后向传播时,需要以相反的顺序再次进行参数获取。参数的通信为 2$\Psi$,在参数更新时只需要执行一个 Reduce-Scatte 操作,通信量为 $\Psi$,因此总通信量是 3$\Psi$,是标准 DP 的 1.5 倍。 ### ZeRO-R -ZeRO-R 的通信开销取决于模型大小、检查点策略和模型并行(MP)策略。与标准模型并行相比(其中没有对激活进行分区),ZeRO-R$P_{a}$的通信开销通常不到标准模型并行的十分之一。 +ZeRO-R 的通信开销取决于模型大小、检查点策略和模型并行(MP)策略。与标准模型并行相比(其中没有对激活进行分区),ZeRO-R$P_{a}$ 的通信开销通常不到标准模型并行的十分之一。 -在使用激活检查点的 Megatron-LM 中,每个 Transformer 块在前向传播中执行两次大小为$batch × seq × length × hidden\_dim$的 All-Reduce 操作,然后在反向传播中再执行两次。在使用激活检查点的 ZeRO-R 中,每个前向重计算激活之前需要执行一个额外的 All-Gather 操作。 +在使用激活检查点的 Megatron-LM 中,每个 Transformer 块在前向传播中执行两次大小为 $batch × seq × length × hidden\_dim$ 的 All-Reduce 操作,然后在反向传播中再执行两次。在使用激活检查点的 ZeRO-R 中,每个前向重计算激活之前需要执行一个额外的 All-Gather 操作。 -通常情况下,对于每个 Transformer 块的输入激活进行检查点,因此每个 Transformer 块需要一个 All-Gather 操作。因此,ZeRO-R$P_{a}$的通信开销为$seq\_length × hidden\_dim$,仅增加不到 10%。 +通常情况下,对于每个 Transformer 块的输入激活进行检查点,因此每个 Transformer 块需要一个 All-Gather 操作。因此,ZeRO-R$P_{a}$ 的通信开销为 $seq\_length × hidden\_dim$,仅增加不到 10%。 -当 MP 与 DP 一起使用时, ZeRO-R$P_{a}$可以将数据并行通信量减少一个数量级,而模型并行通信量只增加 10%,并且当数据并行通信是性能瓶颈时,可以显着提高效率。通过模型并行可以减少数据并行的内存消耗,从而可以成比例地增加批处理大小。 +当 MP 与 DP 一起使用时,ZeRO-R$P_{a}$ 可以将数据并行通信量减少一个数量级,而模型并行通信量只增加 10%,并且当数据并行通信是性能瓶颈时,可以显着提高效率。通过模型并行可以减少数据并行的内存消耗,从而可以成比例地增加批处理大小。 -对于大模型,MP 可以增加到单节点最大NPU数量,从而可以将批处理大小增加多达 NPU数据量的倍数。数据并行训练的通信量与批处理大小成反比,由于$P_{a}$导致批处理大小增加一个数量级,可能会导致数据并行通信量减少一个数量级。 +对于大模型,MP 可以增加到单节点最大 NPU 数量,从而可以将批处理大小增加多达 NPU 数据量的倍数。数据并行训练的通信量与批处理大小成反比,由于 $P_{a}$ 导致批处理大小增加一个数量级,可能会导致数据并行通信量减少一个数量级。 -如果应用$P_{a+cpu}$,则分区激活检查点会被卸载到 CPU,将激活内存需求减少到接近零,但与$P_{a}$相比,往返 CPU 内存的数据移动增加了 2 倍。如果 DP 通信量是主要瓶颈,由于批处理大小较小,$P_{a+cpu}$也可以通过增加批处理大小来提高效率,只要 CPU 数据传输开销小于 DP 通信量开销。 +如果应用 $P_{a+cpu}$,则分区激活检查点会被卸载到 CPU,将激活内存需求减少到接近零,但与 $P_{a}$ 相比,往返 CPU 内存的数据移动增加了 2 倍。如果 DP 通信量是主要瓶颈,由于批处理大小较小,$P_{a+cpu}$ 也可以通过增加批处理大小来提高效率,只要 CPU 数据传输开销小于 DP 通信量开销。 #### ZeRO-Infinity 可以使用**峰值计算吞吐量($peak_{tp}$)**、**数据移动带宽($bw$)** 及其**算术强度($ait$)** 来估算 ZeRO-Infinity -的训练效率,因为它还涉及到了NPU之间的数据移动。 +的训练效率,因为它还涉及到了 NPU 之间的数据移动。 工作负载的**算术强度(AIT)** 是总计算量与计算所需数据量之间的比率。它描述了每次数据移动所需的计算量。AIT 越高,意味着对数据移动带宽的要求越低,因为每加载一个数据,加速器就能完成更多计算。 @@ -411,7 +411,7 @@ efficienc𝑦 \end{aligned} $$ -同样以 Transformer 为例:每次迭代的总计算量可以由参数数量、序列长度和批量大小估算,即对于前向传播为$2 × bsz × seq × params$,反向传播的成本大约是正向传播的两倍。因此可以估算计算量: +同样以 Transformer 为例:每次迭代的总计算量可以由参数数量、序列长度和批量大小估算,即对于前向传播为 $2 × bsz × seq × params$,反向传播的成本大约是正向传播的两倍。因此可以估算计算量: $$ computation\_per\_iter = 2 × 4 × bsz × seq × parameters = 2 × 4 × 12 × bsz × seq × nl × hd^2 @@ -419,19 +419,19 @@ $$ 在前向和反向传播期间,模型参数必须从源位置加载到 NPU 寄存器至少两次(前向传播期间和实际后向传播期间),导致 2 次的数据移动。在存在激活检查点的情况下,可以在向后传递过程中额外加载一次参数以进行重新计算。 -此外,梯度必须至少从 NPU 寄存器存储到其最终位置一次。因此,假设参数和梯度存储在相同的最终位置,则前向和后向传递期间的总数据移动将为$4 × parameters$,即$2 × 4 × parameters$(以字节为单位)。因此参数和梯度的 ait 为: +此外,梯度必须至少从 NPU 寄存器存储到其最终位置一次。因此,假设参数和梯度存储在相同的最终位置,则前向和后向传递期间的总数据移动将为 $4 × parameters$,即 $2 × 4 × parameters$(以字节为单位)。因此参数和梯度的 ait 为: $$ seq × bsz $$ -在优化器迭代期间,必须至少读取一次优化器状态,​​并且必须至少写入一次优化器状态。因此,总数据移动量为$2 × optimizer_states$,大约为$2 × 16 × parameters$字节。因此,在完整的训练迭代期间,优化器状态的 ait 为: +在优化器迭代期间,必须至少读取一次优化器状态,​​并且必须至少写入一次优化器状态。因此,总数据移动量为 $2 × optimizer_states$,大约为 $2 × 16 × parameters$ 字节。因此,在完整的训练迭代期间,优化器状态的 ait 为: $$ seq × bsz/4 $$ -在前向传播期间,激活检查点必须保存到其最终位置,并且必须在后向传播期间获取。因此,激活检查点的总数据移动量(以字节为单位)为$2 × total\_activation\_checkpoints\_in\_b𝑦tes$,带入之间计算的激活检查点大小,可以得到总数据移动量$4 × nl/ci × hd × seq × bsz$。所以激活检查点的 ait 为: +在前向传播期间,激活检查点必须保存到其最终位置,并且必须在后向传播期间获取。因此,激活检查点的总数据移动量(以字节为单位)为 $2 × total\_activation\_checkpoints\_in\_b𝑦tes$,带入之间计算的激活检查点大小,可以得到总数据移动量 $4 × nl/ci × hd × seq × bsz$。所以激活检查点的 ait 为: $$ 24 × hd × ci @@ -439,17 +439,17 @@ $$ 模型状态和激活检查点对带宽的要求大不相同。前者只取决于**批量大小和序列长度**,而后者只取决于**激活检查点的频率和模型的隐藏维度大小**。在实际中,参数和梯度的带宽超过 70 GB/s,即使是最小的批处理量,也能实现超过 50% 的效率。在这种带宽下,数据移动理论上可以与计算完全重叠,从而实现 100% 的效率。与参数和梯度相比,优化器状态需要高出近 4 倍的带宽才能达到 50% 的效率。 -此外,优化器状态在前向和后向传播结束时更新,不能与计算重叠。因此,它们需要更大的带宽来保持整个 DL 工作负载的效率。例如,在每个 NPU的批处理量为 2 的情况下,要达到 90% 的效率,需要近 1.5 TB/s 的有效带宽,甚至超过了 NPU 内存带宽。启用激活检查点后,即使隐藏大小为 2K,2 GB/s 的微薄带宽也能维持 50% 以上的效率。当隐藏大小超过 8K 时,带宽需求降至 1 GB/s 以下。 +此外,优化器状态在前向和后向传播结束时更新,不能与计算重叠。因此,它们需要更大的带宽来保持整个 DL 工作负载的效率。例如,在每个 NPU 的批处理量为 2 的情况下,要达到 90% 的效率,需要近 1.5 TB/s 的有效带宽,甚至超过了 NPU 内存带宽。启用激活检查点后,即使隐藏大小为 2K,2 GB/s 的微薄带宽也能维持 50% 以上的效率。当隐藏大小超过 8K 时,带宽需求降至 1 GB/s 以下。 ## 小结与思考 -- FSDP 通过在多个 NPU 间分片模型的权重、梯度和优化器状态,显著降低了单个NPU上的内存占用,使得训练更大模型成为可能。 +- FSDP 通过在多个 NPU 间分片模型的权重、梯度和优化器状态,显著降低了单个 NPU 上的内存占用,使得训练更大模型成为可能。 -- ZeRO 技术通过优化器状态、梯度和参数的分区,以及将激活检查点卸载到CPU或NVMe内存,进一步提升了内存效率,支持了大模型的训练。 +- ZeRO 技术通过优化器状态、梯度和参数的分区,以及将激活检查点卸载到 CPU 或 NVMe 内存,进一步提升了内存效率,支持了大模型的训练。 -- ZeRO-Infinity作为ZeRO的扩展,智能地在NPU、CPU和NVMe等异构内存组件之间迁移参数,消除了NPU内存限制,为训练高达万亿参数的模型提供了可能。 +- ZeRO-Infinity 作为 ZeRO 的扩展,智能地在 NPU、CPU 和 NVMe 等异构内存组件之间迁移参数,消除了 NPU 内存限制,为训练高达万亿参数的模型提供了可能。 -- 尽管ZeRO技术增加了通信开销,但通过精心设计的通信策略和利用现代硬件的高带宽,可以保持训练效率,同时实现内存效率和计算性能的平衡。 +- 尽管 ZeRO 技术增加了通信开销,但通过精心设计的通信策略和利用现代硬件的高带宽,可以保持训练效率,同时实现内存效率和计算性能的平衡。 ## 本节视频 diff --git a/05Framework/04Parallel/04TensorParallel.md b/05Framework/04Parallel/04TensorParallel.md index b5122991..3261c166 100644 --- a/05Framework/04Parallel/04TensorParallel.md +++ b/05Framework/04Parallel/04TensorParallel.md @@ -1,18 +1,18 @@ -# 模型并行 +# 张量并行 在大模型的训练中,单个设备往往无法满足计算和存储需求,因此需要借助分布式训练技术。其中,模型并行(Model Parallelism, MP)是一种重要的方法。模型并行的基本思想是将模型的计算任务拆分到不同的设备上执行,以提高训练效率和处理更大规模的模型。下面将重点介绍模型并行中的张量并行。 -## 朴素模型并行 +## 朴素张量并行 -模型并行广泛应用于分布式训练技术。之前的部分已经解释了如何使用数据并行在多个设备上训练神经网络;这种方法一般来说将相同的模型复制到所有设备,每个设备消耗不同部分的输入数据。虽然这可以显著加速训练过程,但在某些情况下模型过大无法放入单个设备时,这种方法并不奏效。 +张量并行广泛应用于分布式训练技术。之前的部分已经解释了如何使用数据并行在多个设备上训练神经网络;这种方法一般来说将相同的模型复制到所有设备,每个设备消耗不同部分的输入数据。虽然这可以显著加速训练过程,但在某些情况下模型过大无法放入单个设备时,这种方法并不奏效。 -本节展示了如何通过使用**模型并行**解决这个问题。与数据并行相反,模型并行将单个模型拆分到不同的设备上,而不是在每个设备上复制整个模型(具体来说,假设一个模型 `m` 包含 6 层:使用数据并行时,每个设备将拥有这 6 层的副本,而使用模型并行在两个设备上时,每个设备只拥有 3 层)。 +本节展示了如何通过使用**朴素张量并行**解决这个问题。与数据并行相反,张量并行将单个模型拆分到不同的设备上,而不是在每个设备上复制整个模型(具体来说,假设一个模型 `m` 包含 6 层:使用数据并行时,每个设备将拥有这 6 层的副本,而使用张量并行在两个设备上时,每个设备只拥有 3 层)。 -![模型并行](images/03ModelParallel01.png) +![张量并行](images/03ModelParallel01.png) -我们来看一个简单的模型并行的例子: +来看一个简单的张量并行的例子: ```python from torchvision.models.resnet import ResNet, Bottleneck @@ -27,35 +27,41 @@ class ModelParallelResNet50(ResNet): self.seq1 = nn.Sequential( self.conv1, self.bn1, self.relu, self.maxpool, self.layer1,self.layer2 - ).to('cuda:0') + ).to('npu:0') self.seq2 = nn.Sequential( self.layer3, self.layer4, self.avgpool, - ).to('cuda:1') - self.fc.to('cuda:1') + ).to('npu:1') + self.fc.to('npu:1') def forward(self, x): - x = self.seq2(self.seq1(x).to('cuda:1')) + x = self.seq2(self.seq1(x).to('npu:1')) return self.fc(x.view(x.size(0), -1)) ``` 上面的代码展示了如何将 `torchvision.models.resnet50()` 分解到两个设备,将每个块放置在不同的设备上,并移动输入和中间输出以匹配层设备。思路是继承现有的 `ResNet` 模块,并在构造过程中将层分配到两个设备。然后,重写 `forward` 方法,通过移动中间输出连接两个子网络。 -朴素的模型并行实现解决了模型过大无法放入单个设备的问题。然而,你可能已经注意到,如果模型能够放入单个设备,模型并行将比在单个设备上运行更慢。这是因为在任何时候,只有一个设备在工作,而另一个设备处于空闲状态。当中间输出需要从 `cuda:0` 复制到 `cuda:1` 时,性能会进一步恶化。 +朴素张量并行实现解决了模型过大无法放入单个设备的问题。然而,你可能已经注意到,如果模型能够放入单个设备,朴素张量并行将比在单个设备上运行更慢。这是因为在任何时候,只有一个设备在工作,而另一个设备处于空闲状态。当中间输出需要从 `npu:0` 复制到 `npu:1` 时,性能会进一步恶化。 -![模型并行](images/03ModelParallel02.png) +![朴素张量并行](images/03ModelParallel02.png) -实际上模型并行实现的执行时间比现有的单设备实现慢 `7%`。因此,我们可以得出结论,跨设备复制张量的开销约为 7%。仍有改进的空间,因为我们知道在整个执行过程中有一个设备是空闲的。一种选择是进一步将每个批次分成流水线的分片,这样当一个分片到达第二个子网络时,下一个分片可以进入第一个子网络。这样,两个连续的分片可以在两个设备上并行运行。 +实际上朴素张量并行实现的执行时间比现有的单设备实现慢 `7%`。因此,可以得出结论,跨设备复制张量的开销约为 7%。仍有改进的空间,因为知道在整个执行过程中有一个设备是空闲的。一种选择是进一步将每个批次分成流水线的分片,这样当一个分片到达第二个子网络时,下一个分片可以进入第一个子网络。这样,两个连续的分片可以在两个设备上并行运行。 -朴素模型并行的优点在于实现相对简单,不需要复杂的通信和同步机制。然而,这种方法的缺点也很明显:如果模型的各部分计算量不均衡,可能会导致某些设备的利用率很低,从而影响整体训练效率。此外,对于依赖较强的模型结构,简单的模型并行也可能难以实现。 +朴素朴素张量并行的优点在于实现相对简单,不需要复杂的通信和同步机制。然而,这种方法的缺点也很明显:**如果模型的各部分计算量不均衡,可能会导致某些设备的利用率很低,从而影响整体训练效率。**此外,对于依赖较强的模型结构,简单的朴素张量并行也可能难以实现。 ## 张量并行 -张量并行(Tensor Parallelism,TP)是一种更细粒度的模型并行方法,它将单层内部的参数和计算任务拆分到不同的设备上执行,这种方法特别适合于具有大量参数的大规模模型。它最初是在 Megatron-LM 论文中提出的,它是一种高效的模型并行技术,可用于训练大型 Transformer 模型。通过张量并行,可以将矩阵乘法等计算操作的矩阵按行或按列切分,然后在不同设备上并行执行部分计算,最后通过集合通信操作合并结果。张量并行可以分为 MatMul 并行、Transformer 并行、Embedding 并行、Cross Entropy Loss 并行。序列并行(Sequence Parallel,SP)也是张量并行的一种变体,它在序列维度上对 nn.LayerNorm 或 RMSNorm 进行分割,以进一步节省训练过程中的激活内存。当模型变得越来越大时,激活内存就会成为瓶颈,因此在张量并行训练中,通常会将序列并行应用于 LayerNorm 或 RMSNorm 层。张量并行的主要挑战在于如何切分参数和计算任务,以保证计算的一致性和通信的高效性。例如,在进行矩阵乘法时,必须确保各设备上的部分结果在数学上是一致的。此外,通信开销也是一个重要考虑因素,需要在计算和通信之间找到平衡点,以达到最佳性能。 +张量并行(Tensor Parallelism,TP)是一种更细粒度的模型并行方法,它将单层内部的参数和计算任务拆分到不同的设备上执行,这种方法特别适合于具有大量参数的大规模模型。它最初是在 Megatron-LM 论文中提出的,它是一种高效的模型并行技术,可用于训练大型 Transformer 模型。 + +通过张量并行,可以将矩阵乘法等计算操作的矩阵按行或按列切分,然后在不同设备上并行执行部分计算,最后通过集合通信操作合并结果。张量并行可以分为 MatMul 并行、Transformer 并行、Embedding 并行、Cross Entropy Loss 并行。 + +其中序列并行(Sequence Parallel,SP)也是张量并行的一种变体,它在序列维度上对 nn.LayerNorm 或 RMSNorm 进行分割,以进一步节省训练过程中的激活内存。当模型变得越来越大时,激活内存就会成为瓶颈,因此在张量并行训练中,通常会将序列并行应用于 LayerNorm 或 RMSNorm 层。 + +张量并行的主要挑战在于如何切分参数和计算任务,以保证计算的一致性和通信的高效性。例如,在进行矩阵乘法时,必须确保各设备上的部分结果在数学上是一致的。此外,通信开销也是一个重要考虑因素,需要在计算和通信之间找到平衡点,以达到最佳性能。 ### MatMul 并行 -矩阵乘法(MatMul)是深度学习中最常见的操作之一。在张量并行中,可以将矩阵按列或者按行切分,然后在不同设备上并行执行部分计算。以矩阵乘法 $A \times B = C$ 为例,假设我们将矩阵 $B$ 按列切分成 $B_1$ 和 $B_2$,分别存储在设备 1 和设备 2 上。在这种情况下,设备 1 和设备 2 可以分别计算 $B_1 \times A$ 和 $B_2 \times A$,最终通过合并结果得到 $C$。 +矩阵乘法(MatMul)是深度学习中最常见的操作之一。在张量并行中,可以将矩阵按列或者按行切分,然后在不同设备上并行执行部分计算。以矩阵乘法 $A \times B = C$ 为例,假设将矩阵 $B$ 按列切分成 $B_1$ 和 $B_2$,分别存储在设备 1 和设备 2 上。在这种情况下,设备 1 和设备 2 可以分别计算 $B_1 \times A$ 和 $B_2 \times A$,最终通过合并结果得到 $C$。 ![模型并行](images/03ModelParallel08.png) @@ -65,7 +71,9 @@ class ModelParallelResNet50(ResNet): ![模型并行](images/03ModelParallel09.png) -对于多层感知机(MLP),我们对 A 采用列切割,对 B 采用行切割,在初始时使用函数 f 复制 X,结束时使用函数 g 通过 All-Reduce 汇总 Z,这样设计的原因是,尽量保证各设备上的计算相互独立,减少通信量。对 A 来说,需要做一次 GELU 计算,而 GELU 函数是非线形的,$GeLU(X + Y) \not = GeLU(X) + GeLU(Y)$,对 A 采用列切割,那每块设备就可以继续独立计算了。对于自注意力(Self-Attention)对三个参数矩阵 Q K V,按照列切割。对线性层 B,按照行切割,切割的方式和 MLP 层基本一致。 +对于多层感知机(MLP),对 A 采用列切割,对 B 采用行切割,在初始时使用函数 f 复制 X,结束时使用函数 g 通过 All-Reduce 汇总 Z,这样设计的原因是,尽量保证各设备上的计算相互独立,减少通信量。对 A 来说,需要做一次 GELU 计算,而 GELU 函数是非线形的,$GeLU(X + Y) \not = GeLU(X) + GeLU(Y)$,对 A 采用列切割,那每块设备就可以继续独立计算了。 + +对于自注意力(Self-Attention)对三个参数矩阵 Q K V,按照列切割。对线性层 B,按照行切割,切割的方式和 MLP 层基本一致。 需要注意的是在使用 dropout 时两个设备独立计算,第一个 dropout 在初始化时需要用不同的随机种子,这样才等价于对完整的 dropout 做初始化,然后再切割。最后一个 dropout 需要用相同的随机种子,保证一致性。 @@ -89,14 +97,19 @@ Cross Entropy Loss 并行用于在计算损失函数时节省内存和通信, Cross Entropy Loss 并行可以分为以下几步: -1. 数据拆分:将 logits (input) 按照 vocab 维度进行拆分,同时将不同部分分发到各设备,labels (target) 需要先进行 one hot 操作,然后 scatter 到各个设备上 -2. input(logits) 最大值同步:input(logits) 需要减去其最大值后求 softmax,All Reduce (Max) 操作保证了获取的是全局最大值,有效防止溢出。 -3. exp sum 与 softmax 计算:exp sum 即 softmax 计算中的分母部分, All Reduce (Max) 操作保证了获取的是全局的和。 -4. 计算 Loss: input (logits) 与 one_hot 相乘并求和,得到 label 位置值 im ,并进行 all_reduce (Sum) 全局同步,最后计算 log softmax 操作并加上负号,得到分布式交叉熵的损失值 loss。 +1. 数据拆分:将 logits (input) 按照 vocab 维度进行拆分,同时将不同部分分发到各设备,labels (target) 需要先进行 one hot 操作,然后 scatter 到各个设备上; + +2. input(logits) 最大值同步:input(logits) 需要减去其最大值后求 softmax,All Reduce (Max) 操作保证了获取的是全局最大值,有效防止溢出; + +3. exp sum 与 softmax 计算:exp sum 即 softmax 计算中的分母部分,All Reduce (Max) 操作保证了获取的是全局的和; + +4. 计算 Loss:input (logits) 与 one_hot 相乘并求和,得到 label 位置值 im ,并进行 all_reduce (Sum) 全局同步,最后计算 log softmax 操作并加上负号,得到分布式交叉熵的损失值 loss。 -### 使用 DeviceMesh 进行张量并行简单实现 +## DeviceMesh 实现 TP -我们可以通过 PyTorch DeviceMesh 进行多维度并行的实现。PyTorch 的张量并行应用程序接口(PyTorch Tensor Parallel APIs)提供了一套模块级原语,用于为模型的各个层配置分片功能。它利用 PyTorch DTensor 进行分片张量封装,DeviceMesh 抽象进行设备管理和分片。它们分为: +可以通过 PyTorch DeviceMesh 进行多维度并行的实现。 + +PyTorch 的张量并行应用程序接口(PyTorch Tensor Parallel APIs)提供了一套模块级原语,用于为模型的各个层配置分片功能。它利用 PyTorch DTensor 进行分片张量封装,DeviceMesh 抽象进行设备管理和分片。它们分为: - ColwiseParallel 和 RowwiseParallel:以列或行方式对 Linear 和 Embedding 层进行分片。 @@ -104,7 +117,9 @@ Cross Entropy Loss 并行可以分为以下几步: - PrepareModuleInput 和 PrepareModuleOutput:通过正确的通信操作配置模块输入输出分片布局。 -由于 Tensor Parallel 会将单个张量分片到一组设备上,因此我们需要先建立分布式环境(如 NCCL)。Tensor Parallelism 是一种单程序多数据(SPMD)分片算法,类似于 PyTorch DDP/FSDP,它通常在一台主机内工作。我们尝试初始化一个 8 GPU 的张量并行: +由于 Tensor Parallel 会将单个张量分片到一组设备上,因此需要先建立分布式环境。Tensor Parallelism 是一种单程序多数据(SPMD)分片算法,类似于 PyTorch DDP/FSDP,它通常在一台主机内工作。 + +下面尝试初始化一个 8 NPU 的张量并行: ```python # run this via torchrun: torchrun --standalone --nproc_per_node=8 ./tp_tutorial.py @@ -167,7 +182,17 @@ with loss_parallel(): loss.backward() ``` -这里我们使用 `init_device_mesh` 函数初始化设备网格 `tp_mesh`。这个网格指定了将使用 8 个 CUDA 设备进行并行计算。定义一个 `layer_tp_plan` 字典,指定了模型中各层的并行化策略。通过 `parallelize_module` 函数,我们可以并行化模型,指定 `tok_embeddings` 层进行行并行化,设置对输入进行复制,输出为分片布局(非本地输出);且对 `norm` 层进行序列并行化。在前向传播过程中,通过并行化的模型计算预测值 `pred`。在 `loss_parallel` 上下文中,进行张量并行交叉熵损失计算,并执行反向传播以计算梯度。 +这里使用 `init_device_mesh` 函数初始化设备网格 `tp_mesh`。这个网格指定了将使用 8 个 NPU 进行并行计算。 + +定义一个 `layer_tp_plan` 字典,指定了模型中各层的并行化策略。通过 `parallelize_module` 函数,可以并行化模型,指定 `tok_embeddings` 层进行行并行化,设置对输入进行复制,输出为分片布局(非本地输出);且对 `norm` 层进行序列并行化。 + +在前向传播过程中,通过并行化的模型计算预测值 `pred`。在 `loss_parallel` 上下文中,进行张量并行交叉熵损失计算,并执行反向传播以计算梯度。 + +## 小结与思考 + +- 张量并行(Tensor Parallelism, TP)是模型并行的一种细粒度形式,特别适用于参数众多的大规模模型,通过将矩阵乘法等操作的矩阵在不同设备上进行切分和并行计算,然后合并结果来实现。 + +- 使用 DeviceMesh 和 DTensor,PyTorch 提供了一套 API 来实现多维度并行,适用于不同层的模型并行化策略,如 ColwiseParallel、RowwiseParallel 和 SequenceParallel,以及张量并行交叉熵损失的计算。 ## 本节视频 diff --git a/05Framework/04Parallel/05PipelineParallel.md b/05Framework/04Parallel/05PipelineParallel.md index cc331cad..46d59ae3 100644 --- a/05Framework/04Parallel/05PipelineParallel.md +++ b/05Framework/04Parallel/05PipelineParallel.md @@ -1,85 +1,60 @@ -# 模型并行 +# 流水并行(DONE) -在大模型的训练中,单个设备往往无法满足计算和存储需求,因此需要借助分布式训练技术。其中,模型并行(Model Parallelism, MP)是一种重要的方法。模型并行的基本思想是将模型的计算任务拆分到不同的设备上执行,以提高训练效率和处理更大规模的模型。模型并行主要分为朴素的模型并行、张量并行和流水线并行。下面将分别详细介绍这几种模型并行的方法。 +在大模型的训练中,单个设备往往无法满足计算和存储需求,因此需要借助分布式训练技术。其中,模型并行(Model Parallelism, MP)是一种重要的方法。模型并行的基本思想是将模型的计算任务拆分到不同的设备上执行,以提高训练效率和处理更大规模的模型。模型并行主要分为朴素的模型并行、张量并行和流水线并行。下面将详细介绍模型并行中的流水并行。 -## 朴素模型并行 - -模型并行广泛应用于分布式训练技术。之前的部分已经解释了如何使用数据并行在多个设备上训练神经网络;这种方法一般来说将相同的模型复制到所有设备,每个设备消耗不同部分的输入数据。虽然这可以显著加速训练过程,但在某些情况下模型过大无法放入单个设备时,这种方法并不奏效。本节展示了如何通过使用**模型并行**解决这个问题。与数据并行相反,模型并行将单个模型拆分到不同的设备上,而不是在每个设备上复制整个模型(具体来说,假设一个模型 `m` 包含 6 层:使用数据并行时,每个设备将拥有这 6 层的副本,而使用模型并行在两个设备上时,每个设备只拥有 3 层)。 - -![模型并行](images/03ModelParallel01.png) - -我们来看一个简单的模型并行的例子: - -```python -from torchvision.models.resnet import ResNet, Bottleneck - -num_classes = 1000 - -class ModelParallelResNet50(ResNet): - def __init__(self, *args, **kwargs): - super(ModelParallelResNet50, self).__init__( - Bottleneck, [3, 4, 6, 3], num_classes=num_classes, *args, **kwargs) - - self.seq1 = nn.Sequential( - self.conv1, self.bn1, self.relu, - self.maxpool, self.layer1,self.layer2 - ).to('cuda:0') - - self.seq2 = nn.Sequential( - self.layer3, self.layer4, self.avgpool, - ).to('cuda:1') - self.fc.to('cuda:1') - - def forward(self, x): - x = self.seq2(self.seq1(x).to('cuda:1')) - return self.fc(x.view(x.size(0), -1)) -``` - -上面的代码展示了如何将 `torchvision.models.resnet50()` 分解到两个设备,将每个块放置在不同的设备上,并移动输入和中间输出以匹配层设备。思路是继承现有的 `ResNet` 模块,并在构造过程中将层分配到两个设备。然后,重写 `forward` 方法,通过移动中间输出连接两个子网络。 - -朴素的模型并行实现解决了模型过大无法放入单个设备的问题。然而,你可能已经注意到,如果模型能够放入单个设备,模型并行将比在单个设备上运行更慢。这是因为在任何时候,只有一个设备在工作,而另一个设备处于空闲状态。当中间输出需要从 `cuda:0` 复制到 `cuda:1` 时,性能会进一步恶化。 +## 流水线并行 -![模型并行](images/03ModelParallel02.png) +流水线并行(Pipeline Parallelism,PP)是一种将模型的不同层(layer)按顺序分配到不同设备上的方法。不同于朴素的模型并行,流水线并行通过将输入数据切分成多个微批次(micro-batch),使得每个设备可以在处理完当前批次后立即处理下一个批次,从而提高设备利用率。 -实际上模型并行实现的执行时间比现有的单设备实现慢 `7%`。因此,我们可以得出结论,跨设备复制张量的开销约为 7%。仍有改进的空间,因为我们知道在整个执行过程中有一个设备是空闲的。一种选择是进一步将每个批次分成流水线的分片,这样当一个分片到达第二个子网络时,下一个分片可以进入第一个子网络。这样,两个连续的分片可以在两个设备上并行运行。 +我们主要集中在 Gpipe 流水线并行和 PipeDream 流水线并行上(基于 F-then-B 策略与 1F1B 策略),不过还有很多优秀的流水线并行实现方式,例如:PipeDream-2BW、PipeDream-Flush、PipeDream-Megatron-LM 等,但他们一般都在大规模分布式深度学习训练框架中使用,如:Megatron-LM 和 Deepspeed,而不是深度学习框架,因此并不作为讨论范围。 -朴素模型并行的优点在于实现相对简单,不需要复杂的通信和同步机制。然而,这种方法的缺点也很明显:如果模型的各部分计算量不均衡,可能会导致某些设备的利用率很低,从而影响整体训练效率。此外,对于依赖较强的模型结构,简单的模型并行也可能难以实现。 +### Gpipe 流水线并行 -## 流水线并行 +Gpipe 是一种用于加速神经网络模型训练的流水线并行技术。它通过将模型的计算任务分配到多个设备上,从而提高训练效率。通过流水线并行技术,前向传播和反向传播可以重叠执行,从而提高模型并行的训练速度。 -流水线并行(Pipeline Parallelism,PP)是一种将模型的不同层(layer)按顺序分配到不同设备上的方法。不同于朴素的模型并行,流水线并行通过将输入数据切分成多个微批次(micro-batch),使得每个设备可以在处理完当前批次后立即处理下一个批次,从而提高设备利用率。我们主要集中在 Gpipe 流水线并行和 PipeDream 流水线并行上(基于 F-then-B 策略与 1F1B 策略),不过还有很多优秀的流水线并行实现方式,例如:PipeDream-2BW、PipeDream-Flush、PipeDream-Megatron-LM 等,但他们一般都在大规模分布式深度学习训练框架中使用,如:Megatron-LM 和 Deepspeed,而不是深度学习框架,因此并不作为我们的讨论范围。 +在 Gpipe 中,模型被分割成多个阶段,每个阶段在不同的设备上执行。输入数据也被切分成多个微批次,每个设备同时处理不同的微批次,从而提高并行效率。此外,Gpipe 也可以使用重计算策略,在前向和反向传播过程中节省内存。 -### Gpipe 流水线并行 +![模型并行](images/03ModelParallel03.png) -Gpipe 是一种用于加速神经网络模型训练的流水线并行技术。它通过将模型的计算任务分配到多个设备上,从而提高训练效率。通过流水线并行技术,前向传播和反向传播可以重叠执行,从而提高模型并行的训练速度。在 Gpipe 中,模型被分割成多个阶段,每个阶段在不同的设备上执行。输入数据也被切分成多个微批次,每个设备同时处理不同的微批次,从而提高并行效率。此外,Gpipe 也可以使用重计算策略,在前向和反向传播过程中节省内存。 +朴素模型并行设备视图(a)和时间视图(b):在前向传播阶段,计算任务 $F_0$、$F_1$、$F_2$ 和 $F_3$ 分别在 Device 0、Device 1、Device 2 和 Device 3 上执行。这些任务依次进行,将数据从一个设备传递到下一个设备,最终在 Device 3 上完成前向传播。 -![模型并行](images/03ModelParallel03.png) +在反向传播阶段,反向传播任务 $B_3$、$B_2$、$B_1$ 和 $B_0$ 依次在 Device 3、Device 2、Device 1 和 Device 0 上执行,梯度从最后一层传播回最初的层。所有设备完成反向传播后,梯度汇总并进行参数更新,我们将其称为 F-then-B 策略。这一过程确保了梯度能够正确传递并用于更新模型参数。 -朴素模型并行设备视图(a)和时间视图(b):在前向传播阶段,计算任务 $F_0$、$F_1$、$F_2$ 和 $F_3$ 分别在 Device 0、Device 1、Device 2 和 Device 3 上执行。这些任务依次进行,将数据从一个设备传递到下一个设备,最终在 Device 3 上完成前向传播。在反向传播阶段,反向传播任务 $B_3$、$B_2$、$B_1$ 和 $B_0$ 依次在 Device 3、Device 2、Device 1 和 Device 0 上执行,梯度从最后一层传播回最初的层。所有设备完成反向传播后,梯度汇总并进行参数更新,我们将其称为 F-then-B 策略。这一过程确保了梯度能够正确传递并用于更新模型参数。 +Gpipe 流水线并行(c):由于设备间的依赖性,某些设备在等待其他设备完成任务时会产生空闲时间。在 Gpipe 流水线并行中,将前向传播和反向传播任务分成更细的粒度,如 $F_{i,j}$ 和 $B_{i,j}$(其中 $i$ 表示设备编号,$j$ 表示分段编号),我们称为微批量(micro-batch)。 -Gpipe 流水线并行(c):由于设备间的依赖性,某些设备在等待其他设备完成任务时会产生空闲时间。在 Gpipe 流水线并行中,将前向传播和反向传播任务分成更细的粒度,如 $F_{i,j}$ 和 $B_{i,j}$(其中 $i$ 表示设备编号,$j$ 表示分段编号),我们称为微批量(micro-batch)。通过这种方法,可以更好地平衡各设备的负载,减少空闲时间。然而,由于任务分段的传递顺序,某些设备在等待前一任务完成时会有空闲时间。这种空闲时间被称为“气泡”。通过优化分段和任务分配,可以最小化气泡的影响,提高整体效率。 +通过这种方法,可以更好地平衡各设备的负载,减少空闲时间。然而,由于任务分段的传递顺序,某些设备在等待前一任务完成时会有空闲时间。这种空闲时间被称为“气泡”。通过优化分段和任务分配,可以最小化气泡的影响,提高整体效率。 Gpipe 流水线并行提供了多项显著优势。它可以高效地利用计算资源。通过将模型分段并分配到多个设备上,充分利用各设备的计算能力,从而提高整体计算效率。其次可以减少内存需求。由于模型被分段,每个设备只需要存储当前分段的参数和激活值。这显著降低了每个设备的内存需求,使得可以在内存较小的设备上训练大模型。在启动**激活检查点**后,通过在流水线反向传播时重新计算激活,可以进一步压缩内存需求。 ### PipeDream 流水线并行 -与 Gpipe 流水线并行一样,PipeDream 流水线并行也是一种用于加速神经网络模型训练的流水线并行技术。它通过将模型的计算任务分配到多个机器上,交错执行前向传播和后向传播,从而提高训练效率。与 Gpipe 流水线并行不同的是,PipeDream 流水线并行在做完一次 micro-batch 的前向传播之后,就立即进行 micro-batch 的后向传播,然后释放资源,那么就可以让其他 stage 尽可能早的开始计算,我们将其称为 1F1B 策略,这也是微软 Deepspeed 框架使用的流水线并行策略。交错执行的策略,使前向传播和后向传播任务交替进行,最大化地利用了每个设备的计算资源,减少了空闲时间,提高了整体效率。然而,这也增加了任务调度的复杂性,需要更复杂的管理机制来协调设备之间的数据传递和任务分配。同时异步的流水线也会带来收敛的困难。 +与 Gpipe 流水线并行一样,PipeDream 流水线并行也是一种用于加速神经网络模型训练的流水线并行技术。它通过将模型的计算任务分配到多个机器上,交错执行前向传播和后向传播,从而提高训练效率。 + +与 Gpipe 流水线并行不同的是,PipeDream 流水线并行在做完一次 micro-batch 的前向传播之后,就立即进行 micro-batch 的后向传播,然后释放资源,那么就可以让其他 stage 尽可能早的开始计算,我们将其称为 1F1B 策略,这也是微软 Deepspeed 框架使用的流水线并行策略。交错执行的策略,使前向传播和后向传播任务交替进行,最大化地利用了每个设备的计算资源,减少了空闲时间,提高了整体效率。 + +然而,这也增加了任务调度的复杂性,需要更复杂的管理机制来协调设备之间的数据传递和任务分配。同时异步的流水线也会带来收敛的困难。 ![模型并行](images/03ModelParallel04.png) -PipeDream 流水线并行是异步的,每个 Worker 在执行前向传播和后向传播时,都会使用对应的权重版本。例如,Worker 1 在执行任务 1 时使用权重版本 $ W_1^{(1)} $,在执行任务 5 时使用权重版本 $ W_1^{(2)} $。在前向传播和后向传播完成后,权重会进行异步更新。例如,Worker 1 在执行任务 5 时,会将更新后的权重版本 $ W_1^{(2)} $ 传递给 Worker 2,Worker 2 再根据新的权重版本进行计算。 +PipeDream 流水线并行是异步的,每个 Worker 在执行前向传播和后向传播时,都会使用对应的权重版本。例如,Worker 1 在执行任务 1 时使用权重版本 $ W_1^{(1)} $,在执行任务 5 时使用权重版本 $ W_1^{(2)} $。 + +在前向传播和后向传播完成后,权重会进行异步更新。例如,Worker 1 在执行任务 5 时,会将更新后的权重版本 $ W_1^{(2)} $ 传递给 Worker 2,Worker 2 再根据新的权重版本进行计算。 ![模型并行](images/03ModelParallel05.png) -此外,PipeDream 还扩展了 1F1B,对于使用数据并行的 stage,采用轮询(round-robin)的调度模式将任务分配在同一个 stage 的各个设备上,保证了一个小批次的数据的前向传播计算和后向传播计算发生在同一台机器上,这就是 1F1B-RR(one-forward-noe-backward-round-robin) +此外,PipeDream 还扩展了 1F1B,对于使用数据并行的 stage,采用轮询(round-robin)的调度模式将任务分配在同一个 stage 的各个设备上,保证了一个小批次的数据的前向传播计算和后向传播计算发生在同一台机器上,这就是 1F1B-RR(one-forward-noe-backward-round-robin)。 流水线并行的主要挑战在于如何处理设备之间的数据依赖和通信延迟。在实际应用中,通常需要结合数据并行、张量并行和流水线并行等多种方法,以最大化训练效率和模型规模。例如,可以在同一设备内使用张量并行,在不同设备间使用数据并行和流水线并行,从而充分利用硬件资源,提高整体训练性能。 -### Gpipe 流水线简单并行实现 +## Gpipe 流水并行实现 + +### 朴素实现 -为了实现 Gpipe 的流水线并行,需要注意以下几点。首先是模型分段。将大模型分成多个子模型,每个子模型对应一个设备。子模型之间通过通信接口进行数据传递,以确保数据能够正确传输和处理。其次是任务调度。需要一个高效的调度机制来管理各设备上的任务执行顺序,确保前向传播和反向传播的顺利进行。通过有效的任务调度,可以最大化地利用计算资源,减少设备的空闲时间。在训练过程中我们将数据中每 120 个图像组成的批次进一步划分为 20 图像分片,由于 PyTorch 异步启动 CUDA 操作,我们的实现不需要生成多个线程来实现并发。 +为了实现 Gpipe 的流水线并行,需要注意以下几点。首先是模型分段。将大模型分成多个子模型,每个子模型对应一个设备。子模型之间通过通信接口进行数据传递,以确保数据能够正确传输和处理。其次是任务调度。需要一个高效的调度机制来管理各设备上的任务执行顺序,确保前向传播和反向传播的顺利进行。 + +通过有效的任务调度,可以最大化地利用计算资源,减少设备的空闲时间。在训练过程中我们将数据中每 120 个图像组成的批次进一步划分为 20 图像分片,由于 PyTorch 异步启动 CUDA 操作,实现不需要生成多个线程来实现并发。 ```python class PipelineParallelResNet50(ModelParallelResNet50): @@ -113,7 +88,7 @@ class PipelineParallelResNet50(ModelParallelResNet50): 将输入流水线到模型并行 ResNet50 将训练过程加速约 `49%`。这仍然远低于理想的 100% 加速。不过我们仍有进一步加速训练过程的机会。例如,所有在 `cuda:0` 上的操作都放置在其默认流上。这意味着下一个分片的计算不能与 `prev` 分片的复制操作重叠。然而,由于 `prev` 和下一个分片是不同的张量,将一个的计算与另一个的复制操作重叠没有问题。 -### 使用 RPC 进行 Gpipe 流水线简单并行实现 +### RPC 实现 我们也可以使用 RPC 框架,实现流水线并行。分布式 RPC 框架提供了一套机制,用于多机模型训练,通过一系列原语实现远程通信,并提供高级 API 以自动处理跨多机的模型差异化。这个框架简化了在分布式环境中运行函数、引用远程对象以及在 RPC 边界间进行反向传播和参数更新的过程。分布式 RPC 框架主要包含以下四类 API: @@ -215,7 +190,7 @@ if __name__ == "__main__": 接下来定义所有进程的目标函数。主进程运行主逻辑,Worker 进程被动等待主进程命令。 -### 使用 Pipe 进行模型并行 +### Pipe 实现 同样,我们可以使用 Pytorch 提供的 `torch.distributed.pipeline.sync` 的 `Pipe` 类来简单快捷的实现,Pipe 依赖于 RPC 来管理不同设备之间的通信。我们来看一个简单的示例:首先初始化 RPC(Remote Procedure Call)框架。初始化 RPC 框架时,我们设置了主节点的地址和端口,并调用 `torch.distributed.rpc.init_rpc` 函数来启动 RPC。 @@ -239,6 +214,14 @@ output_rref = model(input) 最后,我们创建一个随机输入张量,并将其分配到 GPU 0 上。调用 `model` 的前向函数时,Pipe 会自动处理不同设备之间的通信,并将输入数据通过模型的各个层进行计算。输出结果是一个 `RRef`(Remote Reference),指向计算结果所在的设备。 +## 小结与思考 + +- 流水线并行(Pipeline Parallelism, PP)通过将模型的不同层顺序分配到不同设备上,并切分输入数据为多个微批次,以提高设备利用率和训练效率。 + +- Gpipe 和 PipeDream 是流水线并行的两种实现方式,Gpipe 通过分段和任务调度最小化设备空闲时间,而 PipeDream 采用 1F1B 策略异步更新权重,两者都旨在提升模型训练速度。 + +- PyTorch 提供了使用 RPC 和 Pipe 的流水线并行简单实现方法,通过分布式环境和自动化的通信处理,简化了模型并行化的实现过程,使得大模型训练更加高效。 + ## 本节视频 diff --git a/05Framework/04Parallel/06HybridParallel.md b/05Framework/04Parallel/06HybridParallel.md index 1e9e9d74..9260f73d 100644 --- a/05Framework/04Parallel/06HybridParallel.md +++ b/05Framework/04Parallel/06HybridParallel.md @@ -1,6 +1,6 @@ -# 混合并行 +# 混合并行(DONE) 混合并行(HybridParallel)是一种用于分布式计算的高级策略,它结合了数据并行和模型并行的优势,以更高效地利用计算资源,解决深度学习中的大模型训练问题。混合并行不仅能提高计算效率,还能在有限的硬件资源下处理更大的模型和数据集。在深度学习中,数据并行和模型并行各自有其适用的场景和局限性。数据并行适用于训练样本较多而模型较小的情况,通过将数据集分割成多个子集并在不同的设备上同时训练来提高训练速度。而模型并行则适用于模型较大无法单独放入一个设备内存的情况,通过将模型切分成不同的部分分别在多个设备上进行计算。混合并行通过将这两种并行方式结合,加速计算和处理超大模型,从而在各种硬件条件下实现高效的神经网络模型训练。现主流的混合并行为 3D 混合并行,但由于他们一般都在大规模分布式深度学习训练框架中使用,如:Deepspeed 和 Colossal AI,而不是深度学习框架,因此只进行简单讨论。 @@ -139,6 +139,14 @@ model_2d = FSDP(model_tp, device_mesh=dp_mesh, use_orig_params=True, ...) 定义模型,并创建一个张量并行计划(Tensor Parallel Plan),该计划指示模型的哪些部分需要并行化,具体配置可以参考上一节使用 Device Mesh 进行张量并行的内容。通过 `parallelize_module` 函数,可以在 `tp_mesh` 上应用张量并行,从而在主机内实现模型的并行化。在 `dp_mesh` 上应用完全分片数据并行(Fully Sharded Data Parallel, FSDP)。FSDP 通过自动分片和重组模型参数,进一步优化跨主机的模型训练。通过将 `model_tp` 传递给 `FSDP`,就可以进行简单的 DP+PP 并行了。 +## 小结与思考 + +- 混合并行(HybridParallel)结合了数据并行和模型并行的优势,通过在不同维度上分割模型和数据,提高了计算效率和内存利用率,适用于训练大规模神经网络模型。 + +- 3D 混合并行是混合并行的一种高级策略,它整合了数据并行(DP)、模型并行(MP)和流水线并行(PP),在多个 GPU 上优化资源利用和训练效率,尤其适合超大规模模型训练。 + +- 使用 Torch RPC 和 Torch Device Mesh 可以分别实现 DP+PP 的简单实现,通过分布式环境和设备网格管理,简化了混合并行的配置和执行过程,提高了模型训练的灵活性和扩展性。 + ## 本节视频 diff --git "a/06Foundation/04Network/02_AI\351\233\206\347\276\244\351\200\232\344\277\241\350\275\257\347\241\254\344\273\266.md" "b/06Foundation/04Network/02_AI\351\233\206\347\276\244\351\200\232\344\277\241\350\275\257\347\241\254\344\273\266.md" index 520f1a4e..473bad74 100644 --- "a/06Foundation/04Network/02_AI\351\233\206\347\276\244\351\200\232\344\277\241\350\275\257\347\241\254\344\273\266.md" +++ "b/06Foundation/04Network/02_AI\351\233\206\347\276\244\351\200\232\344\277\241\350\275\257\347\241\254\344\273\266.md" @@ -116,7 +116,7 @@ RDMA 是为了解决网络传输中数据处理延迟而产生的一种远端内 **MPI(Message Passing Interface)**: 定义了多个原语的消息传递接口,这一接口主要还用于多进程的通信。MPI 系统通信方式是建立在点对点通信之上。而集合通信是建立在端到端通信的基础上在一组进程内的通信原语。对于目前大型的 AI 集群,一般利用 MPI 进行跨机器多进程通信。成熟的软件库有 Open-MPI、Intel-MPI 等等 -**XCCL(X Collective Communication Library)**泛指一类用于集合通信的软件库,包含 NCCL、HCCL、 BCCL 和 Byte CCL 等,它们的功能都是类似,一般都是基于开源 NCCL 进行优化或者改造,使得适用于自研的 AI 芯片架构,实现设备之间的集合通信。这里主要介绍下 NCCL 库。NVIDIA 的 NCCL 提供编程抽象,通过高级拓扑检测、通用路径搜索和针对 NVIDIA 架构优化的算法,针对每个平台和拓扑进行高度调整。 NCCL API 从 CPU 启动,GPU 执行,在 GPU 内存之间移动或交换数据。最后利用 NVLink 聚合多个高速 NIC 的带宽,实现多设备多机器多集群之间的集合通信。 +**XCCL(X Collective Communication Library)**泛指一类用于集合通信的软件库,包含 NCCL、HCCL、 BCCL 和 Byte CCL 等,它们的功能都是类似,一般都是基于开源 NCCL 进行优化或者改造,使得适用于自研的 AI 芯片架构,实现设备之间的集合通信。这里主要介绍下 NCCL 库。NVIDIA 的 NCCL 提供编程抽象,通过高级拓扑检测、通用路径搜索和针对 NVIDIA 架构优化的算法,针对每个平台和拓扑进行高度调整。NCCL API 从 CPU 启动,GPU 执行,在 GPU 内存之间移动或交换数据。最后利用 NVLink 聚合多个高速 NIC 的带宽,实现多设备多机器多集群之间的集合通信。 ![](images/13Cluster_network.png) diff --git a/06Foundation/07Parallel/02.single_device.md b/06Foundation/07Parallel/02.single_device.md index 2ae5da26..a3f9dcef 100644 --- a/06Foundation/07Parallel/02.single_device.md +++ b/06Foundation/07Parallel/02.single_device.md @@ -4,7 +4,7 @@ ## 混精度训练 -在当今大模型训练的背景下,混合精度训练已然成为一种备受推崇的普遍做法 [[1]](#ref1)。通过采用混合精度训练,我们能够将训练速度显著提升数倍,而又不会对模型的整体性能产生重大影响。在数据科学领域,精度一直是评判的重要考量因素——在传统的科学计算领域,人们通常追求较高的精度,如 FP128 或 FP64 等。然而,在深度学习中,我们所面临的实际上是一个高维函数拟合(或近似)的优化问题,因此并不需要过于精确的数值表示,且使用低精度将会带来显著的计算速度提升:在 NVIDIA A00 SXM 与 NVIDIA H00 SXM 中, FP16 浮点运算能力的理论峰值是 FP32 的近 30 倍。 +在当今大模型训练的背景下,混合精度训练已然成为一种备受推崇的普遍做法 [[1]](#ref1)。通过采用混合精度训练,我们能够将训练速度显著提升数倍,而又不会对模型的整体性能产生重大影响。在数据科学领域,精度一直是评判的重要考量因素——在传统的科学计算领域,人们通常追求较高的精度,如 FP128 或 FP64 等。然而,在深度学习中,我们所面临的实际上是一个高维函数拟合(或近似)的优化问题,因此并不需要过于精确的数值表示,且使用低精度将会带来显著的计算速度提升:在 NVIDIA A00 SXM 与 NVIDIA H00 SXM 中,FP16 浮点运算能力的理论峰值是 FP32 的近 30 倍。
@@ -41,11 +41,11 @@ torch.backends.cuda.matmul.allow_tf32 = True torch.backends.cudnn.allow_tf32 = True ``` -TF32 采用与FP16相同的 10 位尾数,这满足了人工智能工作负载的精度要求,并且使用了与 FP32 相同的 8 位指数,因此具有相同的数值范围。从技术上讲,它可以视为一种 19 位格式,也可以视为扩展精度的 BF16。TF32 的优势在于其格式与 FP32 相同。当使用 TF32 进行计算时,输入 FP32 操作数的尾数从 23 位舍入到 10 位,然后进行精确乘法运算,最后以正常的 FP32 格式进行累加。TF32 Tensor Core 在 FP32 输入上运行并生成 FP32 结果,而不需要修改代码,而非矩阵操作则继续使用 FP32。相比之下,FP16 和 BF16 等格式需要更多工作,因为它们涉及不同的位布局。尽管如此,也值得使用这些格式,因为它们可以减少内存带宽,从而提升执行速度。 +TF32 采用与 FP16 相同的 10 位尾数,这满足了人工智能工作负载的精度要求,并且使用了与 FP32 相同的 8 位指数,因此具有相同的数值范围。从技术上讲,它可以视为一种 19 位格式,也可以视为扩展精度的 BF16。TF32 的优势在于其格式与 FP32 相同。当使用 TF32 进行计算时,输入 FP32 操作数的尾数从 23 位舍入到 10 位,然后进行精确乘法运算,最后以正常的 FP32 格式进行累加。TF32 Tensor Core 在 FP32 输入上运行并生成 FP32 结果,而不需要修改代码,而非矩阵操作则继续使用 FP32。相比之下,FP16 和 BF16 等格式需要更多工作,因为它们涉及不同的位布局。尽管如此,也值得使用这些格式,因为它们可以减少内存带宽,从而提升执行速度。 ### 混精度训练 -在深度学习中,使用FP16训练有时会出现下溢出的问题:FP16 的有效的动态范围约为 ${5.96e}^{-8} \sim 65504$,在训练后期,例如激活函数的梯度会非常小,甚至在梯度乘以学习率后,值会更加小。由于 FP16 的精度范围有限,过小的梯度可能导致更新无效——这个时候就需要我们使用混精度训练。混精度训练可以分为两个部分:**半精度** 和 **权重备份**,这里我们拿 FP16 和 FP32 来举例。在训练开始时,我们准备两套模型状态,其中一套为 FP32 类型(优化器状态和模型参数),另一套为 FP16 类型(模型参数),在前向传播、反向传播时,我们都使用 FP16 类型的模型参数进行计算;而在参数更新时,我们将梯度成与学习率 $\eta$ 相乘,更新到 FP32 类型的模型状态上,在新一轮的训练中,我们再次将 FP32 类型的模型拷贝为 FP16 类型的模型。这个过程就是**混精度训练**。由于在计算密集的前向传播、反向传播中,我们使用了半精度进行计算,与单精度相比,训练的速度会大幅度提升。另外,由于激活值在训练过程中占用内存的很大部分,使用 FP16 储存激活值在大批量训练时也会节省内存。同时,在分布式环境下使用 FP16 梯度通信量也会降低。 +在深度学习中,使用 FP16 训练有时会出现下溢出的问题:FP16 的有效的动态范围约为 ${5.96e}^{-8} \sim 65504$,在训练后期,例如激活函数的梯度会非常小,甚至在梯度乘以学习率后,值会更加小。由于 FP16 的精度范围有限,过小的梯度可能导致更新无效——这个时候就需要我们使用混精度训练。混精度训练可以分为两个部分:**半精度** 和 **权重备份**,这里我们拿 FP16 和 FP32 来举例。在训练开始时,我们准备两套模型状态,其中一套为 FP32 类型(优化器状态和模型参数),另一套为 FP16 类型(模型参数),在前向传播、反向传播时,我们都使用 FP16 类型的模型参数进行计算;而在参数更新时,我们将梯度成与学习率 $\eta$ 相乘,更新到 FP32 类型的模型状态上,在新一轮的训练中,我们再次将 FP32 类型的模型拷贝为 FP16 类型的模型。这个过程就是**混精度训练**。由于在计算密集的前向传播、反向传播中,我们使用了半精度进行计算,与单精度相比,训练的速度会大幅度提升。另外,由于激活值在训练过程中占用内存的很大部分,使用 FP16 储存激活值在大批量训练时也会节省内存。同时,在分布式环境下使用 FP16 梯度通信量也会降低。 ![混精度训练](images/02.single_device03.png) @@ -53,7 +53,7 @@ TF32 采用与FP16相同的 10 位尾数,这满足了人工智能工作负载 ### 损失缩放 (Loss Scale) -解决 FP16 下溢问题的另一个方法是损失缩放(Loss Scale)。刚才提到,训练到了后期,梯度(特别是激活函数平滑段的梯度)会特别小,FP16表示容易产生下溢现象。为了解决梯度过小的问题,我们需要对损失进行缩放,由于链式法则的存在,损失的缩放也会作用在梯度上。缩放过后的梯度,就会平移到FP16有效的展示范围内。不过缩放并非对于所有网络而言都是必须的,而缩放的取值为也会特别大,一般在 8 - 32k 之间。在 Pytorch 中,可以通过这样的方式实现自动损失缩放: +解决 FP16 下溢问题的另一个方法是损失缩放(Loss Scale)。刚才提到,训练到了后期,梯度(特别是激活函数平滑段的梯度)会特别小,FP16 表示容易产生下溢现象。为了解决梯度过小的问题,我们需要对损失进行缩放,由于链式法则的存在,损失的缩放也会作用在梯度上。缩放过后的梯度,就会平移到 FP16 有效的展示范围内。不过缩放并非对于所有网络而言都是必须的,而缩放的取值为也会特别大,一般在 8 - 32k 之间。在 Pytorch 中,可以通过这样的方式实现自动损失缩放: ```python from torch.cuda.amp import GradScaler, autocast @@ -74,7 +74,7 @@ scaler.update() ## 内存消耗估算 -在神经网络模型的训练中,合理估算和管理内存消耗是非常重要的。我们的内存存储主要分为两大块:**模型状态(Model States)** 和**剩余状态(Residual States)**。 +在神经网络模型的训练中,合理估算和管理内存消耗是非常重要的。内存存储主要分为两大块:**模型状态(Model States)** 和**剩余状态(Residual States)**。 **模型状态**指和模型本身相关的,必须存储的内容,具体包括: @@ -109,9 +109,9 @@ scaler.update() 而由于剩余状态和具体模型架构有关,因此需要具体分析。 -接下来我们基于 Transformer 的架构进行具体分析,因为所有参数超过 10 亿的 SOTA 模型都遵循这一架构。我们的分析假设使用 Adam 优化器进行混合精度训练,因为此配方是训练基于 Transformer 的模型的事实标准。 +接下来我们基于 Transformer 的架构进行具体分析,因为所有参数超过 10 亿的 SOTA 模型都遵循这一架构。分析假设使用 Adam 优化器进行混合精度训练,因为此配方是训练基于 Transformer 的模型的事实标准。 -**模型状态**:模型状态由优化器状态、梯度和参数组成。基于 Transformer 的模型中的参数总数主要取决于隐藏维度 ($hd$) 和 Transformer 层数($nl$)。 Transformer 块中的几乎所有参数都来自每个块内的四个线性层,其大小分别为:($hd$, $3hd$)、($hd$, $hd$)、($hd$, $4hd$) 和($4hd$, $hd$)。因此,基于 Transformer 的模型中的总参数可以近似为: +**模型状态**:模型状态由优化器状态、梯度和参数组成。基于 Transformer 的模型中的参数总数主要取决于隐藏维度 ($hd$)和 Transformer 层数($nl$)。Transformer 块中的几乎所有参数都来自每个块内的四个线性层,其大小分别为:($hd$, $3hd$)、($hd$, $hd$)、($hd$, $4hd$)和($4hd$, $hd$)。因此,基于 Transformer 的模型中的总参数可以近似为: $$ 12 × nl × hd^2 @@ -154,7 +154,7 @@ $$ 1) $\frac{\partial L}{\partial y}$ 可以通过后续层传递回来 2) $\frac{\partial y}{\partial z}$ 则是该层激活函数 $f$ 对于输出 $y$ 的导数,可由输出 $y$ 和函数 $f$ 确定,如 $ReLU$ 函数 $\frac{\partial y}{\partial z} = 1(z>0)$ -3) $\frac{\partial z}{\partial w} = a$, 即输入激活值 $a$ +3) $\frac{\partial z}{\partial w} = a$,即输入激活值 $a$ 可以看到,计算权重梯度的关键之一就是利用了前向激活值 $a$ diff --git a/06Foundation/07Parallel/03.data_parallel.md b/06Foundation/07Parallel/03.data_parallel.md index 0b94bb5b..e5b2cc1a 100644 --- a/06Foundation/07Parallel/03.data_parallel.md +++ b/06Foundation/07Parallel/03.data_parallel.md @@ -2,7 +2,7 @@ # 数据并行 -**数据并行** [[1]](#ref1) 是一种将原本在单一设备上进行的数据训练过程,扩展到多个设备并行计算,以达到理想情况下设备数量倍率加速效果的并行算法。数据并行的方式和扩展有很多种,例如:数据并行(DP)、分布式数据并行(DDP)、完全分片的数据并行(ZeRO) [[2]](#ref2)、异步的数据并行等。 +**数据并行** [[1]](#ref1) 是一种将原本在单一设备上进行的数据训练过程,扩展到多个设备并行计算,以达到理想情况下设备数量倍率加速效果的并行算法。数据并行的方式和扩展有很多种,例如:数据并行(DP)、分布式数据并行(DDP)、完全分片的数据并行(ZeRO)[[2]](#ref2)、异步的数据并行等。 本重点介绍其中最简单的数据并行,分为**数据并行(DP)**和**分布式数据并行(DDP)**两种模式。相比数据并行,尽管分布式数据并行实现更为复杂,但在选择数据并行算法时,我们仍应优先考虑分布式数据并行。简单来说,它更高效、更易优化,且可以很好地与其他并行算法相结合。更多的数据并行拓展内容如**完全分片的数据并行(ZeRO)** 将在后文中进行详细介绍。 diff --git a/06Foundation/07Parallel/05.ZeRO.md b/06Foundation/07Parallel/05.ZeRO.md index f6f821ff..4ae57dc9 100644 --- a/06Foundation/07Parallel/05.ZeRO.md +++ b/06Foundation/07Parallel/05.ZeRO.md @@ -26,7 +26,7 @@ ZeRO-DP 有三个主要优化阶段,分别对应于优化器状态、梯度和 除了优化模型状态(优化器状态、梯度和参数)的内存利用率,ZeRO 还专门针对剩余状态(如激活数据、临时缓冲区和内存碎片等)进行了优化,以进一步减少内存开销。ZeRO-R 对剩余状态进行了切分和优化,主要包括以下几个策略: -1) **分区激活检查点**(Partitioned Activation Checkpointing,$P_{a}$):解决了模型并行时激活内存冗余的问题。在模型并行中,每个设备需要保存完整的输入激活数据才能计算自己分到的模型部分。ZeRO-R 将激活检查点按模型并行度 $N_m$ 进行分区,每个设备只需存储 $\frac{1}{N_m}$ 的激活检查点。在需要时通过 All-gather 操作重构出完整激活数据,从而按 $N_m$ 的比例减少激活内存。 在极端情况下,当模型规模很大时,ZeRO-R 甚至可以将分区后的激活检查点卸载到 CPU 内存($P_{a+cpu}$),再次降低设备内存占用,代价是额外的主机-设备通信开销。该策略在大模型训练时会自动开启,以保证足够的设备内存用于计算。 +1) **分区激活检查点**(Partitioned Activation Checkpointing,$P_{a}$):解决了模型并行时激活内存冗余的问题。在模型并行中,每个设备需要保存完整的输入激活数据才能计算自己分到的模型部分。ZeRO-R 将激活检查点按模型并行度 $N_m$ 进行分区,每个设备只需存储 $\frac{1}{N_m}$ 的激活检查点。在需要时通过 All-gather 操作重构出完整激活数据,从而按 $N_m$ 的比例减少激活内存。在极端情况下,当模型规模很大时,ZeRO-R 甚至可以将分区后的激活检查点卸载到 CPU 内存($P_{a+cpu}$),再次降低设备内存占用,代价是额外的主机-设备通信开销。该策略在大模型训练时会自动开启,以保证足够的设备内存用于计算。 2) **恒定大小的缓冲区**(Constant Size Buffer,$C_{b}$):一些操作如 All-reduce 需要将张量拼成连续的临时缓冲区,使用恒定大小的缓冲区来避免临时缓冲区随着模型大小的增加而爆炸,同时使它们足够大以保持效率。 @@ -60,7 +60,7 @@ ZeRO-R 的通信开销取决于模型大小、检查点策略和模型并行(M 在使用激活检查点的 Megatron-LM 中,每个 Transformer 块在前向传播中执行两次大小为 $batch × seq × length × hidden\_dim$ 的 All-reduce 操作,然后在反向传播中再执行两次。在使用激活检查点的 ZeRO-R 中,每个前向重计算激活之前需要执行一个额外的 All-gather 操作。通常情况下,对于每个 Transformer 块的输入激活进行检查点,因此每个 Transformer 块需要一个 All-gather 操作。因此,ZeRO-R $P_{a}$ 的通信开销为 $seq\_length × hidden\_dim$,仅增加不到 10%。 -当 MP 与 DP 一起使用时, ZeRO-R $P_{a}$ 可以将数据并行通信量减少一个数量级,而模型并行通信量只增加 10%,并且当数据并行通信是性能瓶颈时,可以显着提高效率。通过模型并行可以减少数据并行的内存消耗,从而可以成比例地增加批处理大小。对于大模型,MP 可以增加到 16(DGX-2 节点上的 GPU 数量),从而可以将批处理大小增加多达 16 倍。数据并行训练的通信量与批处理大小成反比,由于 $P_{a}$ 导致批处理大小增加一个数量级,可能会导致数据并行通信量减少一个数量级。 +当 MP 与 DP 一起使用时,ZeRO-R $P_{a}$ 可以将数据并行通信量减少一个数量级,而模型并行通信量只增加 10%,并且当数据并行通信是性能瓶颈时,可以显着提高效率。通过模型并行可以减少数据并行的内存消耗,从而可以成比例地增加批处理大小。对于大模型,MP 可以增加到 16(DGX-2 节点上的 GPU 数量),从而可以将批处理大小增加多达 16 倍。数据并行训练的通信量与批处理大小成反比,由于 $P_{a}$ 导致批处理大小增加一个数量级,可能会导致数据并行通信量减少一个数量级。 如果应用 $P_{a+cpu}$,则分区激活检查点会被卸载到 CPU,将激活内存需求减少到接近零,但与 $P_{a}$ 相比,往返 CPU 内存的数据移动增加了 2 倍。如果 DP 通信量是主要瓶颈,由于批处理大小较小,$P_{a+cpu}$ 也可以通过增加批处理大小来提高效率,只要 CPU 数据传输开销小于 DP 通信量开销。 @@ -74,7 +74,7 @@ $$ ait = \frac{total\_computation}{total\_data\_movement} $$ -因此我们的效率可以大致估算为: +因此效率可以大致估算为: $$ \begin{aligned} @@ -117,11 +117,11 @@ $$ 24 × hd × ci $$ -模型状态和激活检查点对带宽的要求大不相同。前者只取决于**批量大小和序列长度**,而后者只取决于**激活检查点的频率和模型的隐藏维度大小**。在实际中,参数和梯度的带宽超过 70 GB/s,即使是最小的批处理量,也能实现超过 50% 的效率。在这种带宽下,数据移动理论上可以与计算完全重叠,从而实现 100% 的效率。与参数和梯度相比,优化器状态需要高出近 4 倍的带宽才能达到 50% 的效率。此外,优化器状态在前向和后向传播结束时更新,不能与计算重叠。因此,它们需要更大的带宽来保持整个 DL 工作负载的效率。例如,在每个 GPU 的批处理量为 2 的情况下,要达到 90% 的效率,需要近 1.5 TB/s 的有效带宽,甚至超过了 GPU 内存带宽。启用激活检查点后,即使隐藏大小为 2K,2 GB/s 的微薄带宽也能维持 50% 以上的效率。 当隐藏大小超过 8K 时,带宽需求降至 1 GB/s 以下。 +模型状态和激活检查点对带宽的要求大不相同。前者只取决于**批量大小和序列长度**,而后者只取决于**激活检查点的频率和模型的隐藏维度大小**。在实际中,参数和梯度的带宽超过 70 GB/s,即使是最小的批处理量,也能实现超过 50% 的效率。在这种带宽下,数据移动理论上可以与计算完全重叠,从而实现 100% 的效率。与参数和梯度相比,优化器状态需要高出近 4 倍的带宽才能达到 50% 的效率。此外,优化器状态在前向和后向传播结束时更新,不能与计算重叠。因此,它们需要更大的带宽来保持整个 DL 工作负载的效率。例如,在每个 GPU 的批处理量为 2 的情况下,要达到 90% 的效率,需要近 1.5 TB/s 的有效带宽,甚至超过了 GPU 内存带宽。启用激活检查点后,即使隐藏大小为 2K,2 GB/s 的微薄带宽也能维持 50% 以上的效率。当隐藏大小超过 8K 时,带宽需求降至 1 GB/s 以下。 ## 低内存优化器(Low-Memory Optimization,LOMO) -LOMO(Low-Memory Optimization)[[3]](#ref3)是一种新型优化器,专为资源有限的大型语言模型(LLMs)全参数微调而设计。**核心思想是在计算梯度时立即更新参数,这样就不需要将梯度张量存储在内存中**。 它通过在一步中融合梯度计算和参数更新,将梯度张量的内存使用降低到 O(1),显著减少了内存使用,使得在普通 GPU 上微调巨大模型成为可能。LOMO 结合了梯度归一化、损失缩放等技术,保证了训练过程的稳定性和效率。此外,LOMO 还支持与其他内存节省技术如激活检查点和混合精度训练相结合,进一步降低内存需求。在 SGD 优化器下,LOMO 训练的内存消耗与推理保持一致。 +LOMO(Low-Memory Optimization)[[3]](#ref3)是一种新型优化器,专为资源有限的大型语言模型(LLMs)全参数微调而设计。**核心思想是在计算梯度时立即更新参数,这样就不需要将梯度张量存储在内存中**。它通过在一步中融合梯度计算和参数更新,将梯度张量的内存使用降低到 O(1),显著减少了内存使用,使得在普通 GPU 上微调巨大模型成为可能。LOMO 结合了梯度归一化、损失缩放等技术,保证了训练过程的稳定性和效率。此外,LOMO 还支持与其他内存节省技术如激活检查点和混合精度训练相结合,进一步降低内存需求。在 SGD 优化器下,LOMO 训练的内存消耗与推理保持一致。 ![LOMO](images/05.ZeRO03.png) diff --git a/build_books/sphinx_conf.py b/build_books/sphinx_conf.py index 7dbff0c2..86986751 100644 --- a/build_books/sphinx_conf.py +++ b/build_books/sphinx_conf.py @@ -3,7 +3,7 @@ from urllib.request import urlopen from pathlib import Path -project = "AISystem & AIInfra (AI系统原理)" +project = "AISystem & AIInfra (AI 系统原理)" # copyright = "2024" # author = "ZOMI" language = "cn" # For testing language translations diff --git a/build_books/sphinx_index.md b/build_books/sphinx_index.md index d6702da8..3ea64335 100644 --- a/build_books/sphinx_index.md +++ b/build_books/sphinx_index.md @@ -18,7 +18,7 @@ title: AISystem & AIInfra 本开源项目主要是跟大家一起探讨和学习人工智能、深度学习的系统设计,而整个系统是围绕着在 NVIDIA、ASCEND 等芯片厂商构建算力层面,所用到的、积累、梳理得到 AI 系统全栈的内容。希望跟所有关注 AI 开源项目的好朋友一起探讨研究,共同促进学习讨论。 -![AI系统全栈架构图](images/01Introduction/03Architecture03.png) +![AI 系统全栈架构图](images/01Introduction/03Architecture03.png) # 课程内容大纲 @@ -36,7 +36,7 @@ title: AISystem & AIInfra 第六部分,汇总篇介绍**大模型与 AI 系统**,大模型是基于 AI 集群的全栈软硬件性能优化,通过最小的每一块 AI 芯片组成的 AI 集群,编译器使能到上层的 AI 框架,训练过程需要分布式并行、集群通信等算法支持,而且在大模型领域最近持续演进如智能体等新技术。 -:::{大模型与 AI 系统} 大模型与到AI系统因为内容过多,引起整个 AI 产业和周边的网络、存储、通讯、机房建设风火水电等,在 AI 系统上也加入了更多的集合通信、分布式加速库、AI Agent等内容,因此独立一个大内容后续再详细展开。 ::: +:::{大模型与 AI 系统} 大模型与到 AI 系统因为内容过多,引起整个 AI 产业和周边的网络、存储、通讯、机房建设风火水电等,在 AI 系统上也加入了更多的集合通信、分布式加速库、AI Agent 等内容,因此独立一个大内容后续再详细展开。::: # 课程设立目的