混合精度訓練

混合精度訓練

轉自:https://zhuanlan.zhihu.com/p/441591808

通常我們訓練神經網絡模型的時候默認使用的數據類型為單精度FP32。近年來,為了加快訓練時間、減少網絡訓練時候所占用的內存,并且保存訓練出來的模型精度持平的條件下,業界提出越來越多的混合精度訓練的方法。這里的混合精度訓練是指在訓練的過程中,同時使用單精度(FP32)和半精度(FP16)。

1 浮點數據類型

根據IEEE二進制浮點數算術標準(IEEE 754)的定義,浮點數據類型分為雙精度(Fp64)、單精度(Fp32)、半精度(FP16)三種,其中每一種都有三個不同的位來表示。FP64表示采用8個字節共64位,來進行的編碼存儲的一種數據類型;同理,FP32表示采用4個字節共32位來表示;FP16則是采用2字節共16位來表示。如圖所示:

在這里插入圖片描述

從圖中可以看出,與FP32相比,FP16的存儲空間是FP32的一半,FP32則是FP16的一半。主要分為三個部分:

  • 最高位表示符號位sign bit。
  • 中間表示指數位exponent bit。
  • 低位表示分數位fraction bit。

以FP16為例子,第一位符號位sign表示正負符號,接著5位表示指數exponent,最后10位表示分數fraction。公式為:
x=(?1)S×2E?15×(1+fraction1024)x=(-1)^S\times 2^{E-15}\times (1+\frac{fraction}{1024}) x=(?1)S×2E?15×(1+1024fraction?)
同理,一個規則化FP32的真值為:
x=(?1)S×2E?127×1.Mx=(-1)^S\times 2^{E-127}\times1.M x=(?1)S×2E?127×1.M
一個規則化的FP64的真值為:
x=(?1)S×2E?1023×(1.M)x=(-1)^S\times 2^{E-1023}\times (1.M) x=(?1)S×2E?1023×(1.M)
FP16可以表示的最大值為 01111011111111110\ 11110\ 11111111110?11110?1111111111 ,計算方法為:
(?1)0×230?15×1.1111111111=1.1111111111(b)×215=1.9990234375(d)times215=65504(-1)^0\times2^{30-15}\times1.1111111111\\=1.1111111111(b)\times2^{15}\\=1.9990234375(d)times2^{15}\\=65504 (?1)0×230?15×1.1111111111=1.1111111111(b)×215=1.9990234375(d)times215=65504
FP16可以表示的(絕對值)最小值為 00000100000000000\ 00001\ 00000000000?00001?0000000000,計算方法為:
(?1)0×21?15=6.104×10?5(-1)^0\times 2^{1-15}=6.104\times 10^{-5} (?1)0×21?15=6.104×10?5
因此FP16的最大取值范圍是[-65504 - 66504],能表示的精度范圍 2?242^{-24}2?24,超過這個數值的數字會被直接置0。

2 使用FP16訓練問題

首先來看看為什么需要混合精度。使用FP16訓練神經網絡,相對比使用FP32帶來的優點有:

  1. 減少內存占用:FP16的位寬是FP32的一半,因此權重等參數所占用的內存也是原來的一半,節省下來的內存可以放更大的網絡模型或者使用更多的數據進行訓練。
  2. 加快通訊效率:針對分布式訓練,特別是在大模型訓練的過程中,通訊的開銷制約了網絡模型訓練的整體性能,通訊的位寬少了意味著可以提升通訊性能,減少等待時間,加快數據的流通。
  3. 計算效率更高:在特殊的AI加速芯片如華為Ascend 910和310系列,或者NVIDIA VOTAL架構的Titan V and Tesla V100的GPU上,使用FP16的執行運算性能比FP32更加快。

