探索 TVM 进行量化方法
探索 TVM 進(jìn)行量化方法
Relay框架
如上圖所示,有兩種不同的并行工作正在進(jìn)行中
? 自動(dòng)整數(shù)量化 - 采用 FP32 框架圖,在 Relay 中自動(dòng)轉(zhuǎn)換為 Int8。
? 接受預(yù)量化整數(shù)模型 - 這種方法接受預(yù)量化模型,引入稱為 QNN 的Relay方言,生成 Int8 Relay圖。
關(guān)于 Relay Automatic FP16 Downcasting 的討論很少。還沒有任何 RFC。 正在對(duì)此進(jìn)行探索/原型設(shè)計(jì),計(jì)劃提出 RFC。
Relay優(yōu)化
? 與目標(biāo)無關(guān)的Relay pass- TVM 社區(qū)不斷添加這些 pass。例子是fuse常量,公共子表達(dá)式消除等。
? 依賴于目標(biāo)的Relay pass- 這些 pass轉(zhuǎn)換Relay圖,針對(duì)目標(biāo)對(duì)進(jìn)行優(yōu)化。一個(gè)例子是 Legalize,或 AlterOpLayout 變換,改變卷積/密集層的布局。TVM 社區(qū)正在努力改進(jìn)基礎(chǔ)架構(gòu),實(shí)現(xiàn)此類轉(zhuǎn)換,添加特定于目標(biāo)的布局轉(zhuǎn)換。一些基礎(chǔ)架構(gòu)工作,良好整體設(shè)計(jì)的先決條件。
Relay到硬件
有了優(yōu)化的Relay圖,就需要編寫優(yōu)化的調(diào)度。像 FP32 一樣,必須只專注于昂貴的算子,如 conv2d、dense 等。有分散的努力,一些在不同后端工作的開發(fā)人員(不一定是 Int8),TVM 社區(qū)正在努力統(tǒng)一。
? Intel x86 - 近期 Int8 探索僅限于 Skylake 和 Cascade Lake
? ARM - 目前正在為 FP32 進(jìn)行一些 NHWC 工作。計(jì)劃是在 FP32 工作完成后,將這項(xiàng)工作擴(kuò)展到 Int8。
? 英偉達(dá)——
基于搜索的自動(dòng)量化
背景
在 tvm 中實(shí)現(xiàn)了一個(gè)量化工作流程,從一些現(xiàn)有的量化框架中,選擇采用注釋-校準(zhǔn)-實(shí)現(xiàn)3階段設(shè)計(jì):
? Annotation:annotation pass根據(jù)每個(gè)算子的rewrite函數(shù),重寫graph,插入模擬的量化算子。模擬量化算子模擬,從浮點(diǎn)數(shù)到整數(shù)量化的舍入誤差和飽和誤差,
? 校準(zhǔn):校準(zhǔn)通道將調(diào)整模擬量化算子的閾值,減少精度下降。
? 實(shí)現(xiàn):實(shí)現(xiàn)過程將實(shí)際使用float32計(jì)算的模擬圖,轉(zhuǎn)換為真正的低精度整數(shù)圖。
在開發(fā)過程中,存在一些缺點(diǎn):
? 在注釋中,每個(gè)注釋作為張INPUT/ WEIGHT/ACTIVATION種不同的量化戰(zhàn)略,這是種特殊算子的,需要不同的組合發(fā)生在不同的模式。這個(gè) make annotation 有很多手動(dòng)規(guī)則,變得很難維護(hù)。
? 模擬圖沒有總比例和數(shù)據(jù)類型信息。將尺度推理和數(shù)據(jù)類型選擇,推遲到實(shí)現(xiàn),這使得這部分的邏輯很難理解。此外,缺乏這些信息,無法在模擬過程中,捕獲溢出錯(cuò)誤。
? 嘗試將量化模型,部署到不同硬件時(shí),面臨硬件差異。有兩種解決方案: 1.注解時(shí)檢查目標(biāo),邏輯比較復(fù)雜;2.添加一個(gè)新的partition pass,首先決定量化拓?fù)?#xff0c;每個(gè)硬件都需要實(shí)現(xiàn)一個(gè)定制的partition pass。
提出了一個(gè)新的量化框架,在循環(huán)中,引入了硬件和學(xué)習(xí)方法。已經(jīng)進(jìn)行了多項(xiàng)改進(jìn)以,解決之前的問題:
? 在每條邊上插入 SimQ (simulated_quantize) 算子,不是通過手動(dòng)注釋規(guī)則。讓學(xué)習(xí)算法在每條邊上,發(fā)現(xiàn)最佳量化策略,不是通過標(biāo)記。
? 將in_scale, in_dtype,添加out_dtype到 SimQ 的定義中。在模擬期間,執(zhí)行比例推理和數(shù)據(jù)類型分配。在 SimQ 中,模擬溢出錯(cuò)誤。
? 提出Hardware抽象,描述硬件屬性和算子約束。通過這種聲明方式,用戶只需Hardware,為不同的硬件定義不同的對(duì)象,無需了解量化邏輯。
工作流程概述
工作流程
給定目標(biāo)硬件的模型和描述,系統(tǒng)將生成一組,位的選擇空間和Topology量化的空間。這里的Topology意思,考慮到硬件和算子約束,哪些節(jié)點(diǎn)/邊將被量化,這將在后面討論。
然后搜索循環(huán)開始:學(xué)習(xí)算法將從選擇空間中,選擇一組參數(shù)——每條邊上的位數(shù)。閾值可以通過從小型校準(zhǔn)數(shù)據(jù)集,收集的統(tǒng)計(jì)數(shù)據(jù)估計(jì)。結(jié)合拓?fù)洹⑽缓烷撝?#xff0c;可以生成模擬圖,在校準(zhǔn)數(shù)據(jù)集(大約 128 個(gè)樣本)上,對(duì)其進(jìn)行評(píng)估。輸出/精度作為反饋,學(xué)習(xí)算法可以選擇下一組位設(shè)置。
最后,通過搜索找到的最佳策略,將模擬模型實(shí)現(xiàn)真實(shí)的低精度整數(shù)模型。
規(guī)格:位、閾值、標(biāo)度
將介紹幾種重要性符號(hào):位、閾值、比例。
一般而言,量化的目標(biāo),將浮點(diǎn)數(shù)(實(shí)數(shù)值)運(yùn)行的圖,轉(zhuǎn)換為整數(shù)(定量值)運(yùn)行的圖,不會(huì)犧牲太多精度。給定一個(gè)具有真實(shí)值的張量,轉(zhuǎn)換后的 quant 值的關(guān)系是什么?這是將在當(dāng)前實(shí)現(xiàn)中遵循的規(guī)范:
硬件說明
硬件描述,試圖為在量化過程中,需要考慮的硬件屬性,提供一個(gè)中心抽象。通過聲明這些屬性,可以避免在隨后的量化步驟中,處理硬件特定條件。
目前可以指定每個(gè)算子的,輸入數(shù)據(jù)類型和輸出數(shù)據(jù)類型。
desc = Hardware()
desc[‘a(chǎn)dd’].append(OpDesc(in_dtypes=[‘int32’, ‘int32’], out_dtypes=[‘int32’]))
desc[‘a(chǎn)dd’].append(OpDesc(in_dtypes=[‘float32’, ‘float32’], out_dtypes=[‘float32’]))
desc[‘nn.conv2d’].append(OpDesc(in_dtypes=[‘int16’, ‘int16’], out_dtypes=[‘int32’]))
desc[‘nn.conv2d’].append(OpDesc(in_dtypes=[‘int8’, ‘int8’], out_dtypes=[‘int32’]))
desc[‘nn.global_avg_pool2d’].append(OpDesc(in_dtypes=[‘float32’, ‘float32’], out_dtypes=[‘float32’]))
硬件信息在整個(gè)過程中已經(jīng)使用了多次:
? 通過指定算子只支持浮點(diǎn)計(jì)算,系統(tǒng)將實(shí)現(xiàn)一個(gè)結(jié)束,需要放在算子之前。可以解決 VTA 流水線的一些問題,指定一些算子,在 VTA 核心上,使用整數(shù)指令運(yùn)行,一些算子,在普通 cpu 上,使用浮點(diǎn)指令。
? 位選擇空間由此產(chǎn)生。對(duì)于每條邊,可以推理出使用的最大位,取決于數(shù)據(jù)類型約束。
? 在決定了每條邊使用的位數(shù)后,根據(jù)硬件信息,選擇合適的數(shù)據(jù)類型。
模擬
閾值估計(jì)
為了估計(jì)閾值,在校準(zhǔn)數(shù)據(jù)集上運(yùn)行模型,收集需要的統(tǒng)計(jì)信息。目前將保存中間算子的所有輸出。為了從收集的輸出中確定閾值,有幾種策略:
? max_range:使用輸出的最大值作為對(duì)應(yīng)節(jié)點(diǎn)的閾值。
? power2_range:將最大值四舍五入到最接近的兩個(gè)值的冪,作為閾值。
? kl_estimate:選擇一個(gè)閾值,使實(shí)際輸出和量化輸出之間,KL 距離足夠小。
目前,選擇了這種power2_range方法,可以使用移位來代替乘法,在最終的量化模型中,提供更好的性能。雖然kl_estimate帶來更好的準(zhǔn)確度,但相當(dāng)耗時(shí),目前在搜索中使用不可行。
一個(gè)棘手的問題是,對(duì)于像加法這樣的算子,只能在其算子的標(biāo)度為 eqaul 時(shí)執(zhí)行。首先統(tǒng)一其算子的規(guī)模。為了實(shí)現(xiàn)這一點(diǎn),估計(jì)閾值將在模擬之前進(jìn)行調(diào)整。threshold_rectify引入了一個(gè)命名轉(zhuǎn)換和一個(gè)特定于算子的屬性:
@register_fthreshold_rectify(‘a(chǎn)dd’)
def threshold_rectify_for_add(in_bits, out_bits, in_tholds, out_tholds):
choose scale of the one with maximum threshold
idx = np.argmax(in_tholds)
unified_scale = in_tholds[idx] / (2**(in_bits[idx] - sign_bit))
adjust thresholds according to the unified scale
…
模擬量化
給定比特和閾值,可以嘗試生成一個(gè)模型,模擬量化帶來的誤差。經(jīng)過分析,可以發(fā)現(xiàn)誤差來自幾個(gè)方面: 1.舍入誤差;2.飽和誤差;3.溢出錯(cuò)誤。
將simulated_quantize在每條邊上,插入一個(gè)算子,試圖模擬這些錯(cuò)誤。定義如下:
def simulated_quantize(data, in_scale, out_scale, clip_min, clip_max, in_dtype, out_dtype):
if in_dtype == ‘float32’ and out_dtype == ‘float32’:
# no need to quantize
return data
# simulated overflow error
data = data / in_scale
data = topi.cast(data, in_dtype)
data = data * in_scalescaled_data = data / out_scale
# simulate saturated error
clipped_data = topi.clip(scaled_data, clip_min, clip_max)
# simulate round error
rounded_data = topi.cast(topi.round(scaled_data), out_dtype)
out = rounded_data * out_scalereturn out
如何通過位和閾值,計(jì)算這些參數(shù)呢?out_scale、clip_min、clip_max 是非常嚴(yán)格的:
integer_range = 2**(bit - sign_bit)
out_scale = threshold / integer_range
clip_min = - (integer_range - 1)
clip_max = integer_range - 1
對(duì)于in_scale、in_dtype、out_dtype,需要做額外推理。
尺度推理
可以在上面的模型中,發(fā)現(xiàn)in_scale,SimQ 的實(shí)際上,前一個(gè)算子輸出的尺度,可以根據(jù)算子定義計(jì)算。為這樣的屬性,提供了一個(gè)注冊(cè)函數(shù):
@register_finfer_scale(‘nn.conv2d’):
def infer_scale_for_conv2d(in_scales):
return in_scales[0] * in_scales[1]
數(shù)據(jù)類型分配
對(duì)于數(shù)據(jù)類型,將遍歷算子,從硬件描述中,選擇滿足輸入位和輸出位要求的算子規(guī)范。
學(xué)習(xí)
有了上面描述的所有準(zhǔn)備工作,量化問題轉(zhuǎn)換為學(xué)習(xí)問題:希望從選擇空間中,找到最佳設(shè)置,以實(shí)現(xiàn)模擬模型的最佳精度(或其它目標(biāo),如性能),可以使用每輪的輸出(準(zhǔn)確度)作為反饋。
對(duì)于這個(gè)學(xué)習(xí)問題,實(shí)現(xiàn)了random_search, simulated_anealing, 也是一個(gè)貪心算法。目前實(shí)驗(yàn)表明貪婪搜索是最可行的。
日志格式
搜索空間很大,搜索過程可能很長,最好有一個(gè)正式的日志格式,記錄實(shí)驗(yàn)細(xì)節(jié),實(shí)現(xiàn)可重復(fù)性和可交換性。選擇json格式,詳細(xì)信息如下:
? version : 日志格式版本。
? 策略:量化策略。
o model_hash:模型的哈希值,可用于驗(yàn)證模型是否匹配策略。
o 拓?fù)?#xff1a;量化模型的拓?fù)?br /> ? node_conds : 哪些節(jié)點(diǎn)將被量化
? edge_conds : 哪些邊將被量化
o bits : 每條邊上的位數(shù)。
o 閾值:每個(gè)節(jié)點(diǎn)輸出的閾值。
? 結(jié)果:實(shí)驗(yàn)結(jié)果
o sim_acc : 模擬模型的精度
搜索速度
實(shí)現(xiàn)
在得到最佳量化策略后:拓?fù)洹⒈忍亍㈤撝祵?shí)現(xiàn)模擬圖,到低精度量化圖,相當(dāng)直截了當(dāng)?shù)摹V恍枰玫途日麛?shù)運(yùn)算,替換每條邊上的 SimQ 運(yùn)算。
調(diào)試
調(diào)試量化模型哪里出了問題,因?yàn)橥ǔV恢雷罱K的準(zhǔn)確性很差。實(shí)現(xiàn)了inspect_graph_statistic逐層量化前后統(tǒng)計(jì)差異的功能,可以快速定位到哪里出錯(cuò)了。開發(fā)過程中,證明非常有幫助。
接口演示
from tvm import hago
ideally we will have predefined description for x86, arm, gpu and vta
hardware = hago.create_sample_hardware()
strategy, sim_acc = hago.search_quantize_strategy(graph, hardware, dataset)
quantizer = hago.create_quantizer(graph, hardware, strategy)
simulated_graph = quantizer.simulate()
quantized_graph = quantizer.quantize()
當(dāng)前狀態(tài)
在 resnet18_v1 上獲得了 68.7% 的初步結(jié)果,沒有跳過第一個(gè)卷積層,只使用 2 的冪范圍,不是 kl 距離,應(yīng)該還有更多的改進(jìn)空間。
參考鏈接:
https://discuss.tvm.apache.org/t/rfc-search-based-automated-quantization/5483
https://discuss.tvm.apache.org/t/quantization-story/3920
總結(jié)
以上是生活随笔為你收集整理的探索 TVM 进行量化方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CPU0 处理器的架构及应用
- 下一篇: 一些量化(quantization)技巧