ข้ามไปยังเนื้อหา
13/30บทที่ 13 จาก 30

ทำ Inference ให้ถูกลง: KV cache, Batching และ Quantization

โมเดลเดียวกันตอบเหมือนกันทุก token ใน 8.8 และ 78.9 วินาที แล้ววัด INT4 สามแบบ ไม่ใช่แค่อ้าง

ในหน้านี้

โมเดลเดียวกัน บนเครื่องเดียวกัน ตอบคำถามเดียวกันด้วย 48 token เดิม ผลลัพธ์ทั้งสองเหมือนกันทุก token — ตรวจแล้ว ไม่ได้สมมติ

TEXT
with a key-value cache:     8.85 s   ( 6.01 tokens/second)
without a key-value cache: 78.95 s   ( 0.60 tokens/second)

มี argument เดียวที่เปลี่ยน: use_cache=False ไม่มีอะไรเกี่ยวกับโมเดล, prompt, sampling หรือเลขคณิตที่ต่างออกไป และการรันครั้งที่สองก็ไม่ได้แม่นยำขึ้นให้คุ้มกับความยุ่งยาก มันช้ากว่าเก้าเท่าโดยไม่ได้อะไรกลับมา

นี่คือรูปทรงของบทนี้ ทุกอย่างในบทนี้ — cache, batch, weights ที่ถูก quantized — คือความพยายามหยุดจ่ายให้กับงานที่ไม่ได้เปลี่ยนคำตอบ หรือหาว่าคำตอบที่ถูกลงต้องแลกกับอะไร บทที่ 10 วางบัญชีราคาของ training ไว้แล้ว นี่คือบัญชีราคาของฝั่งที่คุณต้องจ่ายตลอดไป: โมเดลที่ deploy แล้วใช้ประมาณ 2N2N FLOPs ต่อทุก token ที่มันปล่อยออกมา ในทุก request ไปตลอดอายุของมัน

เวลาของการรันครั้งที่สองหายไปไหน

ลิงก์ไปยังส่วน: เวลาของการรันครั้งที่สองหายไปไหน

ในการ generate token หนึ่ง token decoder-only transformer จะรับทั้ง sequence จนถึงตอนนั้น รันผ่านทุก layer แล้วอ่าน probability distribution จากตำแหน่งสุดท้าย จากนั้น append token ที่เลือกเข้าไป แล้วทำซ้ำ คำอธิบายนี้ถูกต้อง และนี่คือสิ่งที่การรันแบบช้าทำ

แต่มันก็สิ้นเปลืองอย่างมหาศาลด้วย และเหตุผลคือ causal mask จาก บทที่ 9 key และ value vectors ของตำแหน่ง 7 คำนวณจาก input ของตำแหน่ง 7 และตำแหน่งก่อนหน้า เมื่อตำแหน่ง 8 มาถึง ตำแหน่ง 7 มองไม่เห็นมัน — นั่นคือความหมายของ causal — ดังนั้น key และ value ของตำแหน่ง 7 จึงเป็น ตัวเลขชุดเดิมทุกประการ การรันแบบช้ากลับคำนวณมันซ้ำอยู่ดี ในทุก step

ดังนั้นก็เก็บมันไว้ ที่เก็บนี้คือ key-value cache ซึ่งเป็นการ optimize ที่มีผลที่สุดอย่างเดียวในการ serving โมเดลภาษา:

generate.pyPYTHON
out = model(prompt_ids, use_cache=True)          # prefill: the whole prompt
past = out.past_key_values                        
nxt = out.logits[:, -1].argmax(-1, keepdim=True)

for _ in range(n - 1):
    out = model(nxt, past_key_values=past, use_cache=True)   
    past = out.past_key_values                                
    nxt = out.logits[:, -1].argmax(-1, keepdim=True)

ดูสิ่งที่ป้อนเข้าโมเดลใน loop: nxt, หนึ่ง token ไม่ใช่ทั้ง sequence query ของ token ใหม่ attend กับ key ทุกตัวที่ cached ไว้ และ cached keys เหล่านั้นก็ไม่มีวันเปลี่ยนอยู่แล้ว นี่ ไม่ใช่ approximation — การตรวจว่า output เหมือนกันด้านบนคือประเด็นสำคัญ cache ไม่ได้แลกคุณภาพกับความเร็ว มันลบเลขคณิตที่ซ้ำซ้อนออกไป

เพื่อดู scaling ให้ชัด ลอก transformer ออกไป แล้วจับเวลา attention head เดียวที่มี d=64d = 64 โดยคำนวณ generation หนึ่ง step ทั้งสองวิธี:

token ใน contextคำนวณใหม่ทั้งหมดมี cacheratioscore matrix
1280.59 ms0.062 ms10x65,536 B vs 512 B
2561.20 ms0.163 ms7x262,144 B vs 1,024 B
5127.03 ms0.078 ms90x1,048,576 B vs 2,048 B
102417.31 ms0.114 ms152x4,194,304 B vs 4,096 B
204859.83 ms0.214 ms279x16,777,216 B vs 8,192 B
4096236.18 ms0.284 ms832x67,108,864 B vs 16,384 B

คอลัมน์ขวาสุดคือสาเหตุ การคำนวณใหม่สร้าง attention matrix เต็ม n×nn \times n ทุก step — O(n2)O(n^2) จากกล่อง asymptotic notation ของบทที่ 9 ที่จ่ายหนึ่งครั้งต่อ token เมื่อมี cache คุณสร้างแถว 1×n1 \times n แทน: ที่ 4,096 token คือ scores 67 MB เทียบกับ 16 KB

การนับ multiply-accumulates แทนมิลลิวินาทีทำให้เอาเครื่องออกจากข้อโต้แย้งได้ เพื่อ generate TT token จาก cold start:

token ที่ generatedมี cacheคำนวณใหม่ratio
1282.6 M192.0 M73x
51223.1 M7.36 G318x
2048293.7 M392.6 G1,336x

ต่อ step เวอร์ชัน cached เป็นเชิงเส้นตาม context ส่วนแบบไม่มี cache เป็นกำลังสอง เมื่อรวมทั้ง generation คือ O(T2)O(T^2) เทียบกับ O(T3)O(T^3) โดย ratio โตขึ้นไม่สิ้นสุด ความต่างเก้าเท่าในตอนเปิดบทวัดจาก 48 token — ยังสั้นกว่าแถวแรกของตารางนั้น

cache ยังเปลี่ยนสิ่งที่ต้องอยู่ใน memory ด้วย บน laptop GPU 8 GB ที่ generate 256 token ใน fp16 โดยเอา peak ของ allocator แล้วลบ resident weights:

peak working memory
มี cache21.8 MB
คำนวณใหม่181.7 MB

ใช้ memory มากกว่า 8.3 เท่า เพื่อผลิต token เดิมให้ช้าลง นี่คือคำสัญญาจาก บทที่ 5 ที่มาถึงจากทิศทางที่ไม่คาดคิด: ที่นั่น reverse-mode autodiff ต้องเก็บ intermediate ทุกตัวไว้ให้ backward pass และ activations ครอง memory ของ training ใน inference ไม่มี backward pass และไม่มีอะไรต้องเก็บไว้เพื่อมัน — ดังนั้นสิ่งที่ครอง memory แทนคือ cache และมันเป็นการเลือกโดยตั้งใจ ไม่ใช่ต้นทุนที่หลีกเลี่ยงไม่ได้