但是使用FP16同樣會帶來一些問題,其中最重要的是1)精度溢出和2)舍入誤差。

  1. **數據溢出:**數據溢出比較好理解,FP16的有效數據表示范圍為 6.10×10?5?655046.10\times 10^{-5} - 655046.10×10?5?65504,FP32的有效數據表示范圍為 1.4×10?45?1.7×10381.4\times10^{-45}-1.7\times10^{38}1.4×10?45?1.7×1038。可見FP16相比FP32的有效范圍要窄很多,使用FP16替換FP32會出現上溢(Overflow)和下溢(Underflow)的情況。而在深度學習中,需要計算網絡模型中權重的梯度(一階導數),因此梯度會比權重值更加小,往往容易出現下溢情況。
  2. **舍入誤差:**Rounding Error指示是當網絡模型的反向梯度很小,一般FP32能夠表示,但是轉換到FP16會小于當前區間內的最小間隔,會導致數據溢出。如0.00006666666在FP32中能正常表示,轉換到FP16后會表示成為0.000067,不滿足FP16最小間隔的數會強制舍入。

3 混合精度相關技術

為了想讓深度學習訓練可以使用FP16的好處,又要避免精度溢出和舍入誤差。于是可以通過FP16和FP32的混合精度訓練(Mixed-Precision),混合精度訓練過程中可以引入權重備份(Weight Backup)、損失放大(Loss Scaling)、精度累加(Precision Accumulated)三種相關的技術。

3.1 權重備份(Weight Backup)

權重備份主要用于解決舍入誤差的問題。其主要思路是把神經網絡訓練過程中產生的激活activations、梯度 gradients、中間變量等數據,在訓練中都利用FP16來存儲,同時復制一份FP32的權重參數weights,用于訓練時候的更新。具體如下圖所示。

在這里插入圖片描述

從圖中可以了解,在計算過程中所產生的權重weights,激活activations,梯度gradients等均使用 FP16 來進行存儲和計算,其中權重使用FP32額外進行備份。由于在更新權重公式為:
weight=weight+η?gradientweight=weight+\eta\ *\ gradient weight=weight+η???gradient
深度模型中,lr x gradent的參數值可能會非常小,利用FP16來進行相加的話,則很可能會出現舍入誤差問題,導致更新無效。因此通過將權重weights拷貝成FP32格式,并且確保整個更新過程是在 fp32 格式下進行的。即:
weight32=weight32+η?gradient16weight_{32}=weight_{32}+\eta\ *\ gradient_{16} weight32?=weight32?+η???gradient16?
權重用FP32格式備份一次,那豈不是使得內存占用反而更高了呢?是的,額外拷貝一份weight的確增加了訓練時候內存的占用。 但是實際上,在訓練過程中內存中分為動態內存和靜態內容,其中動態內存是靜態內存的3-4倍,主要是中間變量值和激活activations的值。而這里備份的權重增加的主要是靜態內存。只要動態內存的值基本都是使用FP16來進行存儲,則最終模型與整網使用FP32進行訓練相比起來, 內存占用也基本能夠減半。

3.2 損失縮放(Loss Scaling)

如圖所示,如果僅僅使用FP32訓練,模型收斂得比較好,但是如果用了混合精度訓練,會存在網絡模型無法收斂的情況。原因是梯度的值太小,使用FP16表示會造成了數據下溢出(Underflow)的問題,導致模型不收斂,如圖中灰色的部分。于是需要引入損失縮放(Loss Scaling)技術。

在這里插入圖片描述

下面是在網絡模型訓練階段, 某一層的激活函數梯度分布式中,其中有68%的網絡模型激活參數位0,另外有4%的精度在 2?32?2?202^{-32}-2^{-20}2?32?2?20這個區間內,直接使用FP16對這里面的數據進行表示,會截斷下溢的數據,所有的梯度值都會變為0。

在這里插入圖片描述

為了解決梯度過小數據下溢的問題,對前向計算出來的Loss值進行放大操作,也就是把FP32的參數乘以某一個因子系數后,把可能溢出的小數位數據往前移,平移到FP16能表示的數據范圍內。根據鏈式求導法則,放大Loss后會作用在反向傳播的每一層梯度,這樣比在每一層梯度上進行放大更加高效。

在這里插入圖片描述

損失放大是需要結合混合精度實現的,其主要的主要思路是:

  • Scale up階段,網絡模型前向計算后在反響傳播前,將得到的損失變化值DLoss增大 2K2^K2K倍。
  • Scale down階段,反向傳播后,將權重梯度縮 2K2^K2K 倍,恢復FP32值進行存儲。

