सामग्री पर जाएँ
13/30अध्याय 13 / 30

Inference को सस्ता बनाना: KV cache, Batching और Quantization

वही model, वही सवाल: 8.8 सेकंड बनाम 78.9, byte-identical output। फिर INT4 को तीन तरीकों से मापा, माना नहीं।

इस पेज पर

वही model, उसी मशीन पर, वही 48 token के साथ वही सवाल जवाब देता है। दोनों outputs 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। model, prompt, sampling या arithmetic में कुछ भी अलग नहीं है, और दूसरी run अपनी मेहनत के बदले ज़्यादा accurate भी नहीं है। वह बिना वजह नौ गुना धीमी है।

यही इस अध्याय का आकार है। इसमें सब कुछ — cache, batch, quantized weights — या तो उस काम की कीमत चुकाना बंद करने की कोशिश है जो जवाब नहीं बदलता, या यह पता लगाने की कि सस्ता जवाब असल में कितना महँगा पड़ता है। अध्याय 10 ने training की price list तय की थी। यह उस हिस्से की price list है जिसके लिए आप हमेशा भुगतान करते हैं: deployed model अपनी ज़िंदगी भर, हर request पर, हर निकाले गए token के लिए लगभग 2N2N FLOPs खर्च करता है।

एक token generate करने के लिए decoder-only transformer अब तक की पूरी sequence लेता है, उसे हर layer से चलाता है, और आख़िरी position से probability distribution पढ़ता है। फिर वह चुना गया token जोड़ता है और यह फिर से करता है। यह वर्णन सही है, और slow run यही करती है।

यह बेहद wasteful भी है, और वजह अध्याय 9 का causal mask है। Position 7 के key और value vectors position 7 के input और उससे पहले की positions से compute होते हैं। जब position 8 आती है, position 7 उसे नहीं देख सकती — causal का मतलब यही है — इसलिए position 7 के key और value ठीक वही numbers हैं जो पहले थे। Slow run फिर भी हर step पर उन्हें दुबारा compute करती है।

तो उन्हें store कर लें। वही store key-value cache है, language model serving में सबसे निर्णायक optimisation:

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 के अंदर model को क्या fed किया जाता है: nxt, एक token। Sequence नहीं। नए token की query हर cached key पर attend करती है, और cached keys बदलने वाली थीं ही नहीं। यह approximation नहीं है — ऊपर identical-output check का point यही है। cache quality के बदले speed नहीं लेता; वह redundant arithmetic हटा देता है।

Scaling साफ़ देखने के लिए transformer को हटाकर d=64d = 64 के साथ एक single attention head time करें, generation का एक step दोनों तरीकों से compute करके:

context में tokensसब कुछ recomputecache के साथratioscore 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

दाईं ओर वाला column कारण है। Recomputing हर step पर पूरी n×nn \times n attention matrix बनाता है — अध्याय 9 के asymptotic-notation box का O(n2)O(n^2), हर token पर एक बार चुकाया गया। cache के साथ आप इसके बजाय 1×n1 \times n row बनाते हैं: 4,096 tokens पर, 67 MB scores बनाम 16 KB।

Milliseconds के बजाय multiply-accumulates गिनने से machine argument से हट जाती है। Cold start से TT token generate करने के लिए:

generated tokenscache के साथrecomputingratio
1282.6 M192.0 M73x
51223.1 M7.36 G318x
2048293.7 M392.6 G1,336x

हर step पर cached version context में linear है और uncached quadratic; पूरी generation पर जोड़ें तो O(T2)O(T^2) बनाम O(T3)O(T^3), और ratio बिना सीमा के बढ़ता है। Opening में नौ गुना अंतर 48 tokens पर measure किया गया था — उस table की पहली row से भी कम।

cache यह भी बदल देता है कि memory में क्या होना चाहिए। 8 GB laptop GPU पर fp16 में 256 tokens generate करते हुए, allocator का peak लेकर resident weights घटाने पर:

peak working memory
cache के साथ21.8 MB
recomputing181.7 MB

8.3 गुना ज़्यादा memory, वही tokens और धीरे produce करने पर खर्च हुई। यह अध्याय 5 में किया गया वादा है, अप्रत्याशित दिशा से आता हुआ: वहाँ reverse-mode autodiff को backward pass के लिए हर intermediate alive रखना पड़ता था, और activations training memory पर हावी थे। inference में backward pass नहीं है और उसके लिए retain करने को कुछ नहीं — इसलिए memory पर जो हावी है वह cache है, और यह unavoidable cost नहीं बल्कि deliberate choice है।