ดูการรันเร็วอีกครั้ง: token แรกของมันมีพฤติกรรมไม่เหมือนอีกสี่สิบเจ็ด token

TEXT
prefill, 40 prompt tokens : 1.0224 s   ->  25.6 ms per token
decode,  47 steps         : 0.1665 s mean per step

prompt ใช้ 25.6 ms ต่อ token และ token ที่ generated แต่ละตัวใช้ 166 ms โมเดลเดียวกัน hardware เดียวกัน weights เดียวกัน ต่างกันหกเท่าต่อ token — และทิศทางกลับกับที่คนส่วนใหญ่คาด prompt คือส่วนที่ ถูก Generation แยกเป็นสอง phase ที่มีฟิสิกส์ต่างกันจริง ๆ:

forward pass หนึ่งครั้งบน prompt ทั้งหมด ทุก token ถูกประมวลผลพร้อมกัน ดังนั้น weight matrix แต่ละตัวถูกโหลดจาก memory ครั้งเดียว แล้วคูณกับ matrix ของ token vectors หลายร้อยตัว — matrix-matrix product ที่มีเลขคณิตจำนวนมากต่อ byte ที่เคลื่อนย้าย ซึ่งเป็นสิ่งที่ GPU ถูกสร้างมาเพื่อทำ Prefill เป็น compute-bound และต้นทุนของมันประมาณเชิงเส้นตามความยาว prompt

forward pass หนึ่งครั้งต่อ token, batch ขนาดหนึ่ง และ sequence ขนาดหนึ่ง weight matrix ทุกตัวยังคงถูกโหลดจาก memory ทั้งหมด แล้วคูณกับ vector เดียว — matrix-vector product ที่แทบไม่มีเลขคณิตต่อ byte ที่เคลื่อนย้าย Decode เป็น memory-bandwidth-bound และต้นทุนต่อ token แทบไม่ขึ้นกับความยาวของ context

ทั้งสองครึ่งวัดได้ Prefill หนึ่ง pass บน PP token:

prompt tokensวินาทีms ต่อ token
160.351521.97
320.525416.42
641.049116.39
1281.655212.93
2563.096512.10

Decode หนึ่ง token เทียบกับ cache ของ CC:

cached tokensms สำหรับหนึ่ง token
16110.05
6497.57
256108.53
1024103.86

อ่านตารางที่สองสองรอบ การเพิ่ม context จาก 16 token เป็น 1,024 — history มากขึ้นหกสิบสี่เท่าให้ attend — เปลี่ยนต้นทุนของ step ไปแบบที่วัดไม่ได้ attention กับ cache เป็นงานจริง แต่ถูกกลบด้วยต้นทุนคงที่ของการลาก weights ครึ่งพันล้านตัวผ่าน memory bus เพื่อผลิต vector หนึ่งตัว ต้นทุนคงที่นี้คือเหตุผลของทุกอย่างในส่วนถัดไป

สอง phase นี้คือต้นกำเนิดของตัวเลขสองตัวที่ serving system ทุกระบบรายงาน Time to first token โดยแก่นแล้วคือ prefill และมันโตตาม prompt ซึ่งเป็นเหตุผลที่บทสนทนายาว ๆ รู้สึกเริ่มช้า Tokens per second คือ 1/decode step1/\text{decode step} และมันประมาณคงที่ จึงเป็นเหตุผลที่คำตอบหลังจากนั้นไหลสม่ำเสมอ chat ที่เริ่มช้าแล้ว stream ลื่นไม่ใช่กลเม็ดการ render มันคือตารางสองตารางนี้

cache แลกเลขคณิตกับ memory และ memory ที่มันต้องการไม่เล็กเลย สำหรับทุก token ใน context ทุก layer เก็บ key vector หนึ่งตัวและ value vector หนึ่งตัวต่อ key-value head:

bytes per token=2×L×Hkv×dhead×bytes per element\text{bytes per token} = 2 \times L \times H_{kv} \times d_{\text{head}} \times \text{bytes per element}

เลข 2 คือสำหรับ keys และ values; ที่เหลือทั้งหมดคือ architecture สำหรับโมเดลที่วัดตลอดบทนี้ — 24 layers, 14 query heads, 2 key-value heads, head dimension 64 — ใน fp16 เท่ากับ 2×24×2×64×2=12,2882 \times 24 \times 2 \times 64 \times 2 = 12{,}288 bytes ต่อ token

Formula ในวงการนี้มักคลาดเคลื่อนด้วย factor of two ดังนั้นตรวจเทียบกับ allocator แทนที่จะเชื่อมัน:

TEXT
KV cache tensors per layer: (1, 2, 295, 64) float16
measured: 3,624,960 bytes for 295 tokens = 12,288 bytes/token
formula : 2 * 24 * 2 * 64 * 2                = 12,288 bytes/token

ตรงเป๊ะ และยังตรงเป๊ะในทุก shape ที่ลอง:

batchcontextmeasured cachepredictedpeak working memory
15126.0 MB6.0 MB15.4 MB
116,384192.0 MB192.0 MB207.3 MB
165,536768.0 MB768.0 MB793.7 MB
84,096384.0 MB384.0 MB401.5 MB
322,048768.0 MB768.0 MB794.2 MB
641,024768.0 MB768.0 MB797.0 MB
128512768.0 MB768.0 MB816.4 MB

สามแถวสุดท้ายควรดูอีกครั้ง user สามสิบสองคนที่มี 2,048 token ต่อคน, หกสิบสี่คนที่มี 1,024, หนึ่งร้อยยี่สิบแปดคนที่มี 512 — cache เป็น 768 MB ทุกกรณี เพราะทั้งสามถือ token รวม 65,536 ตัว cache ขึ้นกับจำนวน token รวมที่ resident เท่านั้น ไม่ขึ้นกับว่ามันกระจายอยู่ใน user กี่คนอย่างไร ข้อเท็จจริงนี้คือรากฐานของส่วน batching

บทที่ 9 แนะนำ multi-query และ grouped-query attention แล้วเลื่อนเหตุผลมาที่บทนี้ เหตุผลคือ formula นั้น และโดยเฉพาะ HkvH_{kv} ในนั้น

standard multi-head attention ให้ query head ทุกตัวมี key และ value heads ของตัวเอง โมเดลนี้มี 14 query heads; ถ้าใช้ full multi-head attention cache ของมันจะเป็น 2×24×14×64×2=86,0162 \times 24 \times 14 \times 64 \times 2 = 86{,}016 bytes ต่อ token — 84 KB แทนที่จะเป็น 12 KB, มากกว่าเจ็ดเท่าพอดี ซึ่งเป็น ratio ของ query heads ต่อ key-value heads