**動態損失縮放(Dynamic Loss Scaling):**上面提到的損失縮放都是使用一個默認值對損失值進行縮放,為了充分利用FP16的動態范圍,可以更好地緩解舍入誤差,盡量使用比較大的放大倍數。總結動態損失縮放算法,就是每當梯度溢出時候減少損失縮放規模,并且間歇性地嘗試增加損失規模,從而實現在不引起溢出的情況下使用最高損失縮放因子,更好地恢復精度。

動態損失縮放的算法如下:

  1. 動態損失縮放的算法會從比較高的縮放因子開始(如2^24),然后開始進行訓練迭代中檢查數是否會溢出(Infs/Nans);
  2. 如果沒有梯度溢出,則不進行縮放,繼續進行迭代;如果檢測到梯度溢出,則縮放因子會減半,重新確認梯度更新情況,直到數不產生溢出的范圍內;
  3. 在訓練的后期,loss已經趨近收斂穩定,梯度更新的幅度往往小了,這個時候可以允許更高的損失縮放因子來再次防止數據下溢。
  4. 因此,動態損失縮放算法會嘗試在每N(N=2000)次迭代將損失縮放增加F倍數,然后執行步驟2檢查是否溢出。

3.3 精度累加(Precision Accumulated)

在混合精度的模型訓練過程中,使用FP16進行矩陣乘法運算,利用FP32來進行矩陣乘法中間的累加(accumulated),然后再將FP32的值轉化為FP16進行存儲。簡單而言,就是利用FP16進行矩陣相乘,利用FP32來進行加法計算彌補丟失的精度。 這樣可以有效減少計算過程中的舍入誤差,盡量減緩精度損失的問題。

例如在Nvidia Volta 結構中帶有Tensor Core,可以利用FP16混合精度來進行加速,還能保持精度。Tensor Core主要用于實現FP16的矩陣相乘,在利用FP16或者FP32進行累加和存儲。在累加階段能夠使用FP32大幅減少混合精度訓練的精度損失。

在這里插入圖片描述

4 混合精度訓練策略(Automatic Mixed Precision,AMP)

混合精度訓練有很多有意思的地方,不僅僅是在深度學習,另外在HPC的迭代計算場景下,從迭代的開始、迭代中期和迭代后期,都可以使用不同的混合精度策略來提升訓練性能的同時保證計算的精度。以動態的混合精度達到計算和內存的最高效率比也是一個較為前言的研究方向。

以NVIDIA的APEX混合精度庫為例,里面提供了4種策略,分別是默認使用FP32進行訓練的O0,只優化前向計算部分O1、除梯度更新部分以外都使用混合精度的O2和使用FP16進行訓練的O3。具體如圖所示。

在這里插入圖片描述

O1策略中,會根據實際Tensor和Ops之間的關系建立黑白名單來使用FP16。例如GEMM和CNN卷積操作對于FP16操作特別友好的計算,會把輸入的數據和權重轉換成FP16進行運算,而softmax、batchnorm等標量和向量在FP32操作好的計算,則是繼續使用FP32進行運算,另外還提供了動態損失縮放(dynamic loss scaling)。

而O2策略中,模型權重參數會轉化為FP16,輸入的網絡模型參數也轉換為FP16,Batchnorms使用FP32,另外模型權重文件復制一份FP32用于跟優化器更新梯度保持一致都是FP32,另外還提供動態損失縮放(dynamic loss scaling)。使用了權重備份來減少舍入誤差和使用損失縮放來避免數據溢出。

當然上面提供的策略是跟硬件有關系,并不是所有的AI加速芯片都使用,這時候針對自研的AI芯片,需要找到適合得到混合精度策略。

5 實驗結果

從下圖的Accuracy結果可以看到,混合精度基本沒有精度損失:

在這里插入圖片描述

Loss scale的效果:

在這里插入圖片描述

題外話,前不久去X公司跟X總監聊下一代AI芯片架構的時候,他認為下一代芯片可以不需要加入INT8數據類型,因為Transformer結構目前有大一統NLP和CV等領域的趨勢,從設計、流片到量產,2年后預計Transformer會取代CNN成為最流行的架構。我倒是不同意這個觀點,目前來看神經網絡的4個主要的結構MLP、CNN、RNN、Transformer都有其對應的使用場景,并沒有因為某一種結構的出現而推翻以前的結構。只能說根據使用場景的側重點比例有所不同,我理解Int8、fp16、fp32的數據類型在AI芯片中仍然會長期存在,針對不同的應用場景和計算單元會有不同的比例。