Fast run को फिर देखें: उसका पहला token बाकी सैंतालीस जैसा behave नहीं करता था।

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 थी और हर generated token की 166 ms। वही model, वही hardware, वही weights, प्रति token छह गुना अंतर — और दिशा वैसी है जिसकी ज़्यादातर लोग उम्मीद नहीं करते। prompt सस्ता हिस्सा है। Generation दो phases में split होती है जिनकी physics सचमुच अलग है:

पूरे prompt पर एक forward pass। हर token parallel में process होता है, इसलिए हर weight matrix memory से एक बार load होती है और सैकड़ों token vectors की matrix के खिलाफ multiply होती है — matrix-matrix product, moved byte के हिसाब से बहुत arithmetic, जिसके लिए GPU बना है। Prefill compute-bound है, और इसकी लागत prompt length में लगभग linear है।

हर token पर एक forward pass, batch of one और sequence of one। हर weight matrix फिर भी पूरी memory से load होती है, और single vector के खिलाफ multiply होती है — matrix-vector product, moved byte के हिसाब से लगभग कोई arithmetic नहीं। Decode memory-bandwidth-bound है, और प्रति token इसकी लागत context की length पर मुश्किल से निर्भर करती है।

दोनों हिस्से measurable हैं। Prefill, PP tokens पर एक pass:

prompt tokenssecondsms per token
160.351521.97
320.525416.42
641.049116.39
1281.655212.93
2563.096512.10

Decode, CC के cache के खिलाफ एक token:

cached tokensएक token के लिए ms
16110.05
6497.57
256108.53
1024103.86

दूसरी table को दो बार पढ़ें। context के 16 tokens से 1,024 तक जाना — attend करने के लिए चौंसठ गुना ज़्यादा history — ने एक step की लागत को measurably बदला ही नहीं। cache के खिलाफ attention वास्तविक काम है, लेकिन वह आधा billion weights memory bus से खींचकर एक vector produce करने की fixed cost के सामने दब जाता है। वही fixed cost अगले section की हर चीज़ की वजह है।

ये दो phases वे दो numbers पैदा करते हैं जो हर serving system report करता है। Time to first token मूलतः prefill है, और prompt के साथ बढ़ता है, इसलिए लंबी conversation शुरू होने में धीमी लगती है। Tokens per second 1/decode step1/\text{decode step} है, और लगभग constant है, इसलिए reply फिर बराबर बहती है। जो chat धीमी शुरू होती है और फिर smoothly stream करती है, वह rendering trick नहीं है। यह ये दो tables हैं।

cache arithmetic को memory से trade करता है, और उसे जितनी memory चाहिए वह छोटी नहीं है। context के हर token के लिए, हर layer हर key-value head पर एक key vector और एक value vector रखती है:

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 है। इस अध्याय में measured model के लिए — 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 per token है।

इस field के formulae अक्सर factor of two से गलत हो जाते हैं, इसलिए इसे मानने के बजाय allocator के खिलाफ check करें:

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

Exact, और tried हर shape पर exact रहता है:

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

आख़िरी तीन rows को फिर देखें। बत्तीस users जिनमें 2,048 tokens each, चौंसठ में 1,024, एक सौ अट्ठाईस में 512 — cache हर case में 768 MB है, क्योंकि तीनों में 65,536 tokens resident हैं। cache केवल resident tokens की कुल संख्या पर निर्भर करता है, इस पर नहीं कि वे users में कैसे distributed हैं। यही fact batching section की नींव है।

अध्याय 9 ने multi-query और grouped-query attention introduce किए और कारण इस अध्याय तक टाल दिया। कारण वही formula है, और खासकर उसमें HkvH_{kv}

Standard multi-head attention हर query head को अपने key और value heads देता है। यहाँ model में 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 per token होता — 12 KB की जगह 84 KB, ठीक सात गुना ज़्यादा, query heads और key-value heads का ratio।

Multi-query attention1 इसे limit तक ले जाता है: सभी query heads एक single key-value head share करते हैं। Grouped-query attention2 वह compromise है जो जीता — कुछ key-value heads, जिनमें हर एक query heads के एक group द्वारा shared है — क्योंकि MQA की quality loss real थी और GQA की नहीं। इनमें से कोई arithmetic नहीं खरीदता। वे उस formula को integer से divide करने के लिए मौजूद हैं, और long contexts ने cache को binding constraint बनाया तो वे industry में तुरंत फैल गए।

और ऐसा तेज़ी से होता है। 32 layers और dimension 128 के 8 key-value heads वाले 7B-class model के लिए, fp16 में cache 128 KB per token है:

context tokensएक user8 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

उस model के अपने weights fp16 में 13.0 GB हैं, इस अध्याय के अंत वाली table का figure। इसलिए 128,000-token context पर, एक user का cache model से बड़ा है। यही arithmetic अध्याय 16 पैसे में बदलता है, और यही वजह है कि लंबी conversation सिर्फ धीमी नहीं होती — वह request alive रहने तक machine का fixed slice घेरती है।

Batching: जो number ऊपर जाता है और जो नीचे जाता है

सेक्शन का लिंक: Batching: जो number ऊपर जाता है और जो नीचे जाता है

Decode memory-bound है: weights bus से खींचे जाते हैं ताकि एक token produce हो, और arithmetic units idle रहते हैं। तो उसी step में और काम डालें। कई requests एक साथ run करें, और weights, एक बार पढ़े जाने पर, उन सबकी सेवा करें। उसी model पर measured, हर request में 64-token cache है और एक token decode हो रहा है:

batchप्रति step latencythroughputlatency 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

दाईं ओर के दो columns को एक-दूसरे के खिलाफ पढ़ें, क्योंकि पूरा point वही है। एक request से सोलह तक जाने पर throughput 6.0 से multiply होता है और किसी individual request का इंतज़ार 2.67 से multiply होता है। batch ने server को बेहतर और हर user को बदतर बनाया।

यह tune करके हटाने वाली bug नहीं है; यही trade है, और हर side पर इसका नाम है। Latency वह है जो reply का इंतज़ार कर रहा व्यक्ति अनुभव करता है। Throughput वह है जिससे invoice divide होता है। कोई setting दोनों को improve नहीं करती।

यह भी देखें कि यह कहाँ रुकता है। 16 से 32 तक throughput 9 % बढ़ता है जबकि latency लगभग double होती है: step memory-bound रहना बंद कर compute-bound हो गया है, और उस knee के बाद batch कुछ नहीं खरीदता। हर deployment में ऐसा knee होता है; उसकी location आपके setup पर measure करनी होगी, पर उसका existence नहीं।

Static batching अपनी जीत का ज़्यादातर हिस्सा waste करता है

सेक्शन का लिंक: Static batching अपनी जीत का ज़्यादातर हिस्सा waste करता है

Batch करने का naive तरीका है BB requests collect करना, उन्हें साथ run करना, और सबके done होने पर return करना। लेकिन वे साथ finish नहीं करते: कुछ replies बीस tokens के होते हैं और कुछ पाँच सौ के। fixed batch अपने longest member के finish होने तक run करता है, और हर finished request तब तक अपना slot घेरकर padding contribute करती रहती है।

64 requests लें जिनमें output lengths का realistic skew है — median 18 tokens, longest 231, total 1,874 — और eight slots के measured per-step cost पर दोनों policies simulate करें:

policywall clockthroughputप्रति request mean latencywasted slot-steps
8 के static batches176.9 s10.6 tok/s83.2 s3,214
continuous, 8 slots109.0 s17.2 tok/s8.1 s0

Throughput 1.6x improve होता है। Mean latency दस गुना से ज़्यादा improve होती है, क्योंकि static batching में चार steps में finish हुई request भी तब तक इंतज़ार करती है जब तक 231-token neighbour खत्म न हो जाए।

Continuous batching3 fix है, और जितना सुनाई देता है उतना ही simple: batch कोई group नहीं बल्कि slots का set है, और जो slot free होता है वह अगले ही step पर अगली queued request admit करता है। scheduler एक request नहीं बल्कि एक token की granularity पर काम करता है। Production में हर serving stack अब यही करता है।

इसका दूसरा आधा cache है। जो slots आते-जाते हैं वे cache memory fragment कर देते हैं, और हर slot का maximum possible context reserve करना reservation का ज़्यादातर हिस्सा waste करता है। PagedAttention4 operating systems से जवाब उधार लेता है: cache को fixed-size blocks में store करें, हर sequence के लिए block table के साथ, ताकि sequence का cache physically scattered हो सके पर logically contiguous रहे — और इससे shared prefix वाली दो sequences उसे रखने वाले blocks share भी कर सकें। vLLM इसी पर बना है, और इसलिए serving engine असल में transformer attached memory allocator है।

Quantization, और पहली चीज़ जो गलत होती है

सेक्शन का लिंक: Quantization, और पहली चीज़ जो गलत होती है