Multi-query attention1 พาสิ่งนี้ไปสุดทาง: query heads ทั้งหมดแชร์ key-value head เดียว Grouped-query attention2 คือทางประนีประนอมที่ชนะ — key-value heads จำนวนหนึ่ง แต่ละตัวถูกแชร์โดยกลุ่มของ query heads — เพราะ quality loss ของ MQA เป็นเรื่องจริง แต่ของ GQA ไม่ใช่ ทั้งสองไม่ได้ซื้อเลขคณิตใด ๆ มันมีอยู่เพื่อหาร formula นั้นด้วยจำนวนเต็ม และแพร่ไปทั่วอุตสาหกรรมทันทีที่ long context ทำให้ cache เป็น binding constraint

ซึ่งมันเกิดขึ้นเร็วมาก สำหรับโมเดลระดับ 7B ที่มี 32 layers และ 8 key-value heads ขนาด dimension 128 cache คือ 128 KB ต่อ token ใน fp16:

context tokensuser เดียว8 users64 users
4,0000.49 GB3.91 GB31.2 GB
32,0003.91 GB31.25 GB250.0 GB
128,00015.62 GB125.00 GB1,000.0 GB
1,000,000122.07 GB976.56 GB7,812.5 GB

weights ของโมเดลนั้นเองคือ 13.0 GB ใน fp16 ตามตัวเลขในตารางท้ายบทนี้ ดังนั้นที่ context 128,000 token cache ของ user คนเดียวใหญ่กว่าโมเดล นี่คือเลขคณิตที่ บทที่ 16 แปลงเป็นเงิน และเป็นเหตุผลว่าบทสนทนายาว ๆ ไม่ได้แค่ช้า — มันครอบครองส่วนตายตัวของเครื่องหนึ่งไว้ตราบใดที่ request ยังมีชีวิต

Batching: ตัวเลขที่เพิ่มขึ้นและตัวเลขที่ลดลง

ลิงก์ไปยังส่วน: Batching: ตัวเลขที่เพิ่มขึ้นและตัวเลขที่ลดลง

Decode เป็น memory-bound: weights ถูกลากผ่าน bus เพื่อผลิต token หนึ่งตัว และหน่วยคำนวณนั่งว่าง ดังนั้นใส่งานมากขึ้นใน step เดียวกัน รันหลาย request พร้อมกัน แล้ว weights ที่อ่านครั้งเดียวก็รับใช้ทุก request วัดบนโมเดลเดียวกัน โดยแต่ละ request ถือ cache 64 token และ decode หนึ่ง token:

batchlatency ต่อ stepthroughputlatency vs B=1
10.1286 s7.78 tok/s1.00x
20.1839 s10.88 tok/s1.43x
40.1909 s20.95 tok/s1.49x
80.2781 s28.76 tok/s2.16x
160.3430 s46.64 tok/s2.67x
320.6302 s50.78 tok/s4.90x

อ่านสองคอลัมน์ขวาเทียบกัน เพราะมันคือประเด็นทั้งหมด การเพิ่มจากหนึ่ง request เป็นสิบหกทำให้ throughput คูณ 6.0 และทำให้เวลารอของ request รายตัวคูณ 2.67 batch ทำให้ server ดีขึ้นและ user ทุกคนแย่ลง

นี่ไม่ใช่ bug ที่ tune ให้หายได้ มันคือ trade-off เอง และมีชื่อคนละฝั่ง Latency คือสิ่งที่คนรอคำตอบสัมผัส Throughput คือสิ่งที่ใช้หาร invoice ไม่มี setting ใดทำให้ทั้งสองดีขึ้นพร้อมกัน

สังเกตด้วยว่ามันหยุดตรงไหน จาก 16 เป็น 32 throughput เพิ่ม 9 % ขณะที่ latency เกือบสองเท่า: step เลิกเป็น memory-bound แล้วกลายเป็น compute-bound และพ้น knee นั้น batch ก็ไม่ซื้ออะไรแล้ว ทุก deployment มี knee แบบนี้ ตำแหน่งของมันต้องวัดบนของคุณเอง แต่ การมีอยู่ ของมันไม่ต้องพิสูจน์

วิธี naive ในการ batch คือรวบรวม BB request รันพร้อมกัน แล้ว return เมื่อทุก request เสร็จ แต่พวกมันไม่ได้เสร็จพร้อมกัน: บางคำตอบมียี่สิบ token บางคำตอบมีห้าร้อย fixed batch รันจนสมาชิกที่ยาวที่สุดเสร็จ และ request ที่เสร็จแล้วทุกตัวก็ยังครอบครอง slot ของตัวเอง สร้าง padding จนถึงตอนนั้น

เอา 64 request ที่มี output length เอียงแบบสมจริง — median 18 token, ยาวสุด 231, รวม 1,874 — แล้ว simulate สอง policy ด้วย per-step cost ที่วัดได้สำหรับแปด slot:

policywall clockthroughputmean latency ต่อ requestwasted slot-steps
static batches of 8176.9 s10.6 tok/s83.2 s3,214
continuous, 8 slots109.0 s17.2 tok/s8.1 s0

Throughput ดีขึ้น 1.6x Mean latency ดีขึ้น มากกว่าสิบเท่า เพราะภายใต้ static batching request ที่เสร็จในสี่ step ยังต้องรอ neighbor 231-token ก่อนที่ใครจะได้ยินอะไร

Continuous batching3 คือทางแก้ และมันง่ายตามชื่อ: batch ไม่ใช่กลุ่ม แต่เป็นชุดของ slots และ slot ที่ว่างจะรับ request ถัดไปใน queue ใน step ถัดไปทันที scheduler ทำงานที่ granularity ของหนึ่ง token แทนที่จะเป็นหนึ่ง request serving stack ทุกตัวใน production ทำแบบนี้แล้วตอนนี้

มันมีครึ่งหลังด้วย ซึ่งคือ cache slots ที่เข้าออกทำให้ cache memory fragmented และการจอง slot แต่ละตัวตาม context สูงสุดที่เป็นไปได้ทำให้เสีย reservation ส่วนใหญ่ PagedAttention4 ยืมคำตอบจาก operating systems: เก็บ cache ใน fixed-size blocks พร้อม block table ต่อ sequence เพื่อให้ cache ของ sequence กระจัดกระจายทาง physical ได้ แต่ยัง contiguous ทาง logical — ซึ่งยังทำให้สอง sequence ที่มี prefix ร่วมกันแชร์ blocks ที่ถือ prefix นั้นได้ด้วย นี่คือสิ่งที่ vLLM สร้างอยู่บนมัน และเหตุผลที่ serving engine คือ memory allocator ที่มี transformer ติดอยู่

อีกครึ่งของบิลคือ weights เอง parameters ครึ่งพันล้านตัวที่สี่ bytes ต่อตัวคือ 1.98 GB; ที่สอง bytes คือ 0.99 GB; ที่หนึ่ง byte คือ 0.49 GB bits ต่อ weight น้อยลงทำให้โมเดลบน disk เล็กลง ทำให้ใน memory เล็กลง และ — เพราะ decode เป็น bandwidth-bound — ทำให้แต่ละ step เร็วขึ้น เนื่องจากมี bytes ให้ย้ายน้อยลง

