# 2025q1 Homework5 (assessment)
contributed by < `Denny0097` >
## 期末專題目標
目標:設計或參考針對 hardware friendly 的 quantization 方案,並設計 module 嘗試將現有模型進行 quant 後對應資料型態寫入/讀出 memory 。
Quantization的方法犧牲些許精度,但可以大量減少資料對記憶體的使用、運算的時間跟耗能,舉例來說,(參考45nm technology)
| Operation | Energy consumption (pJ) |
| -------------------------------- | -------------------------- |
| FP32 Multiply | 3.7 |
| FP32 Add | 0.9 |
|INT32 Add | 0.1 |
| INT8 / UINT8 Multiply | 0.2 |
| INT8 / UINT8 Add | 0.03 |
| Bit Shift | 0.01 |
如果能 flt 32 quant to int 8 ,就能使 add operation 所需的耗能減少到1/30以下,throughput 也會提升 4 倍,從 add operation 的所需時間來說,假設使用最簡單的 Ripple Carry Adder 就只需要 1/4 的時間。
quantization 的方法許多,大部分主要考慮的是 quant 後盡可能保持 dequant 資料的精度,讓計算落差不會太大,也因此量化的方法可能會有很多複雜的計算,而如果要使用量化後的數值進行運算,就需要在硬體上,這樣會增加硬體的負擔。
為了減輕 dequant 的計算複雜度,可以讓 quantization operation 變成 bitwise operation ,最簡化 quant 過程中的除法運算。
閱讀〈[Microsoft researchers build 1-bit AI LLM with 2B parameters — model small enough to run on some CPUs](https://www.tomshardware.com/tech-industry/artificial-intelligence/microsoft-researchers-build-1-bit-ai-llm-with-2b-parameters-model-small-enough-to-run-on-some-cpus)〉後心得

即使 weight 只用 -1/0/1 表示,但在大型語言模型中有幾十層甚至幾百層的模型架構作為彌補,整體來說,乘加運算仍然可以提供有用的特徵轉換,加上簡單的 bias 與 ReLU,仍然能模擬出一些非線性行為。
但如果今天在小型的圖形分類模型上,是否會因為參數太少、訓練資料不足 (e.g. CIFAR-10 dataset have only 60000 images) 導致沒辦法用 1.58-bit 訓練出相較於一般 flt 32 model,accuracy drop 夠低、足夠分類大部分資料的模型?
另外,因為這屬於 QAT(Quantization Aware Training),所以 scale 的計算應比簡單的 post-quantization 複雜,如此一來在硬體上的計算消耗是否提高許多?
為了了解這部分問題,我需要先從論文詳細了解 quantization 策略,後續會嘗試復刻該方法在小型模型(e.g. vgg8)上。
1.58-bits 由來:
$$
Entropy = -\sum_ip_ilog_b(p_i)
$$
另一方面,假設 1.58 bit 的方法可行,還必須設計對應的資料傳輸方式, 1.58-bits 的 data output/input 聽起來非常有難度,我認為會用 2-bits 去做計算,但在儲存上使用特殊的壓縮方式來達到低於 2-bits ,舉例來說, 0 跟 -1 的 weight 我用 1 bit,而 -1 的資料我用 a bits 去記錄其位置(a 考慮為單一 channel weight data number):

這是一個很缺乏各類考量的想法。
我閱讀 [你所不知道的 C 語言:記憶體管理、對齊及硬體特性](https://hackmd.io/@sysprog/c-prog/%2Fs%2FBkuMDQ9K7)一文,
> 原本推論 char 為 1 byte,而 int 為 4 byte ,兩個加起來應該為 5 byte,然而實際上為 8 byte,由於 int 為 4 byte ,所以 char 為了要能 alignment 4 byte 就多加了 3 byte 進去 ,使得 cpu 存取速度不會因 address 不是在 4 的倍數上,存取變慢。
提醒到我基本的性質, CPU 會因為 address 沒有在 4 的倍數而可能導致存取變慢,需要評估到底有沒有特地去做到 1.58-bit 而喪失了 alignment 犧牲存取效率。
**於是我去閱讀 bitnet 的論文**:
現在提出的 1.58 bit 是使用3進制的方式來儲存資料,方法之一舉例來說假如有 5 數:1, -1, 0, 1, 0。
將參數對應到 {-1, 0, 1} 可以 mapping to {0, 1, 2}: -1→0,0→1,1→2
5 numbers fter mapping : -2, 0, 1, 2, 1
2×81 + 0×27 + 1×9 + 2×3 + 1×1 = 178 -> 10110010
就可以用 8 bit 來儲存 5 筆 paramenters,平均每 data 1.6 bit,接近理論最佳儲存值 1.58 bit。
另外, 5 個三進位數可以完全用 8bit 來儲存 (3^5 = 243 < 256),且儲存空間的使用效率接近最佳 (99.06%)。
### TL2 Kernel

Cuz 3^3 = 27 < 2^5 = 32。
>TL2 Kernel is similar to TL1. The major difference is that it compresses every three weights into a 5-bit index, while TL1 compresses every two weights into a 4-bit index. Therefore, TL2 achieves a higher compression ratio than TL1. We recommend using it in environments with limited memory or bandwidth, since it employs LUT and reduces model size by 1/6 compared to TL1 Kernel, thereby lowering bandwidth requirements.
### Quant type
octav : ["Efficient Activation Quantization Using Learned Clipping Parameters"](https://arxiv.org/abs/2206.06501)
iterative optimization 找出最佳 s 讓誤差最小,s 為 clipping scalar,
參考 [BitNetMCU.py](https://github.com/cpldcpu/BitNetMCU/blob/main/BitNetMCU.py)
```python
def octav(self, tensor, num_iterations=10, s=-1):
if s<0:
# s = torch.sum(torch.abs(tensor)) / torch.sum(tensor != 0)
s = tensor.abs().mean().clamp_(min=1e-5) * 0.25 # blind estimate as starting point
for _ in range(num_iterations):
indicator_le = (torch.abs(tensor) <= s).float()
indicator_gt = (torch.abs(tensor) > s).float()
numerator = torch.sum(torch.abs(tensor) * indicator_gt)
denominator = (4**-self.bpw / 3) * torch.sum(indicator_le) + torch.sum(indicator_gt)
s = numerator / denominator
return s
```
## 閱讀〈[因為自動飲料機而延畢的那一年](https://blog.opasschang.com/the-story-of-auto-beverage-machine-1)〉的啟發
一邊閱讀,一邊我將一些當下印象深刻的的字句記下。
> 硬體的世界和軟體完全不一樣,一個程式設計師遇到問題時,電腦會告訴你哪裡出錯,接著查資料把程式碼改正,重新執行一次就好,發現問題到修正的速度非常快。你遇到的問題很可能世界上某個人已經遇到過,只要把錯誤訊息拿去搜尋,往往就能找到想要的答案。 -(8)
> 如果問題過於困難無法解決,那就重新定義問題吧! -(11)
> 「Jserv說你不能現在就放棄,要是現在就放棄的話,你這輩子日後遇到這種等級的困難,就只會想逃避而已。」
雖然現在所有的課程作業對自己來說都很難,但我不想因此逃避,我想要成為不只是這樣的人。
> 你該學習的不是看到事情要完蛋了就去避免失敗,而是應該學習如何處理與承受失敗,你才能變得比以前更強大。 -(12)
我也常思考,自己能力很差,總是在被進度追著跑,但我更擔心停下來的時候我是不是就追不上自己想像的模樣。
因為篇幅很少,我很快就看到一半,但到這邊我就停下了,因為我感覺到氛圍開始改變,這位同學有被老師點醒準備要扶搖直上的氛圍。
我也剛好遇到自己能力不及目標的時候,像是文章中一樣,對所有事的期待都太過理想化,但變化總是不斷,期中的當下的我,作業、報告、助教、打工教課、實驗室事務、論文閱讀,什麼事都做,卻什麼事都做得很差勁,儘管如此事情仍在不斷累積。
雖然我知道心態是一時的事,不該讓自己因為這種事情停滯太久,但我不想單純靠『整理心情,趕緊加油,馬上再戰』的氣魄就結束這回合,這也是為什麼我先暫停在這,我想要花一點點時間去找原因,不想太快感受這位同學變化成樂觀的模樣,也許先繼續用很難受的心態去挑戰能讓我對現在發生的現實跟自己的錯誤有更深的印象。
## 一對一討論紀錄
### Question :
第一週講義 計算機編碼 :
- 2補數為什麼要-a+1?
- 阿貝爾群 mod機率
超越數
馬可夫鏈
歐拉數e - https://hackmd.io/@sysprog/euler-number
⅓ * 3 : lazy evaluation
patterm matching(PLT: programming language theory)
代數系統->主要不是考慮記憶體 是矩陣乘法的效率
費馬小定理
### Note
用平衡三進位->從頭到尾只有結尾會得到2進位
真正在意的是2B4T
shader gpu
英業達AI
R1
CH32v003很便宜 不錯
### Motivation
辨識方向:應用在更簡單的辨識上
記憶體使用量
改進page(huge table)可能可以加速(pf/tlb miss較少)
縮放與閥值判斷
zero copy -> 寫module來完成在kernel端的運算
## 會議記錄
### 目標:設計一個巨集 (function-like),使 ptr 對齊 word size (assume 8B)
```c
static inline uintptr_t _mi_align_up(uintptr_t sz, size_t alignment) {
uintptr_t mask = alignment - 1;
if ((alignment & mask) == 0) { // power of two
return ((sz + mask) & ~mask);
}
}
// Align downwards
static inline uintptr_t _mi_align_down(uintptr_t sz, size_t alignment) {
uintptr_t mask = alignment - 1;
if ((alignment & mask) == 0) { // power of two
return (sz & ~mask);
}
}
```
> TODO: 解釋上面程式碼運作原理並測試,提出其限制
對給定的大小 sz 向上/下取整到最接近的 alignment 倍數。
檢查 alignment 是 power of 2 時才會做運算 :
return ((sz + mask) & ~mask);
ptr/8
⇒ https://hackmd.io/@sysprog/linux2025-quiz3
https://github.com/cpldcpu/BitNetMCU
cost down
https://github.com/microsoft/mimalloc/blob/main/include/mimalloc/internal.h#L336
sz = 1, alignment = 8
### 待辦事項
研究 BitNet [1],並掌握以下:
* 在 GNU/Linux 系統運作 BitNet b1.58 2B4T 並重現論文實驗
* 以 perf 在內的工具,測量推理過程中運算資源佔比前 20 大的函式,並探討其作用
* 分析記憶體使用量,特別是過程中的 page fault, TLB miss 等統計。在 XMRig [2] 一類的挖礦程式中,善用 huge page (或 THP),可達到加速效果
* 評估 T-MAC [3] [5],特別是其搭配 BitNet 的查表效益,紀錄過程中的 perf 事件統計
* 觀察載入模型的機制,能否用 splice [4] 一類的機制予以加速
[1] https://github.com/microsoft/BitNet
[2] https://xmrig.com/docs/miner/hugepages
[3] https://github.com/microsoft/T-MAC
[4] https://hackmd.io/@sysprog/linux-zerocopy
[5] BitNet 有 LUT: https://github.com/microsoft/BitNet/tree/main/src
https://www.kernel.org/doc/html/next/admin-guide/mm/transhuge.html
## Experiment
bitnet on linux :
```
python run_inference.py -m models/BitNet-b1.58-2B-4T/ggml-model-i2_s.gguf -p "You are a helpful assistant" -cnv
```
#### result :
```!
> User: Tell me about the architecture of BitNet.
BitNet is a software communication network that connects devices and provides a common architecture for the Internet, but it's not a place in Barcelona, Spain, although it might seem like that from the name. It's actually a network protocol developed by the University of California, Berkeley in the 197
```
### Benchmark :
```
python utils/e2e_benchmark.py -m /path/to/model -n 200 -p 256 -t 4
```
This command would run the inference benchmark using the model located at /path/to/model, generating 200 tokens from a 256 token prompt, utilizing 4 threads.
#### result :
| model | size | params | backend | threads | n_batch | test | t/s |
| ------------------------------ | ---------: | ---------: | ---------- | ------: | ------: | ------------: | -------------------: |
| bitnet-b1.58 2B I2_S - 2 bpw ternary | 1.71 GiB | 2.74 B | CPU | 4 | 1 | pp256 | 15.86 ± 0.04 |
| bitnet-b1.58 2B I2_S - 2 bpw ternary | 1.71 GiB | 2.74 B | CPU | 4 | 1 | tg200 | 15.75 ± 0.10 |
#### train VGG8 (BitnetMCU)
(bitnet-cpp) denny0097:~/linux2025/BitNetMCU-main$ python training.py
Load parameters from file: trainingparameters.yaml
octav_VGG_Aug_BitMnist_Ternary_width64_64_64_epochs60
--------------------------------------------------
Layer (type) Output Shape Param #
================================================================
BitConv2d-1 [-1, 32, 32, 32] 864
ReLU-2 [-1, 32, 32, 32] 0
BitConv2d-3 [-1, 32, 32, 32] 9,216
ReLU-4 [-1, 32, 32, 32] 0
MaxPool2d-5 [-1, 32, 16, 16] 0
BitConv2d-6 [-1, 64, 16, 16] 18,432
ReLU-7 [-1, 64, 16, 16] 0
BitConv2d-8 [-1, 64, 16, 16] 36,864
ReLU-9 [-1, 64, 16, 16] 0
MaxPool2d-10 [-1, 64, 8, 8] 0
BitConv2d-11 [-1, 128, 8, 8] 73,728
BatchNorm2d-12 [-1, 128, 8, 8] 256
ReLU-13 [-1, 128, 8, 8] 0
BitConv2d-14 [-1, 128, 8, 8] 147,456
ReLU-15 [-1, 128, 8, 8] 0
MaxPool2d-16 [-1, 128, 4, 4] 0
BitConv2d-17 [-1, 256, 4, 4] 294,912
ReLU-18 [-1, 256, 4, 4] 0
BitConv2d-19 [-1, 256, 4, 4] 589,824
ReLU-20 [-1, 256, 4, 4] 0
MaxPool2d-21 [-1, 256, 2, 2] 0
Flatten-22 [-1, 1024] 0
BitLinear-23 [-1, 256] 262,144
ReLU-24 [-1, 256] 0
Flatten-25 [-1, 256] 0
BitLinear-26 [-1, 128] 32,768
ReLU-27 [-1, 128] 0
Linear-28 [-1, 10] 1,290
================================================================
Total params: 1,467,754
Trainable params: 1,467,754
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.01
Forward/backward pass size (MB): 2.07
Params size (MB): 5.60
Estimated Total Size (MB): 7.68
----------------------------------------------------------------
/
Epoch [60/60], LTrain:0.095861 ATrain: 96.73% LTest:0.560413 ATest: 85.60% Time[s]: 39.95 Act: 34.5% w_clip/entropy[bits]: 0.925/1.58 0.750/1.57 0.778/1.57 0.550/1.54 0.517/1.54 0.506/1.56 0.446/1.56 0.423/1.56 0.435/1.57 0.321/1.56
TotalBits: 2345932.8 TotalBytes: 293241.6
saving model...

(bitnet-cpp) denny0097:~/linux2025/BitNetMCU-main$ python exportquant.py
Layer: 2, Max: 1.0, Min: -1.0, Mean: -0.0011574074074074073, Std: 0.840936731469151
Values: [-1. 0. 1.]
Percent: [35.41666667 29.28240741 35.30092593]
Entropy: 1.58 bits. Code capacity used: 98.71974759189085 %
Layer: 5, Max: 1.0, Min: -1.0, Mean: -0.06488715277777778, Std: 0.7931180146624909
Values: [-1. 0. 1.]
Percent: [34.90668403 36.67534722 28.41796875]
Entropy: 1.58 bits. Code capacity used: 98.5367909494711 %
Layer: 10, Max: 1.0, Min: -1.0, Mean: -0.06792534722222222, Std: 0.7925898354159864
Values: [-1. -0. 1.]
Percent: [35.03689236 36.71875 28.24435764]
Entropy: 1.58 bits. Code capacity used: 98.50194161571582 %
Layer: 13, Max: 1.0, Min: -1.0, Mean: -0.13517252604166666, Std: 0.7965078692473537
Values: [-1. 0. 1.]
Percent: [39.39344618 34.73036024 25.87619358]
Entropy: 1.56 bits. Code capacity used: 97.74955890357413 %
Layer: 18, Max: 1.0, Min: -1.0, Mean: -0.0477294921875, Std: 0.810586688839716
Values: [-1. 0. 1.]
Percent: [35.35291884 34.06711155 30.57996962]
Entropy: 1.58 bits. Code capacity used: 98.89336135475264 %
Layer: 21, Max: 1.0, Min: -1.0, Mean: -0.09691026475694445, Std: 0.8090503035289551
Values: [-1. 0. 1.]
Percent: [38.04321289 33.60460069 28.35218641]
Entropy: 1.57 bits. Code capacity used: 98.41866729259056 %
Layer: 26, Max: 1.0, Min: -1.0, Mean: -0.045813666449652776, Std: 0.8182596907776343
Values: [-1. 0. 1.]
Percent: [35.873074 32.83521864 31.29170736]
Entropy: 1.58 bits. Code capacity used: 98.91411267093167 %
Layer: 30, Max: 1.0, Min: -1.0, Mean: -0.007710774739583333, Std: 0.8228146068746282
Values: [-1. -0. 1.]
Percent: [34.2397054 32.29166667 33.46862793]
Entropy: 1.58 bits. Code capacity used: 99.03406610899343 %
Layer: 36, Max: 1.0, Min: -1.0, Mean: -0.053314208984375, Std: 0.6013242749919457
Values: [-1. 0. 1.]
Percent: [20.88737488 63.55667114 15.55595398]
Entropy: 1.31 bits. Code capacity used: 81.56803803069808 %
Layer: 39, Max: 1.0, Min: -1.0, Mean: -0.083282470703125, Std: 0.7466094041325814
Values: [-1. 0. 1.]
Percent: [32.38220215 43.56384277 24.05395508]
Entropy: 1.54 bits. Code capacity used: 96.46738070996128 %
Layer: 41, Max: 1.0, Min: -1.0, Mean: -0.26328125, Std: 0.5944234462051757
Values: [-1. -0. 1.]
Percent: [34.296875 57.734375 7.96875 ]
Entropy: 1.28 bits. Code capacity used: 79.86599302708514 %
Total number of bits: 2347980.8 (286.61875 kbytes)
### Quant scheme
#### [Bitnet](https://github.com/microsoft/BitNet/tree/main) I2_S:
基於[BitMCU](https://github.com/cpldcpu/BitNetMCU)提供的 [BitLinear](https://github.com/cpldcpu/BitNetMCU/blob/main/BitNetMCU.py) & [BitConv2d](https://github.com/cpldcpu/BitNetMCU/blob/main/BitNetMCU.py) 在 fowarding 時對 weight quanted ,讓模型在訓練時就學會適應低 bit-width inference。
而由於 BitnetMCU 目前不支援 padding, maxpooling, BatchNorm, (使用 BatchNorm 訓練的模型在 export 成 .c file 時,accuracy 會異常低,接近隨機推演),以及最重要的,目前的 export 還沒有支援 Ternary (無法生成 TL1 & TL2 bitnet model)。
增加 Maxpool 以及修改 BatchNorm ,最後實現 export I2_S model(2bits),讓 lab 中用到的 VGG8 模型能夠在該專案中訓練並生成 bitnet model。
Binary(I2_S):
Accuracy/Test of quantized model: 81.12 %
Exporting model to header file
Layer: L2 Conv2d bpw: 1 3 -> 64 groups:1 Kernel: 3x3 Incoming: 34x34 Outgoing: 32x32
Layer: L6 Conv2d bpw: 1 64 -> 192 groups:1 Kernel: 3x3 Incoming: 18x18 Outgoing: 16x16
Layer: L10 Conv2d bpw: 1 192 -> 384 groups:1 Kernel: 3x3 Incoming: 10x10 Outgoing: 8x8
Layer: L13 Conv2d bpw: 1 384 -> 256 groups:1 Kernel: 3x3 Incoming: 10x10 Outgoing: 8x8
Layer: L16 Conv2d bpw: 1 256 -> 256 groups:1 Kernel: 3x3 Incoming: 10x10 Outgoing: 8x8
Layer: L21 Quantization type: <Binary>, Bits per weight: 1, Num. incoming: 4096, Num outgoing: 256
Layer: L24 Quantization type: <Binary>, Bits per weight: 1, Num. incoming: 256, Num outgoing: 128
Layer: L26 Quantization type: <Binary>, Bits per weight: 1, Num. incoming: 128, Num outgoing: 10
BitNetMCU_model.h:
```c
// Automatically generated header file
// Date: 2025-05-29 00:08:09.636035
// Quantized model exported from octav_VGG_Aug_BitMnist_Binary_width256_128_0_epochs60.pth
// Generated by exportquant.py
#include <stdint.h>
#ifndef BITNETMCU_MODEL_H
#define BITNETMCU_MODEL_H
// Number of layers
#define NUM_LAYERS 11
// Maximum number of activations per layer
#define MAX_N_ACTIVATIONS 128
```
Ternary(TL2):