Bill का दूसरा आधा खुद weights हैं। आधा billion parameters चार bytes each पर 1.98 GB; दो bytes पर 0.99 GB; एक byte पर 0.49 GB। हर weight पर fewer bits model को disk पर छोटा करते हैं, memory में छोटा करते हैं, और — क्योंकि decode bandwidth-bound है — हर step को तेज़ बनाते हैं, क्योंकि move करने को fewer bytes हैं।

सबसे simple scheme symmetric absolute-maximum quantization है, और यह तीन lines में fit होती है:

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 सबसे बड़े integer पर map हो, divide करें, round करें, integers और scale store करें। वापस reconstruct करने के लिए multiply back करें। इसमें clever कुछ नहीं है, और यह काम करता है — जब तक नहीं करता।

model के real weights पर measured: सभी 168 projection matrices, 357.8 million parameters, relative error WW^/W\lVert W - \hat{W}\rVert / \lVert W \rVert:

schememean relative errorworst matrix
INT8, पूरी matrix के लिए एक scale0.04000.1487
INT8, प्रति output row एक scale0.01000.0149
INT4, पूरी matrix के लिए एक scale0.60260.9931
INT4, प्रति output row एक scale0.17900.2589
INT4, 128 के हर group पर एक scale0.13230.1992
NF4, 64 के हर block पर एक scale0.09520.1205
INT3, 128 के हर group पर एक scale0.30440.4123
INT2, 128 के हर group पर एक scale0.77900.8076

चौथी row collapse है। worst matrix पर 0.99 का relative error मतलब reconstruction में original का लगभग कुछ नहीं बचा — matrix सही magnitude के आसपास के noise से replace हो गई है। कारण एक single matrix पर उसी experiment में दिखता है:

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 बाहर है। पूरी matrix के लिए single scale के साथ, वही एक weight उनमें से सभी 4.3 million के लिए step size set करता है। 8 bits पर 256 steps हैं और typical weight फिर भी meaningful step पर land करता है। 4 bits पर 16 हैं, outermost ऐसे value के लिए reserve है जो लगभग किसी के पास नहीं, और ordinary weights — यानी लगभग सभी — दो या तीन distinct levels पर round होते हैं।

उस row के बाद सब एक ही repair है, अलग granularities पर: scale को छोटा territory दें। Per output row error को 3.4 से divide करता है; 128 consecutive weights का per group फिर divide करता है। लागत bookkeeping है — 128 के हर group पर 16-bit scale 4+16/128=4.1254 + 16/128 = 4.125 bits per weight है, 4 की जगह — और यह gap का ज़्यादातर हिस्सा वापस खरीद लेता है।

NF4 दूसरी तरफ़ से आता है।5 Levels equally spaced होने ज़रूरी नहीं। Block के भीतर weights लगभग normally distributed हैं, इसलिए sixteen levels को normal distribution के quantiles की तरह चुनें: zero के पास dense जहाँ weights सच में हैं, tails में sparse जहाँ वे नहीं हैं। वही four bits, वही block scaling, छोटे block पर — group-128 के 4.125 के मुकाबले 4.25 bits per weight — और measured error 0.1323 से 0.0952 हो जाता है, 28 % कम। इसका कुछ हिस्सा finer block है और बाकी mass जहाँ है वहाँ levels रखना; दोनों को अलग करने के लिए तीसरी row चाहिए होगी।

अध्याय 2 के floating-point box ने एक वादा करके खत्म किया था: यह अध्याय weights को 8 और 4 bits में quantize करेगा और कुछ outlier features पाएगा जो squeeze होने से इंकार करेंगे। वे यहाँ हैं, और वे बताते हैं कि activations पर “बस numbers round कर दो” कभी काम करने वाला नहीं था।

ऊपर के weights badly behaved थे। activations बिल्कुल अलग league में हैं। एक ordinary 84-token prompt लें, हर layer पर residual stream capture करें, और measure करें कि 896 dimensions में से हर एक कितनी largest magnitude तक पहुँचती है:

layerlargest |h|median dimension's largest |h|ratio6x median से ऊपर dimensions
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 से आगे कभी नहीं जाता। यह किसी एक token या एक layer का fluke नहीं है: वही dimension layer 4 पर मौजूद है और layer 20 पर भी है, लगभग उसी value के साथ। यही outlier features हैं,6 और ये systematic हैं — trained model की property, input की नहीं।

Layer 16 पर उन 896 per-dimension maxima का histogram shape को unmistakable बना देता है:

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

आठ से नीचे साफ़ pile में नौ सौ dimensions, तीन octaves तक कुछ नहीं, फिर far end पर अकेला एक dimension। अब उस tensor को INT8 में quantize करें और देखें क्या होता है:

schemerelative errorपूरे tensor में इस्तेमाल हुए distinct integer levels
पूरे tensor के लिए एक scale0.1083256 में से 14
प्रति token एक scale (per row)0.0433158
पूरा tensor, 1 outlier dimension fp32 में रखा0.044248
पूरा tensor, 4 outlier dimensions fp32 में रखे0.027957
पूरा tensor, 16 outlier dimensions fp32 में रखे0.0085102

256 में से चौदह levels। Scale 1,579.6 से set हुआ था, इसलिए हर step 12.44 wide है, और typical activation — median magnitude 0.26, ninety-ninth percentile 2.51 — के land करने की जगह ही नहीं है। Per dimension यह और stark है:

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

एक level। पूरा dimension, हर token, एक ही number में quantized। Eight bits allocate किए गए और लगभग zero इस्तेमाल हुए, और model को वे activations पढ़ते समय एक constant दिया जाता है।

यही measurement उन सभी techniques का justification है जिन्हें लोग सच में use करते हैं:

Outliers को इससे बाहर रखें। LLM.int8()6 matrix multiply को decompose करता है: extreme magnitudes वाले dimensions 16 bits में compute होते हैं, बाकी सब INT8 में, और halves sum होते हैं। ऊपर की table receipt है — चार dimensions हटाने से error लगभग चार गुना कटता है। SmoothQuant7 इसके बजाय difficulty migrate करता है: activations को per-channel factor से divide करें और matching weight column को उससे multiply करें, जिससे product unchanged रहता है और outlier उस tensor से बाहर चला जाता है जो उसे absorb नहीं कर सकता, उस tensor में जो कर सकता है।

Rounding चुनें, सिर्फ round न करें। ऊपर कुछ भी यह नहीं पूछता कि matrix किस लिए है। GPTQ8 column by column quantize करता है और हर column के बाद बाकी full-precision columns को already committed error compensate करने के लिए adjust करता है — real inputs पर layer के output का error minimise करता है, weights का नहीं। AWQ9 note करता है कि weight channels का एक छोटा fraction बाकी से कहीं ज़्यादा matter करता है, उन्हें activation statistics से find करता है, और quantizing से पहले scale up करता है ताकि वे finer levels पर land करें। दोनों को calibration set चाहिए; किसी को gradients नहीं चाहिए।

विवरण दिखाएँ

GGUF, और file format का इस सब से क्या लेना-देना है।

GGUF quantization method नहीं है; यह वह container है जिसे llama.cpp use करता है, और gguf vs gptq comparisons में confusion दोनों को एक ही तरह की चीज़ मानने से आती है। GGUF tensors, tokenizer, architecture metadata और chat template को एक memory-mappable file में रखता है, और अपने अंदर block schemes की एक family carry करता है — Q4_K_M जैसे names bits per weight, block size, और क्या कुछ tensors higher precision पर रखे गए हैं, encode करते हैं।

जो engineering difference matter करता है: GPTQ और AWQ GPU kernel के लिए optimised weights produce करते हैं, जबकि GGUF के schemes CPU पर file mapped रहते हुए, loaded होने के बजाय, cheaply decode होते हैं। इसलिए वही nominal “4-bit 7B model” दोनों worlds में अलग sizes और अलग quality पर मौजूद है, और इसलिए honest comparison कभी format नहीं होता — वह नीचे की measurement है, आपके अपने task पर run की गई।

Quantization पर लगभग हर article पिछले section पर रुक जाता है: method समझाता है, compression ratio quote करता है, और assert करता है कि quality “largely preserved” है। अध्याय 4 खुद को fool न करने के बारे में था, तो पता लगाते हैं।

वही model, हर scheme के साथ weights in place quantized, फिर तीन measurements: held-out English prose के 2,048 tokens पर perplexity — यहाँ इस course का draft, इसलिए repository एक fixed public-domain book substitute करती है और समान shape की table अलग numbers के साथ print करती है — greedy decoding के तहत known answers वाले 16 short factual questions की battery, और identical context given होने पर quantized model कितने fraction tokens पर full-precision one से agree करता है।

schememean weight errorperplexityquestion batteryfp32 से agrees
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 %

उस table में चार बातें साफ़-साफ़ कहने लायक हैं।