scheme ที่ง่ายที่สุดคือ symmetric absolute-maximum quantization และมันใส่ได้ในสามบรรทัด:

quantize.pyPYTHON
qmax  = 2 ** (bits - 1) - 1
scale = W.abs().max() / qmax                        
Wq    = torch.round(W / scale).clamp(-qmax - 1, qmax)
W_hat = Wq * scale                                  # dequantized

เลือก scale เพื่อให้ weight ที่ใหญ่ที่สุด map ไปยัง integer ที่ใหญ่ที่สุด หาร ปัดเศษ เก็บ integers และ scale สร้างกลับโดยคูณคืน ไม่มีอะไรฉลาดเป็นพิเศษ และมันใช้ได้ — จนกระทั่งใช้ไม่ได้

วัดบน weights จริงของโมเดล: projection matrices ทั้งหมด 168 ตัว, parameters 357.8 ล้านตัว, relative error WW^/W\lVert W - \hat{W}\rVert / \lVert W \rVert:

schememean relative errorworst matrix
INT8, หนึ่ง scale สำหรับทั้ง matrix0.04000.1487
INT8, หนึ่ง scale ต่อ output row0.01000.0149
INT4, หนึ่ง scale สำหรับทั้ง matrix0.60260.9931
INT4, หนึ่ง scale ต่อ output row0.17900.2589
INT4, หนึ่ง scale ต่อ group of 1280.13230.1992
NF4, หนึ่ง scale ต่อ block of 640.09520.1205
INT3, หนึ่ง scale ต่อ group of 1280.30440.4123
INT2, หนึ่ง scale ต่อ group of 1280.77900.8076

แถวที่สี่คือการพังทลาย relative error 0.99 บน worst matrix หมายความว่า reconstruction แทบไม่เหลืออะไรของต้นฉบับเลย — matrix ถูกแทนด้วย noise ที่มี magnitude ประมาณถูกต้อง สาเหตุมองเห็นได้ในการทดลองเดียวกันบน matrix เดียว:

TEXT
model.layers.12.mlp.down_proj.weight   (896 x 4864)
mean |w| 0.01386   std 0.01822   max |w| 0.43945   max/std 24.1
weights beyond 6 sigma: 692 of 4,358,144   (0.016 %)

weight หนึ่งตัวในหกพันอยู่นอกหก standard deviations และตัวใหญ่สุดอยู่ไกล 24 ด้วย scale เดียวสำหรับทั้ง matrix weight หนึ่งตัวนั้นกำหนด step size ให้ทั้งหมด 4.3 ล้านตัว ที่ 8 bits มี 256 steps และ weight ทั่วไปยังลงบน step ที่มีความหมายได้ ที่ 4 bits มี 16 steps โดย step นอกสุดถูกกันไว้สำหรับค่าที่แทบไม่มีใครมี และ weights ปกติ — ซึ่งก็คือทั้งหมด — ถูกปัดไปเป็นเพียงสองหรือสามระดับ

ทุกอย่างหลังแถวนั้นคือการซ่อมแบบเดียวกันที่ granularity ต่างกัน: ให้ scale มีอาณาเขตเล็กลง Per output row หาร error ลง 3.4; per group of 128 consecutive weights หารมันอีก ต้นทุนคือ bookkeeping — scale 16-bit ต่อ group of 128 เท่ากับ 4+16/128=4.1254 + 16/128 = 4.125 bits ต่อ weight แทนที่จะเป็น 4 — และมันซื้อช่องว่างส่วนใหญ่กลับมา

NF4 เข้าหาจากอีกฝั่ง5 levels ไม่จำเป็นต้องห่างเท่ากัน weights ภายใน block กระจายประมาณ normal ดังนั้นเลือกสิบหกระดับเป็น quantiles ของ normal distribution: หนาแน่นใกล้ศูนย์ที่ weights อยู่จริง เบาบางใน tails ที่พวกมันไม่ได้อยู่ สี่ bits เดิม block scaling เดิม ที่ block เล็กกว่า — 4.25 bits ต่อ weight เทียบกับ 4.125 ของ group-128 — และ error ที่วัดได้ลดจาก 0.1323 เป็น 0.0952, ต่ำกว่า 28 % ส่วนหนึ่งมาจาก block ที่ละเอียดกว่า และที่เหลือมาจากการวาง levels ตรงที่มวลอยู่ การแยกสองอย่างนี้ต้องมีแถวที่สาม

กล่อง floating-point ของ บทที่ 2 จบด้วยคำสัญญา: ว่าบทนี้จะ quantize weights เป็น 8 และ 4 bits แล้วพบ feature นอกคอกหยิบมือหนึ่งที่ปฏิเสธการถูกบีบ นี่คือพวกมัน และมันอธิบายว่าทำไม “แค่ปัดตัวเลข” จึงไม่มีทางใช้ได้กับ activations

weights ด้านบนประพฤติตัวแย่แล้ว activations อยู่คนละลีกเลย ใช้ prompt ธรรมดา 84-token จับ residual stream ในแต่ละ layer แล้ววัด magnitude สูงสุดที่แต่ละ dimension จาก 896 dimensions ไปถึง:

layerlargest |h|median dimension's largest |h|ratiodimensions above 6x the median
16.190.33918x2
41543.481.550996x34
81571.631.4981049x36
121575.031.5461019x34
161579.601.617977x32
201577.982.361668x24
24204.4410.76019x12

Dimension 62 ไปถึง 1,579.6 ขณะที่ median dimension ไม่เคยเกิน 1.6 มันไม่ใช่ fluke ของ token หนึ่งตัวหรือ layer หนึ่งชั้น: dimension เดิมอยู่ที่ layer 4 และยังอยู่ที่ layer 20 ด้วยค่าเกือบเท่าเดิม นี่คือ outlier features,6 และมันเป็นระบบ — เป็น property ของโมเดลที่ trained แล้ว ไม่ใช่ของ input

histogram ของ maxima ต่อ dimension ทั้ง 896 ตัวที่ layer 16 ทำให้รูปทรงชัดเจนผิดไม่ได้:

TEXT
     0 -      1 | ######################################## 254
     1 -      2 | ######################################## 283
     2 -      4 | ######################################## 226
     4 -      8 | ######################################## 93
     8 -     16 | ##################                       18
    16 -     32 | #########                                9
    32 -     64 | #######                                  7
    64 -    128 | #####                                    5
   128 -    256 |                                          0
   256 -    512 |                                          0
   512 -   1024 |                                          0
  1024 -   4096 | #                                        1

เก้าร้อย dimensions กองเป็นระเบียบต่ำกว่า 8 ไม่มีอะไรเลยตลอดสาม octaves แล้วมี dimension เดียวโดดเดี่ยวอยู่ปลายสุด ตอนนี้ quantize tensor นั้นเป็น INT8 แล้วนับว่าเกิดอะไรขึ้น:

schemerelative errordistinct integer levels used, whole tensor
หนึ่ง scale สำหรับทั้ง tensor0.108314 of 256
หนึ่ง scale ต่อ token (per row)0.0433158
ทั้ง tensor, เก็บ 1 outlier dimension ใน fp320.044248
ทั้ง tensor, เก็บ 4 outlier dimensions ใน fp320.027957
ทั้ง tensor, เก็บ 16 outlier dimensions ใน fp320.0085102

