int8量化和tvm實現

量化主要有兩種方案

  • 直接訓練量化模型如Deepcompression,Binary-Net,Tenary-Net,Dorefa-Net
  • 對訓練好的float模型(以float32爲例)直接進行量化(以int8爲例),這邊博客主要講這個

int8量化原理

將已有的float32型的數據改成A = scale_A * QA + bias_A,B類似,NVIDIA實驗證明可以去掉bias,即A = scale_A * QA
也即是QA(量化後的A) = A / scale_A
在這裏插入圖片描述
有了對應公式A = scale_A * QA,則可以將float32的數據映射到int8,但是由於float32的數據動態範圍比int8要大很多,如果數據分佈不均勻,極限情況比如如果float32的原始數據都在127的周圍,最後量化後都是127了,精度損失嚴重,而int8其他的數值完全沒有用到,沒有完全利用int8的數值範圍,所以直接最大最小映射不是一個最優方案。
在這裏插入圖片描述
那能不能找到一個threshold,丟掉一部分float32數值,然後能夠更加均勻地映射,從而充分地利用到int8的數值範圍
在這裏插入圖片描述

Softmax原理講解中提到
交叉熵= 熵 + KL散度(相對熵)
1)信息熵:編碼方案完美時,最短平均編碼長度的是多少。
2)交叉熵:用次優編碼方式時平均編碼長度是多少,即需要多少個bits來表示
平均編碼長度 = 最短平均編碼長度 + 一個增量
3)相對熵:編碼方案不一定完美時,平均編碼長度相對於最小值的增加值。(即上面那個增量)
int8編碼時所需編碼長度 = float32編碼時所需編碼長度 + int8多需要的編碼長度
因此相對熵就是int8float32(次優編碼)比float32(最優編碼)多出來的編碼長度越小越好,所以需要找到一個合適的threshold,使得兩者之間的相對熵最小即KL散度
這個KL距離代表了損失的信息
在這裏插入圖片描述

如何尋找一個合適的threshold呢,需要一個校準集合 Calibration Dataset,在校準數據集上運行FP32推理。收集激活的直方圖,並生成一組具有不同閾值的8位表示法,並選擇具有最少kl散度的表示;kl-散度是在參考分佈(即FP32激活)和量化分佈之間(即8位量化激活)之間。
在這裏插入圖片描述

TVM實現int8量化

# 從前端load模型,mxnet、onnx等
sym, _ = relay.frontend.from_mxnet(sym, {'data': data_shape})
# 隨機生成test的模型參數,如果有已訓練好的模型參數可以忽略
sym, params = tvm.relay.testing.create_workload(sym)
# 模型量化
with relay.quantize.qconfig(skip_k_conv=0, round_for_shift=True):
    sym = relay.quantize.quantize(sym, params)
# 模型優化(經過試驗,tvm系統默認有一些常用的resnet的卷積優化,注意這個優化是和卷積配置包括輸入輸出kernel的數量綁定的)
# 如果使用系統已有的卷積優化配置則速度可保證,如果使用一些新奇的卷積結構需要使用auto tuning優化,不然很慢
參考 https://docs.tvm.ai/tutorials/autotvm/tune_relay_cuda.html#auto-tuning-a-convolutional-network-for-nvidia-gpu
# load最優的優化算子,然後編譯模型
with autotvm.apply_history_best(log_file):
    print("Compile...")
    with relay.build_config(opt_level=3):
        graph, lib, params = relay.build_module.build(
            net, target=target, params=params)
    # 加載參數並運行
    ctx = tvm.context(str(target), 0)
    module = runtime.create(graph, lib, ctx)
    data_tvm = tvm.nd.array((np.random.uniform(size=input_shape)).astype(dtype))
    module.set_input('data', data_tvm)
    module.set_input(**params)
    # module.set_input(**{k:tvm.nd.array(v, ctx) for k, v in params.items()})
    module.run()
    # 測試forward時間
    e = module.module.time_evaluator("run", ctx, number=2000, repeat=3)
    t = module(data_tvm).results
    t = np.array(t) * 1000
    print('{} (batch={}): {} ms'.format(name, batch, t.mean()))

tvm的一些代碼鏈接tvm-cuda-int8-benchmark


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章