參考文獻:

  1. Micikevicius, Paulius, et al. "Mixed precision training."arXiv preprint arXiv:1710.03740(2017).
  2. Ott, Myle, et al. "Scaling neural machine translation."arXiv preprint arXiv:1806.00187(2018).
  3. https://en.wikipedia.org/wiki/Half-precision_floating-point_format
  4. apex.amp - Apex 0.1.0 documentation.
  5. Automatic Mixed Precision for Deep Learning.
  6. Training With Mixed Precision.
  7. Dreaming.O:淺談混合精度訓練.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532500.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532500.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532500.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

拓撲排序C++

拓撲排序C 幾個基本概念的介紹 入度和出度 圖中的度:所謂頂點的度(degree),就是指和該頂點相關聯的邊數。在有向圖中,度又分為入度和出度。 入度 (in-degree) :以某頂點為弧頭,終止于該頂點的邊的數目稱為該頂點的…

C++面試常考題——編譯內存相關

C面試常考題——編譯內存相關 轉自:https://leetcode-cn.com/leetbook/read/cpp-interview-highlights/e4ns5g/ C程序編譯過程 編譯過程分為四個過程:編譯(編譯預處理、編譯、優化),匯編,鏈接。 編譯預處…

C++遍歷刪除元素

C遍歷刪除元素 轉自:http://zencoder.info/2019/10/11/erase-element-from-container/ 今天看到一個patch fix從std::map中遍歷刪除元素導致crash問題,突然意識到自己對如何正確地從map等C容器中刪除元素也沒有很牢固清醒的認知。重新梳理了下這塊的正…

關鍵字庫函數

關鍵字庫函數 轉自&#xff1a;https://leetcode-cn.com/leetbook/read/cpp-interview-highlights/ej3mx1/ sizeof和strlen的區別 strlen 是頭文件<cstring> 中的函數&#xff0c;sizeof 是 C 中的運算符。 strlen 測量的是字符串的實際長度&#xff08;其源代碼如下&…

memcpy和memmove的區別以及內存重疊問題

memcpy和memmove的區別以及內存重疊問題 轉自&#xff1a;https://www.codecomeon.com/posts/89/ 區別 memcpy() 和 memmove() 都是C語言中的庫函數&#xff0c;在頭文件 string.h 中&#xff0c;作用是拷貝一定長度的內存的內容&#xff0c;原型分別如下&#xff1a; void…

從頭搭建一個深度學習框架

從頭搭建一個深度學習框架 轉自&#xff1a;Build a Deep Learning Framework From Scratch 代碼&#xff1a;https://github.com/borgwang/tinynn 當前深度學習框架越來越成熟&#xff0c;對于使用者而言封裝程度越來越高&#xff0c;好處就是現在可以非常快速地將這些框架作為…

關于python import的sys.path路徑問題

關于python import的sys.path路徑問題 sys.path 先說一下 sys.path 這個變量&#xff0c;該變量需要導入 sys 官方庫方可使用&#xff0c;它是一個列表&#xff0c;是當前 python 文件 import 庫時會逐個搜索列表中的路徑。 初始化 sys.path 從這些位置初始化&#xff1a; …

python pdb調試基本命令整理

python pdb調試基本命令整理 使用簡介 啟動調試 侵入式 在 py 文件內部設置&#xff1a; import pdb; pdb.set_trace()程序會在運行到這一行時停下來&#xff0c;進入 pdb 交互。 非侵入式 在運行 py 腳本時&#xff1a; python -m pdb main.py程序會在一啟動時就進入 pdb 交…

Docker概念理解

Docker概念理解 本文非Docker命令大全&#xff0c;而是對Docker的概念、原理等作說明&#xff0c;適合有一定實操經驗后來加深理解。 轉自&#xff1a;docker從入門到實踐 Docker簡介 本章將帶領你進入 Docker 的世界。 什么是 Docker&#xff1f; 用它會帶來什么樣的好處&a…

Dockerfile詳解

Dockerfile詳解 轉自&#xff1a;https://yeasy.gitbook.io/docker_practice/ 使用Dockerfile定制鏡像 從剛才的 docker commit 的學習中&#xff0c;我們可以了解到&#xff0c;鏡像的定制實際上就是定制每一層所添加的配置、文件。如果我們可以把每一層修改、安裝、構建、操…