สิบสี่ระดับจาก 256 scale ถูกกำหนดโดย 1,579.6 ดังนั้นทุก step กว้าง 12.44 และ activation ทั่วไป — median magnitude 0.26, ninety-ninth percentile 2.51 — ไม่มีที่ให้ลง ต่อ dimension ยิ่งชัดกว่า:

TEXT
single tensor-wide scale = 12.4378
  dim 826 (max |h| = 4.77):  1 distinct level out of 256
  dim 336 (max |h| = 1.62):  1 distinct level out of 256
  dim  96 (max |h| = 0.69):  1 distinct level out of 256

after excluding the top 4 dimensions, scale = 0.5749  (22x smaller)
  dim 826: 8 levels    dim 336: 4 levels    dim  96: 3 levels

หนึ่งระดับ ทั้ง dimension, ทุก token, ถูก quantized เป็นเลขเดียวกัน จัดสรรแปด bits แต่ใช้จริงประมาณศูนย์ และโมเดลที่อ่าน activations เหล่านั้นได้รับ constant

การวัดนั้นคือเหตุผลรองรับทุกเทคนิคที่ผู้คนใช้จริง:

กัน outliers ออกไปจากมัน LLM.int8()6 แยก matrix multiply: dimensions ที่มี magnitude สุดขั้วถูกคำนวณใน 16 bits ส่วนที่เหลือใน INT8 แล้วเอาสองครึ่งมารวมกัน ตารางด้านบนคือใบเสร็จ — เอาออกสี่ dimensions แล้ว error ลดลงเกือบสี่เท่า SmoothQuant7 ย้ายความยากแทน: หาร activations ด้วย factor ต่อ channel และคูณ weight column ที่ตรงกันด้วย factor นั้น ซึ่งทำให้ product ไม่เปลี่ยน และย้าย outlier ออกจาก tensor ที่รับมันไม่ไหวไปยัง tensor ที่รับได้

เลือกวิธีปัดเศษ ไม่ใช่แค่ปัด ไม่มีอะไรด้านบนถามว่า matrix นี้มีไว้ ทำอะไร GPTQ8 quantize ทีละ column และหลังแต่ละ column ปรับ columns ที่ยังเป็น full-precision เพื่อชดเชย error ที่ commit ไปแล้ว — minimising error ของ output ของ layer บน input จริง แทนที่จะเป็น error ของ weights AWQ9 สังเกตว่า weight channels ส่วนน้อยสำคัญกว่าส่วนที่เหลือมาก ค้นหามันจาก activation statistics แล้ว scale มันขึ้นก่อน quantizing เพื่อให้มันลงบน levels ที่ละเอียดกว่า ทั้งสองต้องใช้ calibration set; ไม่มีอันไหนต้องใช้ gradients

แสดงรายละเอียด

GGUF และ file format เกี่ยวอะไรกับเรื่องนี้

GGUF ไม่ใช่วิธี quantization; มันคือ container ที่ llama.cpp ใช้ และความสับสนใน comparison ของ gguf vs gptq มาจากการปฏิบัติต่อสองอย่างนี้ราวกับเป็นสิ่งชนิดเดียวกัน GGUF ถือ tensors, tokenizer, architecture metadata และ chat template ไว้ในไฟล์เดียวที่ memory-mappable ได้ และพา family ของ block schemes ไว้ข้างใน — ชื่ออย่าง Q4_K_M encode bits ต่อ weight, block size และว่ามี tensors บางตัวถูกเก็บไว้ที่ precision สูงกว่าหรือไม่

ความต่างทาง engineering ที่สำคัญ: GPTQ และ AWQ ผลิต weights ที่ optimise สำหรับ GPU kernel ส่วน schemes ของ GGUF decode ได้ถูกบน CPU โดย map file แทนการโหลด นี่คือเหตุผลที่ “4-bit 7B model” ตามชื่อแบบเดียวกันมีอยู่ทั้งสองโลก ด้วยขนาดต่างกันและคุณภาพต่างกัน และเหตุผลที่การเปรียบเทียบที่ซื่อสัตย์ไม่เคยเป็น format — มันคือการวัดด้านล่าง รันบน task ของคุณเอง

quantization มีต้นทุนจริงเท่าไร เมื่อวัดจริง

ลิงก์ไปยังส่วน: quantization มีต้นทุนจริงเท่าไร เมื่อวัดจริง

บทความเกี่ยวกับ quantization แทบทุกบทหยุดที่ส่วนก่อนหน้า: อธิบาย method อ้าง compression ratio และยืนยันว่าคุณภาพ “ส่วนใหญ่ยังคงอยู่” บทที่ 4 ว่าด้วยการไม่หลอกตัวเอง ดังนั้นมาหาคำตอบกัน

โมเดลเดียวกัน weights ถูก quantized in place ด้วยแต่ละ scheme จากนั้นวัดสามอย่าง: perplexity บน prose ภาษาอังกฤษ held-out 2,048 token — ที่นี่คือ draft ของคอร์สนี้ ซึ่งเป็นเหตุผลที่ repository แทนด้วยหนังสือ public-domain คงที่และพิมพ์ตารางรูปทรงเดียวกันแต่ตัวเลขต่างกัน — ชุดคำถามข้อเท็จจริงสั้น 16 ข้อพร้อมคำตอบที่รู้ภายใต้ greedy decoding และสัดส่วน token ที่โมเดล quantized เห็นตรงกับโมเดล full-precision เมื่อให้ context เดียวกัน

schememean weight errorperplexityquestion batteryagrees with fp32
fp32 (reference)0.000023.0813/16100.0 %
INT8 per tensor0.040023.5813/16
INT8 per row0.010022.9613/1698.6 %
INT4 per tensor0.6026365,416,0000/16
INT4 per row0.179046.186/1658.3 %
INT4 group 1280.132331.0810/1671.5 %
NF4 block 640.095224.5511/1684.7 %
INT3 group 1280.3044213.090/165.6 %
INT2 group 1280.779026,325,4360/160.0 %

สี่อย่างในตารางนั้นควรพูดให้ชัด

INT8 ที่ทำถูกต้องคือฟรี Per-row INT8 ได้ 22.96 เทียบกับ 23.08 ของ reference — ช่องว่างหนึ่งส่วนในสองร้อย ซึ่งเป็น noise และควรอ่านว่า “เหมือนกัน” noise จะชี้ไปทางไหนไม่เสถียร: บน corpus public-domain ของ repository schemes สองตัวเดิมออกมา 22.24 เทียบกับ 22.18: ระยะห่างครึ่งหนึ่ง และชี้อีกทาง มันเห็นตรงกับโมเดล full-precision บน 142 จาก 144 generated tokens ใช้ memory หนึ่งในสี่เทียบกับ fp32 reference, ครึ่งหนึ่งเทียบกับ fp16 ที่คุณจะ deploy จริง และไม่มีต้นทุนที่ตรวจจับได้ INT8 ที่ทำ ลวก ๆ ก็เกือบฟรีเช่นกัน: หนึ่ง scale ต่อ matrix เสีย perplexity 0.5 points และไม่เสียคำตอบใน battery แปด bits ให้อภัยมากพอที่ granularity แทบไม่สำคัญ ซึ่งเป็นเหตุผลที่ผู้คน generalise จาก INT8 ไป INT4 แล้วเจ็บตัว

