--- title: 2024q1 Homework5 (assessment) --- # 2024q1 Homework5 (assessment) contributed by < `Lisa304` > ## 測驗題改進與提問 [第一周測驗2](https://hackmd.io/@Lisa304/HkcBeTrpa#%E6%B8%AC%E9%A9%97-2) 將 Timsort 整合進入 lab0-c 專案 ## 閱讀〈因為自動飲料機而延畢的那一年〉的啟發 從閱讀〈因為自動飲料機而延畢的那一年〉後,我在課堂和課外的實作以及觀摩其他學員的成果時,深刻理解到系統軟體開發的態度和對細節的重視。 從文章中得到的啟發是,不管是在文章中提到的創業或是撰寫程式,過程中都更需要注重細節和實際問題的解決。不能只停留在理論和構想的層面,而是要將理論應用到實踐中。通過對細節的重視和對系統軟體開發的深入理解,才將能夠更好地應對未來的挑戰和機遇,並將自己的想法變為現實。 一個看似簡單的功能背後有許多細節要注意,讓我想到老師之前上課中提到的,關於網頁如何呈現各種類型檔案類型的功能,其中得要牽涉許多資料類型的轉換。 文章中引用漫畫《鋼之鍊金術師》的對話提出等價交換的概念,以前看漫畫時只覺得裡頭使用等價交換看到真理的人,失去部分的身體的變得很慘,在閱讀完文章後,等價交換感覺變成了鼓勵自己的動力,現在的付出或許辛苦,但相信未來可能會在某些時刻出其不意的幫助到自己。 ## 研讀教材啟發 ### CS/APP 3/e >Computer Systems A Programmer’s Perspective <s>[PDF](https://www.cs.sfu.ca/~ashriram/Courses/CS295/assets/books/CSAPP_2016.pdf)</s> :::danger 不要閱讀未獲授權的副本,去買一本 CS:APP 並詳閱,你會一輩子受用。 倘若你不尊重他人的智慧財產,你有什麼立場要求他人對你勞心勞力的成果去支付合理報酬? ::: - [老師的重點提示](https://hackmd.io/@sysprog/CSAPP/https%3A%2F%2Fhackmd.io%2Fs%2FSJ7V-qikG?type=book) #### 閱讀筆記 **Lecture 01: Course Overview** 數字平方後真的都能得到正數嗎?對於 float 來講的確如此,但是對於 int 來說並不是。 ``` (lldb) print 40000 * 40000 (int) $0 = 1600000000 (lldb) pirnt 50000 * 50000 (int) $1 = -1794967296 ``` int 的範圍是 -2,147,483,648 ~ 2,147,483,647,所以 50000 * 50000 兩數相乘後發生溢位。對於 float 來說, (x+y)+z 有時候不等於 x+(y+z),因為小數點運算時的誤差。 總之,整數操作滿足“環”性質(交換律、結合律、分配律),而浮點數操作滿足“排序”性質(單調性、符號值)。 C 語言在存取記憶體的時候沒有做保護的機制,所以當存取超出了變數的範圍或者未分配的記憶體,操作系統會報錯並產生Segmentation fault(分段錯誤)錯誤,這是一個撰寫程式需要注意的地方。 複製二維陣列的函式,2層迴圈先掃行再掃列,速度竟然比先掃列再掃行來的快兩倍,由此可見當然演算法那些優化方式很重要,但從細微的存取先後,也能得到相差兩倍的執行時間的優化。 --- **Lecture 02: Bits, Bytes, and Integers** >講解資料如何表示以及它們的特性,像是數字運算後的溢位情形。 **2.1** **Representing information as bits** 在現在的數位計算機(使用二進制以及離散訊號)出現以前,類比計算機就已經存在,他使用連續變化的物理量來模擬問題,物理量可以是電壓、壓力之類的,舉例像是電路使用可變電阻器來調節電壓並使用電壓表測量輸出及是一種簡易的類比計算機。 離散表示一個量只能取特定的值,而不能在這些值之間連續變化。換句話說,離散量只能取有限的幾個值或是可數的值。例如,整數就是一種離散量,因為整數只能取 1、2、3 等特定的值,不能取 1.5 或 2.75 等中間值。 當數值夠高時被稱為 'On',反之稱為 'Off',從此出現二進位制表示。在計算中使用二進位制表示數字的動機是因為二進位制表示法允許更密集地存儲數字,因為每個位數只能有兩個可能的值(0或1)。 >As long as we can assign it to a discrete number, we can represent it in binary. 二進位制表示與離散有著密切的關聯,因為二進位制表示是一種將數字表示為離散的位元(bit)序列的方法。在二進位制表示中,每個位元只能取兩個值之一,通常是0或1,這使得二進位制表示成為一種離散的表示方法。 **Bit-level manipulations** > 位元運算 & | ^ ~ 有四個布林代數(Boolean Algebra) and, or , not, exclusive-or(Xor),在程式中對應到四個位元運算子符號 & | ^ ~,注意不要跟邏輯運算符號搞混 &&, ||, !,兩者概念相似但是應用的對象不同。 位元運算子有兩種形式:左移 (<<) 和右移 (>>),對於左移來說都是填補0,但是對於右移來說有分成兩種情況,邏輯移位的話補 0,而算術移位的話是補上最高有效位(最左邊的位元)。另外如果位移的量小於零或是大於等於 word size,那麼就是未定義行為。 "word size"是指計算機處理器能夠一次處理的位元組數或字節數。這個詞通常用來描述計算機中整數和指針的大小。例如,在32位系統中,一個字(word)通常是32位,而在64位系統中,一個字通常是64位。 **2.2 Integers** >Representation: unsigned and signed 要使用到 signed 就一定跟二補數有關,最高有效位元設為 1 時代表為負數,正數變成負數的話,就是全部的 0 變成 1, 1 變成 0,最後加上 1 就完成正轉負了。 以 16 位元來說,UMax 是 11111111 11111111 (65535),而 TMax 是 01111111 11111111(32767),TMin 是 10000000 00000000(-32768)。如果只有三位元,那麼要表示數字 8 的話,會出現溢位情形,變成 000。 >Conversion, casting T2U 是指二補數的 singed integer 轉換成 unsinged integer,兩者在做比大小的時候有時候會出現出乎意料的情況,需要注意。舉例 -1 > 0U,因為 -1 是 1111 而 0U 是 0000。2147483647 > -2147483647-1 很理所當然,正數必定大於負數,然而 2147483647U < -2147483647-1 因為 TMax 會小於 TMin。 >Expanding, truncating 關於標誌的擴展,目的是往位元的左邊繼續加上數字,只要加上跟最高有效位元一樣的 0 或 1 就完全不會更動到數字的值。 關於截斷,目的是截斷左邊的位元,對於四位元的 unsigned 就是直接做 mod 16,而對於四位元的 singed 也是做類似於 mod 的事情,10 變成 -6, -10 會變成 6。 當發生溢位時,我們會取 word size 位元的數量,這也是一種「截斷」。 --- **Lecture 03: Bits, Bytes, and Integers (cont.)** **2.3 Addition, negation, multiplication, shifting** >Addition 每種運算都要討論對於 singed 和 unsinged 兩個部份,先說關於 unsinged,跟普通加法一樣,不過相加為2就進位制,因為是二進制的,要特別注意的是 true sum 也就是相加後位元數量是 w+1 的情況,如果是 w=4 四位元的運算,那麼當數值大於 16 即為 true sum,舉例像是 16 + 1 = 1111 + 0001 = 1 0001(17),運算結果是 17 發生溢位,會捨棄最高位變成 0001,所以 16 + 1 在四位元運算的結果實際上為 1。 所以 **unsinged 相加實際上是相加後,再取餘數 16**, 17 % 16 = 1。 對於 singed 的相加,要探討二補數運算,現在以四位元運算舉例,如果和的範圍在 -8~7 之間,那麼就算結果發生溢位直接截斷也不會影響數字,但是要是和小於 -8,那會發生 negitive overflow 負數溢位,直接被加上 16,舉例 -6 + -3 = 1010 + 1101 = 10111,負數溢位截斷後為 0111,變成 7,所以 -6 + -3 = 7,而另溢編如果和超過 7,則會發生正數溢位,舉例 7 + 5 = 0111 + 0101 = 1100 = -4,正數溢位截斷後 7 + 5 = -4。 >multiplication 當我們將兩個數字相乘時,產生的結果可能需要更多的位元才能準確表示。最壞情況下,乘法的結果可能需要多達原始位元數的兩倍的位元來表示。但我們的硬體是有限制的,所以遇到溢位情況時,會保留 word size bit。 現在以四位元 (w = 4) 運算來觀察: - unsigned 無號數:很容易發生溢位,乘法的結果最大為 15×15=225,二進制表示為 11100001,共 8 位元。 - singed 有號數:舉例 1000 * 1000 = 64 = 100 0000,為七位元。 >shifting 對於乘法 : u << k = u*2^k,將位元向左移 k 位,表示 u 乘上 k 的 2 次方,對於 singend 和 unsigned 而言都是相同。往左位移都是填補零。 (u << 5) – (u << 3) == u * 24 和 u << 3 == u * 8,對於大多數的機器位移和相加的運算速度快於乘法。 對於除法 : u >> k = u / 2k 取 floor,右移的時候都是做算術移位,補上最高有效位(最左邊的位元),這樣才能保持正負號。 Round-toward-0 divide 是一種除法運算方法,其中將負數除以 2 的冪時,希望結果朝著 0 方向舍入。 具體步驟如下: 1. 首先,將被除數 x 與( 2 的 k 次方 - 1)相加。這個步驟的目的是為了對被除數做一個偏移,以便在進行右移操作後能夠達到期望的結果。 2. 接著,將結果進行右移 k 位。這個右移操作相當於將加法步驟中的偏移量除以 2 的 k 次方,從而實現對被除數的除法操作。 在C語言中,可以使用如下的程式碼來實現這種除法運算: ```cpp (x + (1 << k) - 1) >> k ``` :::danger 注意書寫規範,中英文之間用一個半形空白字元區隔。 ::: 在使用 unsigned 時要注意的他的特性,以下的程式碼逐一存取陣列 `a` 元素,但程式會因為 unsigned 沒有負數存在的特性,對於 i = 0 時進行減法,會溢位變成更大的正數,而陷入無窮迴圈當中。第二段程式因為 DELTA 是 sizeof 函數的返回值其資料型別為 unsigned,而後面迴圈的條件式跟 0 比較,會出現相同的問題。 ```cpp unsigned i; for(i = cnt-2; i >= 0; i--) a[i] += a[i+1]; ``` ```cpp #define DELTA sizeof(int) int i; for(i = CNT; i - DELTA >= 0; i -= DELTA) ``` 下方寫法可以正確地使用 unsigned 來計數,將 i 的資料型態換成 size_t, size_t 是長度為 word size 的 unsigned 值,這樣即使 cnt = UMax 也不會出錯,而條件式改成 `i < cnt`,才不會出現無窮迴圈: ```cpp size_t i; for(i = cnt-2; i < cnt; i--) a[i] += a[i+1]; ``` 雖然 unsigned 在運算上不太直觀,在 Java 標準的使用中甚至不使用此資料型別,但是無號數在某些使用情境下還是很有用處的,比如說與硬體設備或底層系統進行交互時,通常需要使用無符號整數來確保正確的數值表示。 **2.1.3 Byte Ordering** 系統會使用記憶體保護機制來確保程式只能存取允許的記憶體範圍。如果程式嘗試存取超出其允許範圍的記憶體,操作系統通常會觸發一個錯誤,稱為 segmentation fault 或 segfault。這種錯誤通常表示程式正在嘗試訪問未分配給它的記憶體,或者正在嘗試訪問已經被釋放或無效的記憶體地址。透過這種機制,操作系統可以保護系統的穩定性和安全性,防止程式意外地干擾或破壞其他程式或操作系統本身。 現在主流系統像是 IOS, Linux 和 Windows 都是使用 Little Endian,數值的最低有效位(Least Significant Bit,LSB),如果以四 byte 資料 0x1234567 舉例,那 LSB 是最右邊的 67,存儲在記憶體的最低地址。 #### 閱讀心得 這次課程讓我重新思考了許多,過往在程式設計中自己知道但沒有到了解詳細過程的概念。重新確認了數字在電腦中的表示方式以及位元運算的原理,這些對於我理解程式的運作方式至關重要。 課程中對於整數表示和運算的討論,讓我再次思考了 signed 和 unsigned 之間的差異,以及如何避免溢位和處理溢位的情況。在我以前的程式寫作中,有時候可能沒有充分考慮到這些問題,但現在我意識到它們對於程式的正確性和效能有著重大的影響。 此外,在討論位元運算和移位運算時,我也重新思考了它們在實際程式設計中的應用。儘管我在以前的項目中已經使用過這些操作,但透過這次閱讀筆記,我能夠更深入地了解它們的效率和適用性,並且將這些知識應用到未來的程式開發中。 最後,在討論記憶體保護機制和 byte ordering 時,我更加清楚地了解了在系統程式設計中的安全性和效率性的重要性。這些概念對於我在開發複雜系統時的決策和設計具有重要意義,或許對於我未來的工作會有很大的幫助。 總的來說,這次的學習讓我對程式開發中的一些關鍵技術和最佳實踐有了更深入的理解,並且提高了我的技術水平和自信心。這將使我能夠更好地應對未來的挑戰,並寫出更高品質<s>質量</s> 的程式碼。 :::danger 「[質量](https://dict.revised.moe.edu.tw/dictView.jsp?ID=113680)」釋義:「物體內所含物質的量。質量不因所在位置而改變。」 ::: ## 簡述想投入的專案 後來想想是希望能夠跟自己實驗室研究方向結合,也看到有人被指派關於開發加速 LLaMA 的 Linux 核心模組。 LLaMA 也應該是實驗室再來都會要求要使用進論文的模型,不管是題目要基於 LLaMA 去做使用,還是最後的評估分數該要有 LLaMA 當作基線模型去做對照組,所以認為這個題目很不錯,想要嘗試。 --- 另外瀏覽去年的專題題目後,最感興趣的是網頁伺服器、以及其相關的防火牆題目 ### 高效網頁伺服器 >探討從無到有打造 Linux 平台的高效能網頁伺服器,涵蓋是否該將伺服器實作於 Linux 核心內部、並行處理、I/O 模型、epoll、Reactor pattern,和 Web 伺服器在事件驅動架構的考量。 - 2023 執行人: SPFishcool → [開發紀錄](https://hackmd.io/@sysprog/HJgX4_MH3) - 2023 執行人: JoshuaLee0321 → [開發紀錄](https://hackmd.io/@sysprog/SJyZrfnSh) ### 以 Linux XDP 為基礎的防火牆 > XDP (eXpress Data Path) 自 Linux 核心 4.8 版本起作為以 eBPF 為基礎的高效資料處理路徑,一旦網路中斷觸發後,XDP 允許將特定的操作提前在 TCP/IP 堆疊之前就處理,不僅反應更快而且省下寶貴的記憶體分配的成本。本議程以 XDP 作為切入,探討如何在這之上發展高效網路負載平衡器,並針對典型的 High Availability (HA) 叢集和非典型的情境去調整。 - 2023 執行人: jhin1228, D4nnyLee → [開發紀錄](https://hackmd.io/@sysprog/ryvKMFgr2) --- # Linux 核心專題: LLaMA 推論之效能議題 > 執行人: Lisa304 > [專題解說錄影](https://youtu.be/zCnMppLCh4s) ## TODO: 實作程式碼 IEEE 754 浮點數乘以 2 >IEEE 二進位制浮點數算術標準(IEEE 754)是 20 世紀 80 年代以來最廣泛使用的浮點數運算標準,為許多 CPU 與浮點運算器所採用。這個標準定義了表示浮點數的格式(包括負零-0)與反常值(denormal number),一些特殊數值((無窮(Inf)與非數值(NaN)),以及這些數值的「浮點數運算子」;它也指明了四種數值修約規則和五種例外狀況(包括例外發生的時機與處理方式)。 --wikipedia 有五個步驟把十進位制的小數轉成 IEEE 754 二進位制浮點數算術標準: ![image](https://hackmd.io/_uploads/HyMcN1om0.png) 1. 對於正數開頭為 0,負數為 1,這點跟二補數是相同的。 2. 將十進位制先轉成二進位制 - 後續才會在轉成 IEEE 754 標準的福點數表達方式。先做整數部分一直除以 2,直到商為零。再做小數部分,不斷乘 2 直到小數點後為 0,計算時可以不斷捨去整數部分,有可能會出現循環小數的情形。 --- ![image](https://hackmd.io/_uploads/ByFAcysXR.png) - [圖片來源](https://www.youtube.com/watch?v=RuKkePyo9zk) 3. 正規化 Mantissa (尾數) - 簡單說就是將小數點移到一個 1 後面 - 舉例: $0.00011_2 = 1.1_{2} * 2^{-4}$ - 舉例: $1111.1001_2 = 1.1111001_{2} * 2^3$ 4. 決定偏差後的指數部分 - IEEE 754 是使用 bias representation 所以要來處理偏差的部分。 - Exponent 有 8 bits,能夠顯示的範圍是 -127~128,因為位元排列一開始由負數佔據,所以正數要轉換成二進位制時,要先加上 127(offset of the bias),下面表格以 4 位元舉例: - 在偏差表示的情況下,數字 7 想要轉成 2 進位制表示,要先加上偏差 8 得到 15,對應到的二進位制是 1111。 | Actual Number | Biased Number | Biased Representaion | | ------------- | ------------- | -------------------- | | -8 | 0 | 0000 | | -7 | 1 | 0001 | | 0 | 8 | 1000 | | 1 | 9 | 1001 | | ==7== | ==15== | ==1111== | - 也可以參考這個[教學](https://www.youtube.com/watch?v=e_J9lXnU_vs) 5. 將方才判斷出的三個部分連起來 - 如果是循環小數的情況,會決定是要做 round up 還是 round down - round up: 直接把下一位加上去。 IEEE 754. Assume float is 32-bit width ```c #include <stdio.h> #include <stdint.h> #include <string.h> float float_mul2(float x) { // using bitwise operation. No mul/div uint32_t newX; memcpy(&newX, &x, sizeof(float)); uint32_t sign = newX & 0x80000000; int32_t exponent = newX & 0x7F800000; int32_t mantissa = newX & 0x007FFFFF; exponent += 0x00800000; newX = sign | exponent | mantissa; float result; memcpy(&result, &newX, sizeof(float)); return result; } ``` 1. 先把十進位制轉成 IEEE 表達 - memcpy 將浮點數的二進位制表示複製到 uint32_t 類型的變數。 - memcpy 從 <string.h> 引入,uint32_t 從 <stdint.h> 引入。 2. 分成三個部分 - sign bit: newX & 1000 0000 0000 0000 0000 0000 0000 0000 - (0x80000000) - exponent bits : newX & 0111 1111 1000 0000 0000 0000 0000 - (0x7F800000) - mantissa: newX & 0000 0000 0111 1111 1111 1111 1111 1111 - (0x007FFFFF) 3. 指數部分加一,因為要將數值乘二 - exponent += 0x00800000; 4. 然後把三個部分再合起來 - newX = sign | exponent | mantissa; ### 使用 union 改進程式碼 > 老師建議參考[教材](https://hackmd.io/@sysprog/constant-time-relu),利用 union 簡化程式 - union 跟 struct 語法相似,但是它們最大的差別是 union 中各個變數是共用記憶體的。 - union 的大小會以最大的為準,如果有兩個屬性 int(4 byte) 和 double(8 byte),那這個 union 就會是 8 byte。 ```c union{ uint32_t i; float f; }out={.f = x}; uint32_t sign = out.i & 0x80000000; uint32_t exponent = out.i & 0x7F800000; uint32_t mantissa = out.i & 0x007FFFFF; exponent += 0x00800000; return sign | exponent | mantissa; ``` 1. 在程式碼中加入 union 結構 out - 因為記憶體共用特性,可以省去 mencpy 的動作。 - 可以方便地==在浮點數和整數表示之間切換。== 2. 修改切出 `sign`, `exponent` 和 `mantissa` 的部分 - newX 要換成 out.i 對於 uint32_t 型態作 bitwise 擷取部分出來 --- ## 閱讀 LLaMA Now Goes Faster on CPUs >TODO: 閱讀 https://justine.lol/matmul/ 並紀錄問題,定位出效能瓶頸 作者為 llamafile 開發了 84 new matrix multiplication kernels,旨在顯著提高在各類型的 CPU 上讀取提示/圖像的速度。在 CPU 上使用 F16 和 Q8_0 的權重時,相較於 llama.cpp,在指令計算時間上將提升 30%~500% 的速度。 F16 (FP16) 指的是 16 位浮點表示。與傳統的 32 位浮點數 (FP32) 相比,它是一種更緊湊的表示方式,允許更快的計算和減少記憶體使用。Q8_0 指的是 8 位量化權重,模型權重使用 8 位表示。量化有助於減小模型大小並提高推理速度,同時保持合理的精度。 >模型量化(quantization)是將模型從原本高精度資料格式轉換為低精度格式儲存的一種模型最佳化(model optimization)手段。目前在電腦上訓練模型時使用的資料格式多為32位元單精度浮點數(32-bit single-precision floating-point, FP32),訓練完畢後經過模型量化轉換為8位元整數(8-bit integer, INT8)格式儲存。 > >量化主要包含兩個步驟: > >1. 選定準備進行量化的實數範圍(FP32),範圍外的數字clip到上限或下限; >2. 將實數對應到低位元代表的整數(INT8)。 > > ----[模型量化:轉換為低精度格式提升運算效能](https://medium.com/@lee.kevin.lin/%E6%A8%A1%E5%9E%8B%E9%87%8F%E5%8C%96-quantization-%E8%BD%89%E6%8F%9B%E7%82%BA%E4%BD%8E%E7%B2%BE%E5%BA%A6%E6%A0%BC%E5%BC%8F%E6%8F%90%E5%8D%87%E9%81%8B%E7%AE%97%E6%95%88%E8%83%BD-6af462fba0b1) > The improvements are most dramatic for ARMv8.2+ (e.g. RPI 5), Intel (e.g. Alderlake), and AVX512 (e.g. Zen 4) computers. - RPI 5 樹莓派的第五代是 ARM 架構的 v8.2 版本開始及更新版本 - Alderlake 是 Intel 第 12 代處理器的代號 - AVX512(Advanced Vector Extensions 512) 是 Intel 為 x86 架構引入的一組指令集擴展。它允許處理寬向量(512 位),顯著提高了涉及大規模計算任務的性能,如科學模擬、金融分析和機器學習工作負載。 - Zen 4 是 AMD 的微架構代號,用於其 Ryzen 和 EPYC 處理器。Zen 4 引入了性能、功耗效率的各種改進,並支持新的指令集如 AVX512。 作者的核心使用 Level 2 Cache 時比 MKL(Math Kernel Library) 快兩倍,如果任務是少於 1000 個 token 的話,能提供可觀的加速。 **Background** llamafile 是作者在 2023.11 在 Mozilla 啟動的本地 LLM 專案。他們使用 Cosmopolitan Libc 來把 llama.cpp 打包成單檔跨平台二進制文件,可在 [AMD64(x86-64)](https://zh.wikipedia.org/wiki/X86-64) 和 ARM64 的六種作業系統上運行,同時對其進行少量修改。 llamafile 和 Cosmopolitan Libc 都是由 Mozilla 贊助作者的。 >Mozilla是一個自由軟體社群,由網景通訊公司的成員於1998年創立。在非正式的場合下,「Mozilla」這個名字常用於不同的事物上。這些事物大都與現已歇業的網景通訊公司及其旗下的應用軟體相關。 > >名字由來: Mosaic + Godzilla + Killa = Mozilla (哥吉拉殺死當時世界第一的網頁瀏覽器 Mosaic) > >---- 維基百科 **Performance Gains on Enterprise Hardware** >HP Intel® Core™ i9-9900 ($439) w/ 2200 MT/s RAM c. 2020 引入了 `mmap()` 支持,這使得權重加載變得即時,並且內存佔用減少了一半,但對於評估速度的提升幫助不大。後來作者通過優化內核,實現了更大的性能提升,特別是在特定數據類型(如 q8_0 和 f16)上。 在這裡,我們看到在 Skylake 平台上(硬體部分),軟體部分 llamafile 的用戶可以期待看到 2 倍的速度提升,而 llama.cpp 用戶則可以期待性能提升 50%,表格中同樣是權重 4 量化格式的時候,根據軟體部分個更改,指令處理速度從 12 上升到 28。 但是這只適用於某些權重。到目前為止,作者只為 q8_0、f16、q4_1、q4_0 和 f32 這些數據類型編寫了優化的內核。認為 q8_0 和 f16 是非常穩健的選擇,他們在參數量提升的同時,指令處理速度竟然也有提升 205 和 171。如果你有足夠的內存,可能 f32 也不錯。 | prompt tok/sec | eval tok/sec | model | weights data type | software | | -------------- | ------------ | -------------- | ----------------- | -------------------- | | ==28== | 7 | Mistral 7b | q4_0 | llamafile-0.7 | | 17 | 7 | Mistral 7b | q4_0 | llama.cpp 2024-03-26 | | ==12== | 7 | Mistral 7b | q4_0 | llamafile-0.6.2 | | 32 | 4 | Mistral 7b | q8_0 | llamafile-0.7 | | 23 | 2 | Mistral 7b | f16 | llamafile-0.7 | | ==205== | 26 | TinyLlama 1.1B | q8_0 | llamafile-0.7 | | ==171== | 15 | TinyLlama 1.1B | f16 | llamafile-0.7 | >That's because my new kernels change the rules. They're doing such a good job fixing the memory bandwidth quants always solved, that quantization could become the bigger bottleck.That would be great news for the future of local language models, since it means less need to trade away knowledge for speed. 作者的新方法能夠解決 memory bandwidth quants 的問題,但是在量化上是一個更大的瓶頸。這對於本地語言模型的未來是個好消息,因為這意味著減少了為了速度而犧牲知識的必要性。 memory bandwidth quants 內存帶寬問題指的是處理器從內存中讀取或向內存寫入數據的速度受限,內存帶寬不足可能導致處理器等待數據的時間增加,對於 LLM 頻繁訪問大量數據時,此問題會很明顯。 在語言模型中,量化是一種常見技術,用於減少模型的內存和計算需求。量化通過將浮點數據轉換為較低精度的數據格式(如 8 位或 4 位)來實現。儘管這可以顯著減少模型的內存佔用和計算負擔,但也可能導致模型性能下降,即犧牲了一部分模型的“知識性”,因為量化過程中可能會丟失一些細節信息。 :::info Q: 解決了內存帶寬問題,不是就不需要進行量化了嗎?這也比較能跟下一句說,本地 LM 不再需要為了速度犧牲知識性能接上,為什麼內文說解決內存帶寬問題,會導致量化成為瓶頸呢? ::: **Performance Gains on Hobbyist Hardware** >$100 Raspberry Pi v5 (ARMv8.2) and v4 (ARMv8.0) Raspberry Pi 是當今商店中最好的個人電腦之一。它們以優惠的價格提供良好的性能,並且功耗極低。 表格是 100 美元的 Raspberry Pi v5 (ARMv8.2) 和 v4 (ARMv8.0) 上的表現: | prompt tok/sec | eval tok/sec | model | weights data type | hardware | software | | -------------- | ------------ | -------------- | ----------------- | -------- | ------------- | | ==62== | 5 | TinyLlama 1.1B | f16 | RPI5 | llamafile-0.7 | | 45 | 9 | TinyLlama 1.1B | q8_0 | RPI5 | llamafile-0.7 | | 10 | 3 | TinyLlama 1.1B | q8_0 | RPI4 | llamafile-0.7 | | ==3== | 2 | TinyLlama 1.1B | f16 | RPI4 | llamafile-0.7 | Raspberry Pi 幾個月前發布了他們的第五代產品,相較於之前的型號速度快得令人難以置信。他們還引入了對 ARMv8.2 dotprod 和 fp16 算術 ISA(指令集架構)的支持,這對於大規模語言模型(LLM)非常有用。僅這兩個特性就讓 llama.cpp 在去年針對 f16 權重實現了 10 倍的性能提升。 本週我在此基礎上又實現了額外的 2 倍性能提升,所以總共是提升了二十倍(3 => 62),使用的是原本打算用於 AVX512 (指令擴展集,從 256-bit 擴展到 512-bit)的內核。強大的數據中心設備設計的內核能在微小的 Raspberry Pi 上工作,因為兩者的 CPU 都有 32 個向量寄存器(32 vector registers)。 新 ARMv8.2 fp16 ISA 可能會==引入更多的誤差==,因為它使得 llamafile 使用 fp16 字(fp16 words),並且我們沒有使用 ==Kahan 求和==來計算點積。fp16(16 位浮點數)因為精度較低,容易在累加大量數據時產生較大的數值誤差。 >Kahan 求和演算法,又稱為補償求和或進位制求和演算法,是一個用來降低有限精度浮點數序列累加值誤差的演算法。 > >在電腦程式中,我們需要用有限位數對實數做近似表示,如今的大多數電腦都使用 IEEE-754 規定的浮點數來作為這個近似表示。對於 $\frac{1}{3}$,由於我們無法在有限位數內對它進行精準表示,因此在使用 IEEE-754 表示法時,必須四捨五入一部分數值(truncate)。這種舍入誤差(Rounding off error)是浮點計算的特徵。 > >---- [Kahan summation](https://oi-wiki.org/misc/kahan-summation/) :::spoiler ```c= void kahan_sum(float *sum, float input[], int n) { float c = 0.0; // 補償誤差 for (int i = 0; i < n; i++) { float y = input[i] - c; // 去掉誤差部分的輸入值 float t = *sum + y; // 可能存在浮點誤差 c = (t - *sum) - y; // 更新補償誤差 *sum = t; // 更新累計和 } } float kahan_dot_product(float *a, float *b, int n) { float sum = 0.0; for (int i = 0; i < n; i++) { a[i] *= b[i]; // 計算每個元素的乘積 } kahan_sum(&sum, a, n); // 對乘積應用 Kahan 求和 return sum; } ``` ::: 因此,Q8_0 (8位整數量化)權重實際上會有略好一些的困惑度,因為它使用的是 dotprod ISA,這使我們==能將有符號的 8 位整數升到 32 位的計算類型==,從而吸收誤差。這意味著在某些情況下,使用 Q8_0 權重可以得到比 fp16 權重更準確的結果。然而,這並不意味著更快的 fp16 權重不能有用。許多這個領域的開發者認為這些差異是微不足道的。 例如,假設在 pihole 上設置一個郵件伺服器並讓 TinyLLaMA 過濾垃圾郵件。可以配置 Postfix 來使用 shell 腳本來過濾內容,透過運行 llamafile 命令,詳細的內容請看[原文 hobbyist 段落](https://justine.lol/matmul/#hobbyist)。 **Performance Gains on Gaming Hardware** > Intel® Core™ i9-14900K ($530) w/ 6400 MT/s RAM - 遊戲硬體的高品質對機器學習產業有很大幫助,因為其高效能可以加速模型的訓練和推理。 | prompt tok/sec | eval tok/sec | model | weights data type | hardware | software | | -------------- | ------------ | -------------- | ----------------- | --------- | ------------- | | 406 | 67 | TinyLlama 1.1B | q8_0 | Alderlake | llamafile-0.7 | | 407 | 42 | TinyLlama 1.1B | f16 | Alderlake | llamafile-0.7 | - Alderlake處理器具有強大的性能,尤其在浮點計算方面有顯著提升。 - 性能核心和效率核心的設計使得高性能計算和日常使用可以更好地平衡。 - 編譯速度的提升表明新硬體在處理大型代碼庫時具有極高的效能。 - 通過使用液態金屬和 AI 超頻技術,這款電腦的編譯速度從 35 秒進一步提升至 20 秒,而原本的 HP 需要 64 秒。 - AVX512指令集的未來改進有望進一步提升計算速度和效能。 **Performance Gains on Apple Hardware** >Mac Studio CPU w/ 24-core M2 Ultra ($5000) - 作者選擇使用 Stallman 的編譯器(如 GCC),而不是 Apple 的專有編譯工具(如 Xcode 和 Clang)。這可能帶來一定的劣勢,因為 Apple 的工具更能夠針對其硬體進行最佳化。 | prompt tok/sec | eval tok/sec | model | weights data type | hardware | software | | -------------- | ------------ | -------------- | ----------------- | --------- | ------------- | | 419 | 66 | TinyLlama 1.1B | q8_0 | M2 Ultra | llamafile-0.7 | | 457 | 95 | TinyLlama 1.1B | f16 | M2 Ultra | llamafile-0.7 | - Apple 的 M2 Ultra 處理器使用了一種稱為==垂直整合==的技術,即將 RAM DIMMs 嵌入到 CPU 內部。這樣做的好處是大大降低了延遲,因為 CPU 不再需要進行遠距離的數據傳輸。 - **作業系統的穩定性**對於保持桌面環境的可靠運行至關重要,macOS和Windows都能很好地保護系統不受用戶不當操作的影響。 - Asahi Linux 提供了一種在 Apple Silicon 上運行 Linux 的解決方案,能夠更好地利用 M2 的硬體特性。 **Performance Gains on Professional Hardware** >AMD Ryzen Threadripper PRO 7995WX w/ 96 cores ($10,000) | prompt tok/sec | eval tok/sec | model | weights data type | hardware | software | | -------------- | ------------ | -------------- | ----------------- | --------- | ------------- | | 1268 | 60 | TinyLlama 1.1B | q8_0 | 7995WX | llamafile-0.7 | | 1819 | 52 | TinyLlama 1.1B | f16 | M7995WX | llamafile-0.7 | - AMD Ryzen Threadripper PRO 7995WX:高性能和高價格並存,是目前市場上最強大的CPU之一,特別適合需要大量計算能力的工作負載。 - 這款 CPU 的 384MB L3 快取有助於提高 token 生成速度 - AVX512指令集:更大向量通助於減少舍入誤差,在處理大量數據時有顯著優勢,對於高性能計算至關重要。 - VDPBF16PS 指令能夠將 bfloat16 進行類似於 VNNI 和 ARM dotprod 的點積運算。 - 對於使用 bfloat16 作為權重的模型(如 Mistral 和 TinyLLaMA),有原生的 bfloat16 支持非常有利。 - bfloat16支持:bfloat16(bf16)能表示更多的數字範圍,而不會像 float16(fp16)那樣僅能準確表示 13% 的可能數字。 - RAM和磁碟性能:在選擇硬件時,需要考慮到不同配置的性能差異和可能的問題,以保證系統穩定和高效運行。 :::info 在 Intel 軟體方面都是較新版本的 llamafile-0.7(發布在 03-31) 表現較好,對於 Mac(108) 在 llama.cpp 2024-03-26 表現最好,而 7995WX(93) 在 llamafile-0.6.2 表現得最好,三種硬體的最佳表現不是都發生在最新版本,會是因為什麼原因? ::: --- ### Technical Details **How I Improved Prompt Eval Time on CPUs** >矩陣乘法是 transformer 模型中的基礎運算之一,通常消耗大量的計算資源。作者研究各種實作方式,做性能優化,並探索不同實現中的權衡。 - Python 0.042 GFLOPS => 使用 NumPy 會更好 - NumPy(基於 FORTRAN)的實現 29 GFLOPS - C 跟 python 的編譯方式差異很大,導致 C++ 的執行速度快許多,47 GFLOPS - C++ 加上 BLAS 庫,在多線程模式下可達到約 85 GFLOPS。而其中最強大的是英特爾的數學內核庫 (MKL),其運算速度為 384 gigaflops。 - 提供的 SLLMM4 函數展示了如何利用指令級並行性和高效的記憶體訪問模式來顯著加速矩陣-矩陣乘法。 **How I Got Multiple Threads to Work** >確保矩陣乘法的高效計算,同時與 Llama.cpp 的線程模型兼容,避免了傳統 BLAS 庫帶來的延遲問題。 - 定義了一個 GEMMER 類來實現矩陣乘法,該類負責處理矩陣的不同部分 - 提供了一個外部接口函數 SLLMMT,可以被 GGML 調用 ### Methodology 在 Linux 上執行以下命令才能可靠地對 llamafile 進行基準測試。 ``` echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor ``` 作者有像 BLIS 那樣用彙編語言編寫內核,配置了 Emacs 可以把組合語言直接反編譯成 C++ 程式碼。 --- ## 實作 > TODO: 執行 https://github.com/jart/matmul (針對 x86-64 和 [Intel MKL](https://www.intel.com/content/www/us/en/developer/tools/oneapi/onemkl.html)) > 所有專案都該確保在 Linux v6.8+ 運作 (搭配 Ubuntu Linux 24.04),[升級步驟](https://www.ecscoupon.com/5735.html) TODO: 目標是理解 LLaMA 推理過程中的各式效能議題,予以量化,並針對現有的效能改進方案進行分析 ### 開發環境 ```bash $ gcc --version gcc (Ubuntu 13.2.0-23ubuntu4) 13.2.0 Copyright (C) 2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ lscpu 架構: x86_64 CPU 作業模式: 32-bit, 64-bit Address sizes: 46 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 48 On-line CPU(s) list: 0-47 供應商識別號: GenuineIntel Model name: Intel(R) Xeon(R) CPU E5-2650 v4 @ 2.20GHz CPU 家族: 6 型號: 79 每核心執行緒數: 2 每通訊端核心數: 12 Socket(s): 2 製程: 1 CPU max MHz: 2900.0000 CPU min MHz: 1200.0000 BogoMIPS: 4390.21 ``` >Intel(R) Xeon(R) CPU E5-2650 v4 @ 2.20GHz | Launch Date | Total Cores | Max Turbo Frequency | Processor Base Frequency | Cache | TDP | | -------- | -------- | -------- | --- | --- | --- | | Q1'16 | 12 | 2.90 GHz | 2.20 GHz | 30 MB Intel® Smart Cache | 105 W | ### 執行 llamafile (針對 x86-64) - llamafile 結合了 llama.cpp 與 Cosmopolitan Libc,讓 LLM 可以在自己的電腦上執行的專案,為檔案更改權限後執行 `./llava-v1.5-7b-q4.llamafile` 的畫面: - 這是一個範例,是為 LLaVA 模型建立的 llamafile![image](https://hackmd.io/_uploads/SkgBAvOrR.png) - 將 llamafile 與外部權重一起使用 - 首先在 Mozilla-Ocho 的 [release page](https://github.com/Mozilla-Ocho/llamafile/releases) 下載沒有任何權重設置的 llamafile。 - 並且在 huggingface 找到想要使用的模型以及權重的網址(會以 .gguf 結尾),在檔案列表的下載圖案上,按右鍵複製鏈結即可。 - 這是模型 [Mistral-7B-v0.1](https://huggingface.co/TheBloke/Mistral-7B-v0.1-GGUF/tree/main) 的網址 - 最後下指令: ``` curl -L -o llamafile.exe https://github.com/Mozilla-Ocho/llamafile/releases/download/0.8/llamafile-0.8 curl -L -o mistral.gguf https://huggingface.co/TheBloke/Mistral-7B-v0.1-GGUF/resolve/main/mistral-7b-v0.1.Q4_0.gguf?download=true ./llamafile.exe -m mistral.gguf ``` - 使用 GPU 的話指令是 `./llamafile.exe -m mistral.gguf -ngl 99` - 從 [GGUF](https://huggingface.co/docs/hub/gguf) 的網頁擷取量化權重的對照表格 | Quantization type | description | | ----------------- | ------------------------------------------------------------------- | | Q4_0 | 4-bit round-to-nearest quantization (q). Each block has 32 weights. | | Q4_K | 4-bit quantization(q). Super-blocks with 8 blocks, each block has 32 weights. | | Q8_0 | 8-bit round-to-nearest quantization (q). Each block has 32 weights. | | F16 (FP16) | 16-bit standard IEEE 754 half-precision floating-point number. | - Q4_0 使用四位元(4-bit)來表示權重,這意味著每個權重值可以用16個不同的數值來表示。 - 採用 group-wise 量化技術,這意味著權重被分成了許多小的組(block),每個組的大小(blocksize)為32。 - 雖然 Q4_0 曾經被使用,但目前社群已經較少使用這種方法,轉而採用更新、更高效的量化技術。 - Q4_K 方法也不如現代量化技術普及,但它比 Q4_0 具有較高的精度。 - 這樣用指令執行模型,要怎麼評估 prompt/sec? - 使用 Bash 試試看: ```bash #!/bin/bash MODEL_FILE="mistral.gguf" EXECUTABLE="./llamafile.exe" PROMPT="The following is a conversation between a Researcher and their helpful AI assistant Digital Athena which is a large language model trained on the sum of human knowledge.Researcher: Good morning.Digital Athena: How can I help you today? Researcher:'" # 紀錄開始時間 START_TIME=$(date +%s.%N) # 執行模型並計算總的 token 數量 TOTAL_PROMPTS=1 echo "$PROMPT" | $EXECUTABLE -m $MODEL_FILE -e -p $PROMPT # 紀錄結束時間 END_TIME=$(date +%s.%N) # 計算經過的時間(秒) ELAPSED_TIME=$(echo "$END_TIME - $START_TIME" | bc) # 計算每秒的 prompts 數量 PROMPTS_PER_SEC=$(echo "$TOTAL_PROMPTS / $ELAPSED_TIME" | bc -l) echo "Prompt per second: $PROMPTS_PER_SEC" ``` - 但這樣會記錄到模型初始化的時間,所以改成另一個版本,使用 python 去 call 伺服器,伺服器已經開好,就不會計算到這段初始化時間了。 - 閱讀伺服器設定後,呼叫 API 的部分: ```python # LlamaFile 模型 API 伺服器 URL url = "http://127.0.0.1:8080/completion" headers = {"Content-Type": "application/json"} # 定義請求的 payload payload = { "prompt": myprompt, "n_predict": 128 } response = requests.post(url, headers=headers, json=payload) ``` - 計算 token/sec 的部分: ```python generated_tokens = response.json()["content"] eval_tok_sec = len(generated_tokens) / (end_time - start_time) ``` - Evaluation tokens per second: 17.20 - 計算 prompt/sec 的部分: ```python generated_tokens = response.json()["content"] prompt_time = end_time - start_time - len(generated_tokens)/eval_tok_sec prompt_tok_sec = len(prompt2)/ prompt_time ``` - Prompt per second: 102.79 - 後來發現將 n_predict 設為 0,模型不會生成句子,可以直接算 prompt/ token,下面是修改後的程式: ```diff payload = { "prompt": myprompt, - "n_predict": 128 + "n_predict": 0 } generated_tokens = response.json()["content"] - prompt_time = end_time - start_time - len(generated_tokens)/eval_tok_sec + prompt_time = end_time - start_time prompt_tok_sec = len(prompt2)/ prompt_time ``` - 因為原始辦法計算出的 token/sec 只有 19,實在是太低。發現 token 應該不是指有幾個英文字母,嘗試使用 mistral 模型原本的 tokenizer,來仿照模型進行斷詞。 - `conda create -n linuxLLama python=3.8` - `conda activate linuxLLama` - 觀察 [mistral-common](https://github.com/mistralai/mistral-common)後,處理斷詞的函式: ```python def countToken(generated_text): # 使用 Mistral 模型的 tokenizer tokenizer_v1 = MistralTokenizer.v1() tokenized = tokenizer_v1.encode_chat_completion( ChatCompletionRequest( messages = [ UserMessage(content = generated_text)])) return len(tokenized.tokens) ``` - 後來發現 llamafile 有提供斷詞的 API,所以有了第二種斷詞的方法。 ```python urlToken = "http://127.0.0.1:8080/tokenize" text2token = requests.post(urlToken, headers=headers, json={"content": generated_text}) tokens = len(text2token.json()['tokens']) ``` - 在評估的計算方法上也有所更改,先前使用提示長度/秒數,現在更新成提示的 token 數量/秒數。 - 在多次實驗過程中,我發現了一個異常現象:將長度不同的句子輸入 mistral-common 這個 tokenizer 後,其輸出長度均為 137。經過進一步檢查,我注意到這是由於在生成過程中我設定了 n_predict 參數為 128,而 mistral 的 tokenizer 始終輸出 137 的結果未能引起我的注意。隨後,我改用 llamafile 的 API 進行分詞,明顯發現所有句子的長度均為 129,這才讓我意識到這一現像是由 n_predict 參數所引起的。 - 發現有極端值出現的情況,所以在最後算平均時做了去頭去尾的處理: ```python def final(sec_list): sec_list.remove(min(sec_list)) sec_list.remove(max(sec_list)) return sum(sec_list)/len(sec_list) ``` **對於 Mistral 7B 的實驗** - 實驗使用的模型下載來源紀錄: - [TheBloke/Mistral-7B-v0.1-GGUF](https://huggingface.co/TheBloke/Mistral-7B-v0.1-GGUF/tree/main) - [Undi95/Mistral-RP-0.1-7B-GGUF](https://huggingface.co/Undi95/Mistral-RP-0.1-7B-GGUF/tree/main?not-for-all-audiences=true) | weights data type | creater | | ----------------- | ----------------------------- | | q4_0 | TheBloke/Mistral-7B-v0.1-GGUF | | q8_0 | TheBloke/Mistral-7B-v0.1-GGUF | | fp_16 | Undi95/Mistral-RP-0.1-7B-GGUF | **Mistral 7B llamafile 實驗結果:** | prompt (tok/sec) | eval (tok/sec) | model | weights data type | hardware | software | tokenizer | | ---------------- | -------------- | --------------- | ----------------- | --------------------------- | ------------- | -------------- | | 22.03 | 5.99 | Mistral 7B v0.1 | q4_0 | Products formerly Broadwell | llamafile-0.8 | llamafile | | 25.04 | 5.42 | Mistral 7B v0.1 | q8_0 | Products formerly Broadwell | llamafile-0.8 | llamafile | | 13.25 | 2.89 | Mistral 7B v0.1 | fp_16 | Products formerly Broadwell | llamafile-0.8 | llamafile | | 31.85 | 7.10 | Mistral 7B v0.1 | q4_0 | Products formerly Broadwell | llamafile-0.8 | Mistral Common | | 38.20 | 6.77 | Mistral 7B v0.1 | q8_0 | Products formerly Broadwell | llamafile-0.8 | Mistral Common | | 30.75 | 3.02 | Mistral 7B v0.1 | fp_16 | Products formerly Broadwell | llamafile-0.8 | Mistral Common | **實驗觀察** `eval` 會隨著權重增加而下降,跟原本實驗的觀察相符。 關於額外的斷詞實驗,由於 Mistral 模型的斷詞方式會加入許多特殊字元,例如 "hi im Lisa" 會被轉換成 `'</s>▁[INST]▁hi▁im▁Lisa▁[/INST]'`,長度為 11,這使得 `eval` 的結果可能高於使用 LlamaFile 斷詞的情況。 --- **對於 Mistral 1.1B 的實驗** 然而,在發送跟先前 7B 大小模型的相同請求時,發現了模型完全不產生回應的情況。即使嘗試將 n_predict 數值調高,或加入 minkeep 參數,模型仍然無法生成回應。因此,我決定調換評估順序,並在 prompt 中輸入文字。 關於 prompt/tok 的計算方法並沒有更改,但是在 eval/tok 函數上做了一些調整: ```diff generated_text = response.json()["content"] text2token = requests.post(urlToken, headers=headers, json={"content": generated_text}) tokens = len(text2token.json()['tokens']) - eval_tok_sec = tokens / (end_time - start_time) + eval_tok_sec = tokens / (end_time - start_time - prompt_tokens / prompt_tok_sec) ``` - 實驗使用的模型下載來源紀錄: - [afrideva/malaysian-mistral-1.1B-4096-GGUF ](https://huggingface.co/afrideva/malaysian-mistral-1.1B-4096-GGUF/tree/main) | weights data type | creater | | ----------------- | ----------------------------- | | q4_k | afrideva/malaysian-mistral-1.1B-4096-GGUF | | q8_0 | afrideva/malaysian-mistral-1.1B-4096-GGUF | | fp_16 | afrideva/malaysian-mistral-1.1B-4096-GGUF | **Mistral 1.1B llamafile 實驗結果:** | prompt (tok/sec) | eval (tok/sec) | model | weights data type | hardware | software | tokenizer | | ---------------- | -------------- | ----------------- | ----------------- | --------------------------- | ------------- | -------------- | | 100.64 | 35.05 | Mistral 1.1B v0.1 | q4_k | Products formerly Broadwell | llamafile-0.8 | llamafile | | 158.68 | 29.26 | Mistral 1.1B v0.1 | q8_0 | Products formerly Broadwell | llamafile-0.8 | llamafile | | 129.98 | 8.74 | Mistral 1.1B v0.1 | fp_16 | Products formerly Broadwell | llamafile-0.8 | llamafile | | 138.14 | 32.26 | Mistral 1.1B v0.1 | q4_k | Products formerly Broadwell | llamafile-0.8 | Mistral Common | | 185.32 | 24.89 | Mistral 1.1B v0.1 | q8_0 | Products formerly Broadwell | llamafile-0.8 | Mistral Common | | 130.87 | 11.25 | Mistral 1.1B v0.1 | fp_16 | Products formerly Broadwell | llamafile-0.8 | Mistral Common | --- ### 執行 llama.cpp (針對 x86-64) > [llama.cpp github](https://github.com/ggerganov/llama.cpp) - [如何使用 llama.cpp](https://medium.com/@yvontarbajo/%E9%BA%BB%E7%93%9C%E8%B5%B0%E5%85%A5%E9%AD%94%E6%B3%95%E4%B8%96%E7%95%8C-ii-llm-in-house-0b178a62084f) ``` git clone https://github.com/ggerganov/llama.cpp cd llma.cpp make ./llama-server -m ../models/mistral_1b_q4_k.gguf -ngl 99 ``` **評估過程記錄** 我發現 llama.cpp 的伺服器 API 與 llamafile 完全相容,最初計劃直接使用相同的程式碼來進行效能評估。 以下是基於 llama.cpp 伺服器 API 和原本 Mistral 模型所使用的斷詞器的兩種斷詞方法所得到的輸出結果: ``` prompt: Hi, I'm Lisa. API:{'tokens': [28023, 15, 355, 10, 80, 413, 1922, 17]}, length:8 mistral-common:<s>▁[INST]▁Hi,▁I'm▁Lisa.▁[/INST], length:15 ``` **Mistral 1.1B llama.cpp 實驗結果:** > 使用兩種斷詞方法的比較實驗: | prompt (tok/sec) | eval (tok/sec) | model | weights data type | hardware | software | tokenizer | | ---------------- | -------------- | ----------------- | ----------------- | --------------------------- | ------------------- | -------------- | | 169.21 | 33.68 | Mistral 1.1B v0.1 | q4_k | Products formerly Broadwell | llama.cpp 2024-6-19 | llamafile | | 154.77 | 22.68 | Mistral 1.1B v0.1 | q8_0 | Products formerly Broadwell | llama.cpp 2024-6-19 | llamafile | | 132.90 | 13.84 | Mistral 1.1B v0.1 | fp_16 | Products formerly Broadwell | llama.cpp 2024-6-19 | llamafile | | 218.59 | 36.09 | Mistral 1.1B v0.1 | q4_k | Products formerly Broadwell | llama.cpp 2024-6-19 | Mistral Common | | 202.57 | 23.57 | Mistral 1.1B v0.1 | q8_0 | Products formerly Broadwell | llama.cpp 2024-6-19 | Mistral Common | | 214.27 | 14.15 | Mistral 1.1B v0.1 | fp_16 | Products formerly Broadwell | llama.cpp 2024-6-19 | Mistral Common | **Mistral 7B llama.cpp 實驗結果:** > 使用兩種斷詞方法的比較實驗: | prompt (tok/sec) | eval (tok/sec) | model | weights data type | hardware | software | tokenizer | | ---------------- | -------------- | --------------- | ----------------- | --------------------------- | ------------- | -------------- | | 21.38 | 5.66 | Mistral 7B v0.1 | q4_0 | Products formerly Broadwell | llama.cpp 2024-6-19 | llamafile | | 21.51 | 3.78 | Mistral 7B v0.1 | q8_0 | Products formerly Broadwell | llama.cpp 2024-6-19 | llamafile | | 14.80 | 2.19 | Mistral 7B v0.1 | fp_16 | Products formerly Broadwell | llama.cpp 2024-6-19 | llamafile | | 35.61 | 6.03 | Mistral 7B v0.1 | q4_0 | Products formerly Broadwell | llama.cpp 2024-6-19 | Mistral Common | | 35.32 | 4.15 | Mistral 7B v0.1 | q8_0 | Products formerly Broadwell | llama.cpp 2024-6-19 | Mistral Common | | 22.58 | 2.37 | Mistral 7B v0.1 | fp_16 | Products formerly Broadwell | llama.cpp 2024-6-19 | Mistral Common | 關於兩種軟體的實作實驗,通過實驗數據我們可以得出以下結論: 1. 斷詞器影響: 使用 Mistral Common 斷詞器的效能普遍高於 llamafile 斷詞器,尤其是在prompt階段的提升更為顯著。這表明 Mistral Common 斷詞器在處理特定模型和權重時具有更高的效率。 2. 權重類型: 不同的權重類型對效能有明顯影響。輕量化的 q4_0 和 q8_0 權重在 prompt 和 eval 階段的效能均優於 fp_16,這符合量化模型通常能在保持一定精度的同時提高效能的預期。 3. 模型大小: Mistral 1.1B 模型的效能顯著高於 Mistral 7B 模型,這可能是由於模型結構和優化策略的不同所致。 4. 軟體實現: llama.cpp 能夠與 llamafile 完全相容。 --- ### 兩種軟體對照結果 >先前是不同斷詞方式的對照實驗,再來進入到兩種軟體的對照實驗: **Mistral 7b 兩種軟體對照結果表格** | prompt (tok/sec) | eval (tok/sec) | model | weights data type | hardware | software | tokenizer | | ---------------- | -------------- | --------------- | ----------------- | --------------------------- | ------------------- | --------- | | 22.03 | 5.99 | Mistral 7B v0.1 | q4_0 | Products formerly Broadwell | llamafile-0.8 | llamafile | | 21.38 | 5.66 | Mistral 7B v0.1 | q4_0 | Products formerly Broadwell | llama.cpp 2024-6-19 | llamafile | | 25.04 | 5.42 | Mistral 7B v0.1 | q8_0 | Products formerly Broadwell | llamafile-0.8 | llamafile | | 21.51 | 3.78 | Mistral 7B v0.1 | q8_0 | Products formerly Broadwell | llama.cpp 2024-6-19 | llamafile | | 13.25 | 2.89 | Mistral 7B v0.1 | fp_16 | Products formerly Broadwell | llamafile-0.8 | llamafile | | 14.80 | 2.19 | Mistral 7B v0.1 | fp_16 | Products formerly Broadwell | llama.cpp 2024-6-19 | llamafile | **Mistral 1.1B 兩種軟體對照結果表格** | prompt (tok/sec) | eval (tok/sec) | model | weights data type | hardware | software | tokenizer | | ---------------- | -------------- | ------------------ | ----------------- | --------------------------- | ------------------- | --------- | | 100.64 | 35.05 | Mistral 1.1B v0.1 | q4_k | Products formerly Broadwell | llamafile-0.8 | llamafile | | 169.21 | 33.68 | Mistral 1.1B v0.1 | q4_k | Products formerly Broadwell | llama.cpp 2024-6-19 | llamafile | | 158.68 | 29.26 | Mistral 1.1B v0.1 | q8_0 | Products formerly Broadwell | llamafile-0.8 | llamafile | | 154.77 | 22.68 | Mistral 1.1B v0.1 | q8_0 | Products formerly Broadwell | llama.cpp 2024-6-19 | llamafile | | 143.88 | 21.87 | Mistral 1.1B v0.1 | fp_16 | Products formerly Broadwell | llamafile-0.8 | llamafile | | 132.90 | 13.84 | Mistral 1.1B v0.1 | fp_16 | Products formerly Broadwell | llama.cpp 2024-6-19 | llamafile | 上下測試模型皆為 Mistral,只是上方參數大小為 7B,而下方為 1.1B。 第一個實驗 Mistral 7B v0.1: 1. 在 q4_0 和 q8_0 量化權重下,llamafile 和 llama.cpp 的效能相近,但前者略勝一籌。 2. 在 fp_16 權重下,llamafile 在 prompt 和 eval 兩個階段的效能均高於 llama.cpp。 3. 整體而言,llamafile 在 Mistral 7B v0.1 模型中的效能略高於 llama.cpp。 第二個實驗 Mistral 1.1B v0.1: 1. 在 q4_k 權重下,llama.cpp 的 prompt 效能顯著高於 llamafile,但 eval 效能稍低。 2. 在 q8_0 和 fp_16 權重下,llamafile 在 eval 階段的效能均高於 llama.cpp,但在 prompt 階段則稍低。 3. 整體來看,llamafile 和 llama.cpp 各有優劣,具體效能依權重類型而異。