INT8 ठीक से किया जाए तो free है। Per-row INT8 reference के 23.08 के मुकाबले 22.96 score करता है — दो सौ में एक part का gap, जो noise है और “identical” पढ़ा जाना चाहिए। Noise किस दिशा में point करता है, stable नहीं है: repository के public-domain corpus पर वही दो schemes 22.18 के मुकाबले 22.24 आते हैं: आधी distance, और दूसरी दिशा में। यह 144 generated tokens में से 142 पर full-precision model से agree करता है। fp32 reference के मुकाबले memory का एक quarter, fp16 के मुकाबले आधी जिसे आप वास्तव में deploy करेंगे, और कोई detectable cost नहीं। INT8 carelessly किया जाए तो भी लगभग free है: प्रति matrix एक scale 0.5 perplexity points और battery answers में कोई cost नहीं। Eight bits इतने forgiving हैं कि granularity मुश्किल से matter करती है, और यही वजह है कि लोग INT8 से INT4 तक generalise करते हैं और चोट खाते हैं।

प्रति tensor एक scale वाला INT4 model को नष्ट कर देता है। Perplexity 365 million: degraded नहीं, annihilated। फिर granularity ही पूरा game है — per-tensor 365,416,000, per-row 46.18, per-group-of-128 31.08, NF4 24.55। प्रति weight वही four bits, worst और best के बीच पंद्रह million का factor।

Perplexity coarse instrument है और battery उससे भी coarser। NF4 और group-128 INT4 के बीच perplexity gap 6.5 points है और battery में एक question का फर्क — और अध्याय 4 का confidence interval कहता है कि सोलह में एक question बिल्कुल कुछ distinguish नहीं करता। Interval से भी sharp demonstration है: वही battery model की stock repetition penalty switched off के साथ run करें, जो greedy decoding का असली मतलब है, और वे दो rows जगह बदल लेते हैं। सोलह में एक question छोटा effect नहीं, no effect है। अध्याय 8 की warning भी लागू होती है: perplexity केवल उन models के बीच comparable है जो tokenizer share करते हैं, इसलिए किसी और की write-up का number आपके number से compare नहीं किया जा सकता।

Agreement column तीनों में सबसे sharp है, और लगभग free: full-precision model greedily run करें, फिर quantized one से हर position पर पूछें कि same prefix given होने पर वह क्या चुनता। इसमें 16 की जगह 144 independent observations हैं, ground truth नहीं चाहिए, और battery जहाँ jumps में degrade होती है वहाँ यह smoothly degrade होता है। यह ठीक वही quantity भी है जिसकी अगले section को ज़रूरत है।

यह अध्याय 1 का इस अध्याय के बारे में किया गया वादा है, समय पर आता हुआ: mathematics कहती है 4-bit model possible है, और engineering तय करती है कि वह usable है या नहीं।

अध्याय 12 ने इसे announce किया था और bill यहाँ छोड़ दिया था।

Idea सीधे prefill/decode split से आता है। γ\gamma tokens की proposed sequence verify करने की लागत γ\gamma positions पर एक forward pass है — matrix-matrix product, एक पर pass से मुश्किल से ज़्यादा expensive। तो:

एक छोटा, सस्ता model autoregressively γ\gamma candidate tokens generate करता है।

बड़ा model सभी γ\gamma candidates पर एक साथ एक forward pass run करता है, यह produce करते हुए कि वह हर position पर क्या कहता।

सबसे लंबा prefix रखें जिस पर दोनों agree करते हैं, साथ में वह token जो बड़ा model first disagreement पर free में supply करता है। बाकी discard करें और फिर शुरू करें।

Output distribution unchanged है। Greedy decoding के साथ यह obvious है — token तभी accepted है अगर target उसे produce करता। Sampling के साथ modified acceptance rule चाहिए, और Leviathan et al. prove करते हैं कि resulting distribution ठीक target का है।10 यह इस अध्याय की दूसरी exact optimisation है।

इसलिए सब कुछ acceptance rate α\alpha पर टिका है, जो measurable है — यह ऊपर वाला agreement column है, इसलिए वहाँ compute किया गया। हर quantized model को full-precision target के draft के रूप में use करते हुए, 144 generated positions पर:

draft modelacceptancelongest accepted runtarget pass per expected tokens, γ=4\gamma = 4
fp32 (target खुद)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

Draft length γ\gamma पर, प्रति verification pass accepted expected tokens है

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

और net speedup इसे draft की अपनी cost से divide करता है, target per token का fraction cc:

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

Bold entry याद रखने वाली है: speculative decoding generation को धीमा कर सकता है। 30 % acceptance पर, target के पाँचवें हिस्से की cost वाले draft के साथ, आप पाँच forward passes के पैसे देते हैं और 1.4 tokens रखते हैं। आख़िरी column दूसरा trap है — लंबा draft तभी मदद करता है जब acceptance high हो, क्योंकि γ\gamma-token guess की tail तक लगभग कभी पहुँचा नहीं जाता। 90 % acceptance पर γ=8\gamma = 8 3.40x के लायक है और 30 % पर 0.79x: वही configuration, आपके traffic पर measured number के हिसाब से win या loss।