INT4 ที่มีหนึ่ง scale ต่อ tensor ทำลายโมเดล Perplexity 365 ล้าน: ไม่ใช่ degraded แต่ annihilated จากนั้น granularity คือทั้งเกม — per-tensor 365,416,000, per-row 46.18, per-group-of-128 31.08, NF4 24.55 สี่ bits ต่อ weight เหมือนกัน แต่ต่างกันสิบห้าล้านเท่าระหว่างแย่สุดกับดีที่สุด

Perplexity เป็นเครื่องมือหยาบ และ battery ก็หยาบกว่า ระหว่าง NF4 กับ group-128 INT4 ช่องว่าง perplexity คือ 6.5 points และ battery ต่างกันหนึ่งคำถาม — และ confidence interval ของบทที่ 4 บอกว่าหนึ่งคำถามจากสิบหกไม่แยกแยะอะไรเลย มี demonstration ที่คมกว่า interval: รัน battery เดิมโดยปิด stock repetition penalty ของโมเดล ซึ่งคือสิ่งที่ greedy decoding หมายถึงจริง ๆ แล้วสองแถวนั้นสลับที่กัน หนึ่งคำถามจากสิบหกไม่ใช่ effect เล็ก มันคือไม่มี effect คำเตือนของ บทที่ 8 ก็ใช้ได้ด้วย: perplexity เปรียบเทียบได้เฉพาะระหว่างโมเดลที่ใช้ tokenizer ร่วมกัน ดังนั้นตัวเลขจาก write-up ของคนอื่นเอามาเทียบกับของคุณไม่ได้

คอลัมน์ agreement คมที่สุดในสามอย่าง และแทบฟรี: รันโมเดล full-precision แบบ greedy แล้วถามโมเดล quantized ที่ทุกตำแหน่งว่ามันจะเลือกอะไรเมื่อให้ prefix เดียวกัน มันมี observations อิสระ 144 จุดแทนที่จะเป็น 16 ไม่ต้องมี ground truth และ degrade อย่างราบรื่นในจุดที่ battery degrade เป็นขั้น ๆ มันยังเป็นปริมาณพอดีที่ส่วนถัดไปต้องใช้

นี่คือคำสัญญาที่ บทที่ 1 ให้ไว้เกี่ยวกับบทนี้ มาถึงตรงเวลา: คณิตศาสตร์บอกว่าโมเดล 4-bit เป็นไปได้ และ engineering ตัดสินว่ามันใช้งานได้หรือไม่

บทที่ 12 ประกาศเรื่องนี้ไว้และทิ้งบิลไว้ที่นี่

ไอเดียมาจากการแยก prefill/decode ตรง ๆ การ verify sequence ที่ ถูกเสนอ จำนวน γ\gamma token ใช้ forward pass หนึ่งครั้งบน γ\gamma positions — matrix-matrix product ที่แทบไม่แพงกว่าการ pass บนหนึ่งตัว ดังนั้น:

โมเดลเล็กที่ถูกกว่า generate candidate tokens γ\gamma ตัวแบบ autoregressive

โมเดลใหญ่รัน forward pass หนึ่งครั้งบน candidates ทั้ง γ\gamma ตัวพร้อมกัน สร้างสิ่งที่มันจะพูดในแต่ละตำแหน่ง

เก็บ prefix ที่ยาวที่สุดซึ่งทั้งสองเห็นตรงกัน บวก token ที่โมเดลใหญ่ให้มาฟรี ณ จุดแรกที่ไม่ตรงกัน ทิ้งส่วนที่เหลือแล้วเริ่มใหม่

output distribution ไม่เปลี่ยน ด้วย greedy decoding นั่นชัดเจน — token จะถูก accept ก็ต่อเมื่อ target จะผลิตมันเอง ด้วย sampling ต้องใช้ acceptance rule ที่ดัดแปลง และ Leviathan et al. พิสูจน์ว่า distribution ที่ได้ตรงกับ target ทุกประการ10 นี่คือ exact optimisation อย่างที่สองในบทนี้

ดังนั้นทุกอย่างขึ้นกับ acceptance rate α\alpha ซึ่งวัดได้ — มันคือคอลัมน์ agreement ด้านบน นั่นคือเหตุผลที่คำนวณมันไว้ตรงนั้น ใช้แต่ละโมเดล quantized เป็น draft ให้ target full-precision บน generated positions 144 จุด:

draft modelacceptancelongest accepted runexpected tokens per target pass, γ=4\gamma = 4
fp32 (the target itself)100.0 %485.00
INT8 per row98.6 %484.86
NF4 block 6484.7 %203.69
INT4 group 12871.5 %132.85
INT4 per row58.3 %72.24
INT3 group 1285.6 %21.06
INT2 group 1280.0 %01.00

จำนวน token ที่คาดว่าจะ accept ต่อ verification pass ที่ draft length γ\gamma คือ

E[tokens]=1αγ+11α\mathbb{E}[\text{tokens}] = \frac{1 - \alpha^{\gamma+1}}{1 - \alpha}

และ net speedup หารด้วยต้นทุนของ draft เอง ซึ่งเป็น fraction cc ของ target ต่อ token:

acceptancec=0.05c=0.05, γ=4\gamma=4c=0.1c=0.1, γ=4\gamma=4c=0.2c=0.2, γ=4\gamma=4c=0.1c=0.1, γ=8\gamma=8
30 %1.19x1.02x0.79x0.79x
50 %1.61x1.38x1.08x1.11x
70 %2.31x1.98x1.54x1.78x
90 %3.41x2.93x2.28x3.40x

รายการตัวหนาคือสิ่งที่ต้องจำ: speculative decoding อาจทำให้ generation ช้าลงได้ ที่ acceptance 30 % และ draft มีต้นทุนหนึ่งในห้าของ target คุณจ่าย forward pass ห้าครั้งแล้วเก็บได้ 1.4 token คอลัมน์สุดท้ายคือกับดักอีกอย่าง — draft ที่ยาวขึ้นช่วยเฉพาะเมื่อ acceptance สูง เพราะหางของการเดา γ\gamma-token แทบไม่มีวันไปถึง ที่ acceptance 90 % γ=8\gamma = 8 คุ้ม 3.40x และที่ 30 % มันคุ้ม 0.79x: configuration เดียวกัน เป็นกำไรหรือขาดทุนขึ้นกับตัวเลขที่วัดบน traffic ของคุณ

Quantization ย่อโมเดลโดยเก็บ function เดิมด้วย bits ที่น้อยลง Distillation ย่อมันโดย train โมเดลที่เล็กกว่าให้เลียนแบบโมเดลที่ใหญ่กว่า11 — ไอเดียที่มาก่อน deep learning เกือบทศวรรษ12