Dockerfile最佳實踐

Dockerfile最佳實踐 本文是原作者對 Docker 官方文檔中 Best practices for writing Dockerfiles 的理解與翻譯。 轉自&#xff1a;附錄四&#xff1a;Dockerfile 最佳實踐 一般性指南和建議 容器應該是短暫的 通過 Dockerfile 構建的鏡像所啟動的容器應該盡可能短暫&#xf…

Linux內存背后的那些神秘往事

Linux內存背后的那些神秘往事 作者&#xff1a;大白斯基&#xff08;公眾號&#xff1a;后端研究所&#xff09; 轉自&#xff1a;https://mp.weixin.qq.com/s/l_YdpyHht5Ayvrc7LFZNIA 前言 大家好&#xff0c;我的朋友們&#xff01; CPU、IO、磁盤、內存可以說是影響計算機…

mmdeploy快速上手

mmdeploy快速上手 若要將使用 openmmlab 的框架&#xff08;如mmdet、mmcls&#xff09;等訓練的模型進行快速部署&#xff0c;同樣來自 openmmlab 的 mmdeploy 無疑是最合適的選擇&#xff0c;本文將簡單地完成一個 Faster RCNN 模型的部署。 配置 本文基于如下軟硬件配置&…

精簡CUDA教程——CUDA Driver API

精簡CUDA教程——CUDA Driver API tensorRT從零起步邁向高性能工業級部署&#xff08;就業導向&#xff09; 課程筆記&#xff0c;講師講的不錯&#xff0c;可以去看原視頻支持下。 Driver API概述 CUDA 的多級 API CUDA 的 API 有多級&#xff08;下圖&#xff09;&#xff…

CUDA編程入門極簡教程

CUDA編程入門極簡教程 轉自&#xff1a;CUDA編程入門極簡教程 作者&#xff1a;小小將 前言 2006年&#xff0c;NVIDIA公司發布了CUDA&#xff0c;CUDA是建立在NVIDIA的CPUs上的一個通用并行計算平臺和編程模型&#xff0c;基于CUDA編程可以利用GPUs的并行計算引擎來更加高效地…

精簡CUDA教程——CUDA Runtime API

精簡CUDA教程——CUDA Runtime API tensorRT從零起步邁向高性能工業級部署&#xff08;就業導向&#xff09; 課程筆記&#xff0c;講師講的不錯&#xff0c;可以去看原視頻支持下。 Runtime API 概述 環境 圖中可以看到&#xff0c;Runtime API 是基于 Driver API 之上開發的…

Python并發——concurrent.futures梳理

Python并發——concurrent.futures梳理 參考官方文檔&#xff1a; concurrent.futures — 啟動并行任務 Executor對象 class concurrent.funtures.Executor該抽象類是 ThreadPoolExecutor 和 ProcessPoolExecutor 的父類&#xff0c;提供異步執行調用方法。要通過它的子類調用…

TensorRT ONNX 基礎

TensorRT ONNX 基礎 tensorRT從零起步邁向高性能工業級部署&#xff08;就業導向&#xff09; 課程筆記&#xff0c;講師講的不錯&#xff0c;可以去看原視頻支持下。 概述 TensorRT 的核心在于對模型算子的優化&#xff08;合并算子、利用當前 GPU 特性選擇特定的核函數等多種…

回文子串、回文子序列相關題目

回文子串、回文子序列相關題目 回文子串是要連續的&#xff0c;回文子序列可不是連續的。 516. 最長回文子序列 dp數組含義&#xff1a;dp[i][j]dp[i][j]dp[i][j] 表示子序列 s[i,j]s[i,j]s[i,j] 中的最長回文子序列的長度。 dp數組初始化&#xff1a;子序列長度為 1 時&am…

mmdetection tools工具梳理

mmdetection tools工具梳理 mmdetection 是一個非常好用的開源目標檢測框架&#xff0c;我們可以用它方便地訓練自己的目標檢測模型&#xff0c;mmdetection 項目倉庫提供許多實用的工具來實現幫助我們進行各種測試。本篇將梳理以下 mmdetection 項目倉庫 tools 目錄下的各種實…