Quantization model को उसी function को fewer bits में store करके shrink करता है। Distillation उसे छोटे model को बड़े model की नकल करना train करके shrink करता है11 — ऐसा idea जो deep learning से लगभग एक decade पुराना है।12

Subtle हिस्सा यह है कि student किससे सीखता है। Correct answer से नहीं: उस पर directly train किया जा सकता था। Teacher जो जोड़ता है वह whole distribution है। Model से पूछें कि 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 — अभी भी live था। यही original argument है: यह 7 है, लेकिन 1 जैसा काफी दिखता है, और resemblance information है जिसे hard label फेंक देता है।

यही वजह है कि distillation temperature use करता है। softmax से पहले logits को TT से divide करना distribution को flatten करता है और runners-up का relative weight बढ़ाता है: इस phrase पर, top token और third के बीच ratio T=1T = 1 पर 2.24 से T=2T = 2 पर 1.50 तक गिरता है — पहले का square root, जो logits को दो से divide करने पर ratio के साथ होता है। वही ordering, loss का ज़्यादा attention near misses पर। Student का gradient teacher की uncertainty carry करता है, सिर्फ verdict नहीं।

इस अध्याय की हर चीज़ अब एक sum है:

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 सभी concurrent requests में resident total tokens है। इसे apply करें: 7B और 70B rows dimension 128 के 8 key-value heads assume करती हैं, 13B row 40 heads के साथ full multi-head attention, जैसे model की वे generations बनी थीं — और यह दिखता है।

8 GB

modelprecisionweightsoverhead के बाद freefit होने वाले context tokens
7Bfp1613.0 GBfit नहीं होता
7Bint86.5 GBfit नहीं होता
7Bint4 (g128)3.4 GB3.1 GB25,710
13Bint4 (g128)6.2 GB0.3 GB337
70Bint4 (g128)33.6 GBfit नहीं होता

16 GB

modelprecisionweightsoverhead के बाद freefit होने वाले context tokens
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

modelprecisionweightsoverhead के बाद freefit होने वाले context tokens
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 GBfit नहीं होता

8 GB table में 13B row देखें। weights fit होते हैं — 8 में से 6.2 GB — इसलिए usual बात करने के तरीके से 13B model “8 GB card पर चलता है”। उसके पास 337 tokens का context है, जो conversation नहीं बल्कि मुश्किल से prompt है। “क्या यह fit होता है” गलत सवाल है। सही सवाल है “कितने context के साथ, और एक साथ कितने users के लिए”।

16 GB की दो int8 rows भी देखें। 7B को 65,378 tokens मिलते हैं और 13B को 3,136 — extra weights के 5.6 GB से twenty-fold difference, क्योंकि यहाँ 13B में multi-head attention है और उसके cache की लागत 7B के 128 KB के मुकाबले 800 KB per token है। Similar size के दो models, एक long context के लिए unusable, ऐसी वजह से जो किसी model card की headline में नहीं दिखती।

तेरह अध्याय पहले यह दो weights और एक bias वाला perceptron था। अब यह transformer है जिसे design, train, align किया गया है, hard questions पर compute खर्च करना सिखाया गया है, और प्रति token measured cost पर serve किया गया है — और इसमें कोई box unopened नहीं बचा।

यह यहीं खत्म होता है, और जानबूझकर खत्म होता है।

अध्याय 14 model के कहीं और होने से शुरू होता है। आपके process में नहीं, आपकी memory में नहीं, किसी variable में नहीं जिसे आप print कर सकें: ऐसी machine पर जिसे आप administer नहीं करते, API key, port और bill के पीछे। यहाँ measure की गई हर चीज़ अभी भी हो रही है — पहला token आने से पहले prefill अब भी run होता है, cache अब भी conversation के साथ बढ़ता है, जिस batch में आप हैं वह अब भी किसी और का है और अब भी आपकी latency decide करता है — लेकिन अब से आप उसे Server-Sent Events की stream, एक finish_reason, और Retry-After header वाले HTTP 429 के ज़रिए observe करते हैं। Vantage point के साथ questions बदलते हैं: यह gradient कैसे compute होता है नहीं, बल्कि मेरा invoice तीन गुना क्यों हो गया। भाषा भी बदलती है, और अध्याय 14 उस rule को announce करने के बजाय explain करता है — यहाँ तक code weights, gradients, logits और tokenizer bytes hold करता था; वहाँ से आगे वह connection, retry, cancellation और accumulated state hold करता है। आपके पीछे के तेरह अध्याय crossing से discard नहीं होते। वे port के दूसरी तरफ़ चल रही चीज़ का description हैं।