ส่วนละเอียดอ่อนคือ student เรียนจาก อะไร ไม่ใช่คำตอบที่ถูกต้อง: มัน train จากสิ่งนั้นโดยตรงได้อยู่แล้ว สิ่งที่ teacher เพิ่มให้คือ distribution ทั้งหมด ถามโมเดลว่าอะไรตามหลัง phrase หนึ่ง แล้วมองเลย argmax ไป:

TEXT
"She poured the milk into the"
  ' jug' 0.1355   ' cup' 0.1051   ' bowl' 0.0605   ' large' 0.0380   ' milk' 0.0360

hard label บอกว่า jug และไม่มีอะไรอื่น soft label บอกว่า jug และยังบอกด้วยว่า cup เกือบดีพอกัน, bowl plausible และ large — adjective ซึ่งเป็น grammatical continuation คนละแบบโดยสิ้นเชิง — ยังมีชีวิตอยู่ นั่นคือข้อโต้แย้งดั้งเดิม: นี่คือเลข 7 แต่ดูคล้ายเลข 1 อยู่ไม่น้อย และความคล้ายนั้นคือข้อมูลที่ hard label โยนทิ้ง

นี่ก็เป็นเหตุผลที่ distillation ใช้ temperature การหาร logits ด้วย TT ก่อน softmax ทำให้ distribution แบนลง และเพิ่มน้ำหนักสัมพัทธ์ของตัวรองลงมา: บน phrase นี้ ratio ระหว่าง top token กับตัวที่สามลดจาก 2.24 ที่ T=1T = 1 เป็น 1.50 ที่ T=2T = 2 — รากที่สองของตัวแรก ซึ่งคือสิ่งที่การหาร logits ด้วยสองทำกับ ratio ลำดับเดิม แต่ loss ให้ attention กับ near misses มากขึ้น gradient ของ student พก uncertainty ของ teacher ไม่ใช่แค่ verdict ของมัน

ทุกอย่างในบทนี้ตอนนี้คือผลบวกเดียว:

memory=N×bytes per weightfixed+T×2LHkvdhead×bytesgrows with every token+runtime overheadcall it 1.5 GB\text{memory} = \underbrace{N \times \text{bytes per weight}}_{\text{fixed}} + \underbrace{T \times 2 L H_{kv} d_{\text{head}} \times \text{bytes}}_{\text{grows with every token}} + \underbrace{\text{runtime overhead}}_{\text{call it 1.5 GB}}

โดยที่ TT คือ token รวม ที่ resident ข้าม concurrent requests ทั้งหมด เมื่อนำไปใช้: แถว 7B และ 70B สมมติ 8 key-value heads ขนาด dimension 128 ส่วนแถว 13B ใช้ full multi-head attention ที่มี 40 heads ซึ่งเป็นวิธีที่โมเดล generation เหล่านั้นถูกสร้าง — และมันแสดงให้เห็น

8 GB

modelprecisionweightsfree after overheadcontext tokens that fit
7Bfp1613.0 GBdoes not fit
7Bint86.5 GBdoes not fit
7Bint4 (g128)3.4 GB3.1 GB25,710
13Bint4 (g128)6.2 GB0.3 GB337
70Bint4 (g128)33.6 GBdoes not fit

16 GB

modelprecisionweightsfree after overheadcontext tokens that fit
7Bfp1613.0 GB1.5 GB11,972
7Bint86.5 GB8.0 GB65,378
7Bint4 (g128)3.4 GB11.1 GB91,246
13Bint812.1 GB2.4 GB3,136
13Bint4 (g128)6.2 GB8.3 GB10,822

24 GB

modelprecisionweightsfree after overheadcontext tokens that fit
7Bfp1613.0 GB9.5 GB77,508
7Bint86.5 GB16.0 GB130,914
7Bint4 (g128)3.4 GB19.1 GB156,782
13Bint812.1 GB10.4 GB13,622
13Bint4 (g128)6.2 GB16.3 GB21,308
70Bint4 (g128)33.6 GBdoes not fit

ดูแถว 13B ในตาราง 8 GB weights ใส่ได้ — 6.2 GB จาก 8 — ดังนั้นตามวิธีพูดทั่วไป โมเดล 13B “รันบนการ์ด 8 GB” มันมี context 337 token ซึ่งไม่ใช่บทสนทนา แต่แทบจะเป็น prompt เท่านั้น “มันใส่ได้ไหม” เป็นคำถามที่ผิด คำถามที่ถูกคือ “ด้วย context เท่าไร และสำหรับ user พร้อมกันกี่คน”

ดูสองแถว int8 ของ 16 GB ด้วย 7B ได้ 65,378 token และ 13B ได้ 3,136 — ต่างกันยี่สิบเท่าจาก weights เพิ่ม 5.6 GB เพราะ 13B ตรงนี้มี multi-head attention และ cache ของมันมีต้นทุน 800 KB ต่อ token เทียบกับ 128 KB ของ 7B โมเดลสองตัวที่ขนาดใกล้กัน ตัวหนึ่งใช้ long context ไม่ได้ ด้วยเหตุผลที่ไม่ปรากฏใน headline ของ model card ใด ๆ

สิบสามบทก่อน นี่คือ perceptron ที่มี weights สองตัวและ bias หนึ่งตัว ตอนนี้มันเป็น transformer ที่ถูกออกแบบ, trained, aligned, สอนให้ใช้ compute กับคำถามยาก และ served ด้วยต้นทุนต่อ token ที่วัดแล้ว — ไม่มี box ใดในนั้นที่ยังไม่ถูกเปิด

มันจบตรงนี้ และจบอย่างตั้งใจ

บทที่ 14 เริ่มด้วยโมเดลที่อยู่ที่อื่น ไม่ได้อยู่ใน process ของคุณ ไม่ได้อยู่ใน memory ของคุณ ไม่ได้อยู่ใน variable ที่คุณ print ได้: มันอยู่บนเครื่องที่คุณไม่ได้ administer อยู่หลัง API key, port และ bill ทุกอย่างที่วัดตรงนี้ยังเกิดขึ้นอยู่ — prefill ยังรันก่อน token แรก cache ยังโตตามบทสนทนา batch ที่คุณอยู่ในนั้นยังเป็นของคนอื่นและยังตัดสิน latency ของคุณ — แต่จากนี้ไปคุณสังเกตมันผ่าน stream ของ Server-Sent Events, finish_reason และ HTTP 429 ที่มี header Retry-After คำถามเปลี่ยนตาม vantage point: ไม่ใช่ gradient นี้คำนวณอย่างไร แต่เป็น ทำไม invoice ของฉันถึงเพิ่มสามเท่า ภาษาก็เปลี่ยนด้วย และบทที่ 14 อธิบายกฎนั้นแทนที่จะประกาศมัน — ถึงตรงนี้ code ถือ weights, gradients, logits และ bytes ของ tokenizer; จากตรงนั้นเป็นต้นไปมันถือ connection, retry, cancellation และ accumulated state สิบสามบทที่คุณผ่านมาข้างหลังไม่ได้ถูกทิ้งเมื่อข้ามฝั่ง มันคือคำอธิบายของสิ่งที่กำลังรันอยู่หลัง port อีกด้าน


การละไว้สองอย่างเป็นความตั้งใจ FlashAttention (Dao et al., arXiv:2205.14135) ไม่ใช่ attention อีกแบบ — มันคำนวณ function เดิมโดย tile operation เพื่อไม่ให้ score matrix n×nn \times n ถูกเขียนลง memory ซึ่งเป็นเหตุผลที่ 67 MB ในตารางที่สองของบทนี้เล็กกว่าในทางปฏิบัติเมื่อเทียบกับที่เลขคณิตบอก และ kernels เองถูกมอบหมายต่อ: lecture 10 ของ CS336 จาก Stanford ครอบคลุม inference systems ในความลึกที่บทนี้ไม่ได้พยายามทำ และ repository llama.cpp กับ GGUF specification คือแหล่งหลักสำหรับฝั่ง CPU

  1. Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019). paper นี้โดยมากคือข้อโต้แย้งเรื่อง memory-bandwidth และอ่านได้แบบนั้น

  2. Ainslie, J. et al. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023). รวม uptraining recipe ที่แปลง checkpoint multi-head ที่มีอยู่ ซึ่งเป็นเหตุผลที่ GQA แพร่เร็วมาก

  3. Yu, G.-I., Jeong, J. S., Kim, G.-W., Kim, S. and Chun, B.-G. Orca: A Distributed Serving System for Transformer-Based Generative Models. OSDI 2022. แนะนำ iteration-level scheduling — continuous batching — และ selective batching

  4. Kwon, W. et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. arXiv:2309.06180 (2023), SOSP 2023. paper ที่ vLLM สร้างอยู่บนมัน; §3 คือ analogy กับ operating systems แบบเต็ม

  5. Dettmers, T., Pagnoni, A., Holtzman, A. and Zettlemoyer, L. QLoRA: Efficient Finetuning of Quantized LLMs. arXiv:2305.14314 (2023). NF4 ถูกนิยามใน §3; ค่าระดับทั้งสิบหกที่ใช้ในการวัดด้านบนคือค่าที่ paper นี้ derive

  6. Dettmers, T., Lewis, M., Belkada, Y. and Zettlemoyer, L. LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale. arXiv:2208.07339 (2022). การวิเคราะห์ outlier-feature ใน §4 คือแหล่งที่มาของปรากฏการณ์ที่วัดด้านบน รวมถึงข้อค้นพบว่า outliers เกิดขึ้นอย่างเป็นระบบเมื่อ scale ใหญ่ขึ้น 2

  7. Xiao, G., Lin, J., Seznec, M., Wu, H., Demouth, J. and Han, S. SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models. arXiv:2211.10438 (2022).

  8. Frantar, E., Ashkboos, S., Hoefler, T. and Alistarh, D. GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers. arXiv:2210.17323 (2022).

  9. Lin, J. et al. AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration. arXiv:2306.00978 (2023).

  10. Leviathan, Y., Kalman, M. and Matias, Y. Fast Inference from Transformers via Speculative Decoding. arXiv:2211.17192 (2022). Theorem 1 คือ proof ว่า output distribution ไม่เปลี่ยน; Chen et al. (arXiv:2302.01318) เผยแพร่ไอเดียเดียวกันอย่างอิสระ

  11. Hinton, G., Vinyals, O. and Dean, J. Distilling the Knowledge in a Neural Network. arXiv:1503.02531 (2015). temperature และข้อโต้แย้งเรื่อง “dark knowledge”

  12. Buciluă, C., Caruana, R. and Niculescu-Mizil, A. Model Compression. KDD 2006. Distillation เก้าปีก่อนหน้า สำหรับ ensembles แทนที่จะเป็น transformers


สร้างโดย

David Vicente Campos

ผู้ก่อตั้ง NeuraLIA Labs และผู้ร่วมก่อตั้ง MyRealFood

ผมเป็นวิศวกรคอมพิวเตอร์ที่จบจากมหาวิทยาลัยเลออน ผมร่วมก่อตั้ง MyRealFood ที่ที่ผมในฐานะ CTO ได้สร้างแอปซึ่งผู้คนหลายล้านคนใช้เพื่อกินให้ดีขึ้น และผมก่อตั้ง NeuraLIA Labs ที่ที่ผมสร้างผลิตภัณฑ์ AI ที่นี่ผมเขียนถึงสิ่งที่ผมต้องทำความเข้าใจระหว่างทาง ในแบบที่ผมเคยหวังว่าจะมีใครสักคนอธิบายให้ผมฟัง

เพิ่มเติมเกี่ยวกับผู้เขียน

เผยแพร่โดย NeuraLIA Labs

รับโพสต์ใหม่ในกล่องจดหมาย

ข่าว AI คู่มือ และอัปเดตผลิตภัณฑ์ — อีเมลสั้น ๆ เมื่อเรามีสิ่งที่คุ้มเวลาของคุณ

ชอบแบบข้อความมากกว่าไหม รับเนื้อหาเดียวกันได้ที่นี่:คอมมูนิตี้ WhatsApp (เปิดในแท็บใหม่)ช่อง Telegram (เปิดในแท็บใหม่)

ดัชนีคอร์ส

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jevอ่าน 5 นาที

โมเดล AI Jev สร้างมาเพื่อการตัดสินใจ ไม่ใช่การเขียนความเรียง

Jev ของ TypeSafe AI กำลังได้รับความสนใจ เพราะมองความฉลาดของซอฟต์แวร์เป็นปัญหาความน่าจะเป็น: เลือกกิ่งที่ถูกต้อง แนบความมั่นใจ และหลีกเลี่ยงการจ่ายเงินให้ LLM เขียนข้อความเมื่อโค้ดต้องการการตัดสินใจ

Abstract legal research workspace with documents, search nodes and governance controls.
openaiอ่าน 4 นาที

Astra for Law ของ OpenAI คือระบบ AI ด้านกฎหมาย ไม่ใช่โมเดลใหม่

การเปิดตัวด้านกฎหมายของ OpenAI ไม่ได้เน้นโมเดลฐานรากใหม่เท่ากับระบบที่ล้อมรอบโมเดลนั้น: การค้นคืนเฉพาะโดเมน เครื่องมือที่เชื่อถือได้ สิทธิ์ เบนช์มาร์ก และเส้นทางการตรวจทาน

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineeringอ่าน 4 นาที

วิศวกรรมบริบทสำหรับเอเจนต์ AI ที่ทำงานระยะยาว

เอเจนต์ที่ทำงานต่อเนื่องไม่ได้ล้มเหลวเพียงเพราะหน้าต่างบริบทเล็กเกินไป แต่ล้มเหลวเมื่อไฟล์ ผลลัพธ์จากเครื่องมือ และประวัติที่ค้างเก่าบดบังงานที่เอเจนต์ควรทำให้เสร็จ

พร้อมให้ LIA เลือกโมเดลให้แล้วหรือยัง?

สร้างงานด้วยโมเดล AI ทุกตัวในที่เดียว เริ่มฟรีวันนี้