दो omissions deliberate हैं। FlashAttention (Dao et al., arXiv:2205.14135) कोई अलग attention नहीं है — यह operation को tile करके same function compute करता है ताकि n×nn \times n score matrix कभी memory में लिखी ही न जाए, इसलिए इस अध्याय की दूसरी table में 67 MB practice में arithmetic के suggest करने से छोटा है। और kernels खुद delegated हैं: Stanford की CS336 का lecture 10 inference systems को उस depth में cover करता है जहाँ यह जाने की कोशिश नहीं करता, और llama.cpp repository और GGUF specification CPU side के primary sources हैं।

  1. Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019). Paper largely memory-bandwidth argument है, और वैसा ही पढ़ता है।

  2. Ainslie, J. et al. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023). इसमें uptraining recipe शामिल है जो existing multi-head checkpoint convert करती है, इसलिए 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 introduce करता है।

  4. Kwon, W. et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. arXiv:2309.06180 (2023), SOSP 2023. वह paper जिस पर vLLM बना है; §3 पूरी operating-systems analogy है।

  5. Dettmers, T., Pagnoni, A., Holtzman, A. and Zettlemoyer, L. QLoRA: Efficient Finetuning of Quantized LLMs. arXiv:2305.14314 (2023). NF4 §3 में defined है; ऊपर की measurement में इस्तेमाल sixteen level values वही हैं जो यह 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). §4 में outlier-feature analysis ऊपर measured phenomenon का source है, जिसमें यह finding भी शामिल है कि outliers scale पर systematically emerge होते हैं। 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 unchanged है; Chen et al. (arXiv:2302.01318) ने same idea independently publish किया।

  11. Hinton, G., Vinyals, O. and Dean, J. Distilling the Knowledge in a Neural Network. arXiv:1503.02531 (2015). Temperature और “dark knowledge” argument।

  12. Buciluă, C., Caruana, R. and Niculescu-Mizil, A. Model Compression. KDD 2006. Distillation, नौ साल पहले, transformers के बजाय ensembles के लिए।


निर्माता

David Vicente Campos

NeuraLIA Labs के संस्थापक और MyRealFood के सह-संस्थापक

मैं लेओन विश्वविद्यालय से कंप्यूटर इंजीनियर हूँ। मैंने MyRealFood की सह-स्थापना की, जहाँ CTO के रूप में मैंने वह ऐप बनाया जिसे लाखों लोग बेहतर खान-पान के लिए इस्तेमाल कर चुके हैं, और मैंने NeuraLIA Labs की स्थापना की, जहाँ मैं AI प्रोडक्ट्स बनाता हूँ। यहाँ मैं उन बातों के बारे में लिखता हूँ जो इस सफ़र में मुझे समझनी पड़ीं, उस तरह जिस तरह काश किसी ने मुझे समझाई होतीं।

लेखक के बारे में और जानें

NeuraLIA Labs द्वारा प्रकाशित।

नए पोस्ट अपने इनबॉक्स में पाएं

AI समाचार, गाइड और प्रोडक्ट अपडेट — जब भी हम कुछ उपयोगी प्रकाशित करें, एक छोटा ईमेल।

कोर्स सूची

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev13 मिनट पढ़ें

Jev AI मॉडल गद्य के लिए नहीं, निर्णयों के लिए बना है

TypeSafe AI का Jev ध्यान खींच रहा है क्योंकि यह सॉफ्टवेयर इंटेलिजेंस को संभावना की समस्या मानता है: सही शाखा चुनें, भरोसे का स्तर जोड़ें, और जब कोड को निर्णय चाहिए तो LLM से टेक्स्ट लिखवाने पर खर्च न करें।

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering14 मिनट पढ़ें

लॉन्ग-होराइजन AI एजेंट्स के लिए कॉन्टेक्स्ट इंजीनियरिंग

लंबे समय तक चलने वाले एजेंट सिर्फ इसलिए असफल नहीं होते कि विंडो छोटी है। वे तब असफल होते हैं जब फ़ाइलें, टूल आउटपुट और पुराना इतिहास उस काम को ही पीछे धकेल देते हैं जिसे एजेंट को पूरा करना था।

मॉडल चुनने का काम LIA पर छोड़ने के लिए तैयार हैं?

हर AI मॉडल एक ही जगह — आज ही मुफ़्त शुरू करें।