Φθηνότερο inference: KV cache, batching και quantization
Ίδιο μοντέλο, ίδια έξοδος σε 8,8 ή 78,9 δευτ. Μετά INT4, μετρημένο με τρεις τρόπους, όχι απλώς ισχυρισμοί.
Σε αυτή τη σελίδα
Το ίδιο μοντέλο, στο ίδιο μηχάνημα, απαντά στην ίδια ερώτηση με τα ίδια 48 tokens. Οι δύο έξοδοι είναι πανομοιότυπες token προς token — ελέγχθηκε, δεν θεωρήθηκε δεδομένο.
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 ή στην αριθμητική δεν είναι διαφορετικό, και το δεύτερο run δεν είναι πιο ακριβές για τον κόπο του. Είναι εννέα φορές πιο αργό χωρίς λόγο.
Αυτή είναι η μορφή αυτού του κεφαλαίου. Όλα όσα περιέχει — το cache, το batch, τα quantized weights — είναι μια προσπάθεια να πάψουμε να πληρώνουμε για εργασία που δεν αλλάζει την απάντηση, ή να μάθουμε τι κοστίζει μια φθηνότερη απάντηση. Το Κεφάλαιο 10 καθόρισε τον τιμοκατάλογο για το training. Αυτός είναι ο τιμοκατάλογος για την πλευρά που πληρώνετε για πάντα: ένα deployed μοντέλο ξοδεύει περίπου FLOPs για κάθε token που εκπέμπει, σε κάθε αίτημα, για όλη την υπόλοιπη ζωή του.
Πού πήγε ο χρόνος του δεύτερου run
Σύνδεσμος στην ενότητα: Πού πήγε ο χρόνος του δεύτερου runΓια να παραγάγει ένα token, ένας decoder-only transformer παίρνει ολόκληρη την ακολουθία μέχρι εκείνη τη στιγμή, την περνά από κάθε layer και διαβάζει την κατανομή πιθανοτήτων από την τελευταία θέση. Έπειτα προσθέτει το επιλεγμένο token και το ξανακάνει. Αυτή η περιγραφή είναι σωστή, και αυτό κάνει το αργό run.
Είναι επίσης τεράστια σπάταλη, και ο λόγος είναι η causal mask από το Κεφάλαιο 9. Τα key και value vectors της θέσης 7 υπολογίζονται από το input της θέσης 7 και τις θέσεις πριν από αυτή. Όταν φτάσει η θέση 8, η θέση 7 δεν μπορεί να τη δει — αυτό σημαίνει causal — άρα τα key και value της θέσης 7 είναι ακριβώς οι ίδιοι αριθμοί όπως πριν. Το αργό run τους ξαναυπολογίζει παρ’ όλα αυτά, σε κάθε βήμα.
Οπότε αποθηκεύστε τους. Αυτή η αποθήκευση είναι το key-value cache, η πιο καθοριστική optimization στο serving γλωσσικών μοντέλων:
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. Όχι η ακολουθία. Το query του νέου token κάνει attend σε κάθε cached key, και τα cached keys δεν επρόκειτο ποτέ να αλλάξουν. Αυτό δεν είναι προσέγγιση — το παραπάνω identical-output check είναι το νόημα. Το cache δεν ανταλλάσσει ποιότητα με ταχύτητα· διαγράφει περιττή αριθμητική.
Για να δείτε καθαρά το scaling, αφαιρέστε τον transformer και χρονομετρήστε ένα μόνο attention head με , ένα βήμα generation υπολογισμένο και με τους δύο τρόπους:
| tokens στο context | επαναϋπολογισμός όλων | με cache | ratio | score matrix |
|---|---|---|---|---|
| 128 | 0.59 ms | 0.062 ms | 10x | 65,536 B vs 512 B |
| 256 | 1.20 ms | 0.163 ms | 7x | 262,144 B vs 1,024 B |
| 512 | 7.03 ms | 0.078 ms | 90x | 1,048,576 B vs 2,048 B |
| 1024 | 17.31 ms | 0.114 ms | 152x | 4,194,304 B vs 4,096 B |
| 2048 | 59.83 ms | 0.214 ms | 279x | 16,777,216 B vs 8,192 B |
| 4096 | 236.18 ms | 0.284 ms | 832x | 67,108,864 B vs 16,384 B |
Η δεξιά στήλη είναι η αιτία. Ο επαναϋπολογισμός φτιάχνει ολόκληρο το attention matrix σε κάθε βήμα — το από το πλαίσιο ασυμπτωτικού συμβολισμού του Κεφαλαίου 9, πληρωμένο μία φορά ανά token. Με το cache φτιάχνετε αντί γι’ αυτό μία σειρά : στα 4.096 tokens, 67 MB scores αντί για 16 KB.
Το να μετράμε multiply-accumulates αντί για milliseconds αφαιρεί το μηχάνημα από το επιχείρημα. Για να παραχθούν tokens από cold start:
| tokens που παράγονται | με cache | με επαναϋπολογισμό | ratio |
|---|---|---|---|
| 128 | 2.6 M | 192.0 M | 73x |
| 512 | 23.1 M | 7.36 G | 318x |
| 2048 | 293.7 M | 392.6 G | 1,336x |
Ανά βήμα, η cached εκδοχή είναι γραμμική στο context και η uncached τετραγωνική· αθροισμένα σε ένα generation, έναντι , με το ratio να αυξάνεται χωρίς όριο. Η εννεαπλάσια διαφορά στην αρχή μετρήθηκε σε 48 tokens — λιγότερα από την πρώτη γραμμή αυτού του πίνακα.
Το cache αλλάζει επίσης τι πρέπει να βρίσκεται στη μνήμη. Σε laptop GPU 8 GB που παράγει 256 tokens σε fp16, παίρνοντας το peak του allocator και αφαιρώντας τα resident weights:
| peak working memory | |
|---|---|
| με cache | 21.8 MB |
| με επαναϋπολογισμό | 181.7 MB |
8,3 φορές περισσότερη μνήμη, ξοδεμένη για να παραχθούν τα ίδια tokens πιο αργά. Αυτή είναι η υπόσχεση που δόθηκε στο Κεφάλαιο 5, φτάνοντας από απρόσμενη κατεύθυνση: εκεί, το reverse-mode autodiff έπρεπε να κρατά κάθε intermediate ζωντανό για το backward pass, και τα activations κυριαρχούσαν στη μνήμη του training. Στο inference δεν υπάρχει backward pass και τίποτα που πρέπει να διατηρηθεί γι’ αυτό — άρα αυτό που κυριαρχεί στη μνήμη είναι αντίθετα το cache, και είναι σκόπιμη επιλογή, όχι αναπόφευκτο κόστος.
Prefill και decode είναι δύο διαφορετικά μηχανήματα
Σύνδεσμος στην ενότητα: Prefill και decode είναι δύο διαφορετικά μηχανήματαΚοιτάξτε ξανά το γρήγορο run: το πρώτο token του συμπεριφέρθηκε διαφορετικά από τα άλλα σαράντα επτά.
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. Ίδιο μοντέλο, ίδιο hardware, ίδια weights, εξαπλάσια διαφορά ανά token — και προς την κατεύθυνση που οι περισσότεροι δεν περιμένουν. Το prompt είναι το φθηνό μέρος. Το generation χωρίζεται σε δύο φάσεις με πραγματικά διαφορετική φυσική:
Ένα forward pass πάνω σε ολόκληρο το prompt. Κάθε token επεξεργάζεται παράλληλα, άρα κάθε weight matrix φορτώνεται από τη μνήμη μία φορά και πολλαπλασιάζεται με ένα matrix εκατοντάδων token vectors — ένα matrix-matrix product, με πολλή αριθμητική ανά byte που μετακινείται, ακριβώς αυτό για το οποίο είναι φτιαγμένη μια GPU. Το prefill είναι compute-bound, και το κόστος του είναι περίπου γραμμικό στο μήκος του prompt.
Ένα forward pass ανά token, batch του ενός και ακολουθία του ενός. Κάθε weight matrix εξακολουθεί να φορτώνεται ολόκληρο από τη μνήμη, και πολλαπλασιάζεται με ένα μοναδικό vector — ένα matrix-vector product, με σχεδόν καθόλου αριθμητική ανά byte που μετακινείται. Το decode είναι memory-bandwidth-bound, και το κόστος του ανά token σχεδόν δεν εξαρτάται από το μήκος του context.
Και τα δύο μισά είναι μετρήσιμα. Prefill, ένα pass πάνω από tokens:
| prompt tokens | δευτερόλεπτα | ms ανά token |
|---|---|---|
| 16 | 0.3515 | 21.97 |
| 32 | 0.5254 | 16.42 |
| 64 | 1.0491 | 16.39 |
| 128 | 1.6552 | 12.93 |
| 256 | 3.0965 | 12.10 |
Decode, ένα token απέναντι σε cache :
| cached tokens | ms για ένα token |
|---|---|
| 16 | 110.05 |
| 64 | 97.57 |
| 256 | 108.53 |
| 1024 | 103.86 |
Διαβάστε τον δεύτερο πίνακα δύο φορές. Η μετάβαση από 16 tokens context σε 1.024 — εξήντα τέσσερις φορές περισσότερο ιστορικό για attention — άλλαξε το κόστος ενός βήματος κατά τίποτα μετρήσιμο. Το attention απέναντι στο cache είναι πραγματική δουλειά, αλλά επισκιάζεται από το σταθερό κόστος του να σύρεις μισό δισεκατομμύριο weights μέσα από το memory bus για να παραγάγεις ένα vector. Αυτό το σταθερό κόστος είναι ο λόγος για όλα στην επόμενη ενότητα.
Αυτές οι δύο φάσεις είναι η προέλευση των δύο αριθμών που αναφέρει κάθε serving system. Time to first token είναι ουσιαστικά το prefill, και αυξάνεται με το prompt, γι’ αυτό μια μεγάλη συνομιλία αργεί να ξεκινήσει. Tokens per second είναι , και είναι περίπου σταθερό, γι’ αυτό η απάντηση έπειτα ρέει ομοιόμορφα. Ένα chat που ξεκινά αργά και μετά κάνει stream ομαλά δεν είναι rendering trick. Είναι αυτοί οι δύο πίνακες.
Το cache είναι και ο λογαριασμός
Σύνδεσμος στην ενότητα: Το cache είναι και ο λογαριασμόςΤο cache ανταλλάσσει αριθμητική με μνήμη, και η μνήμη που θέλει δεν είναι μικρή. Για κάθε token στο context, κάθε layer κρατά ένα key vector και ένα value vector ανά key-value head:
Το 2 είναι για keys και values· όλα τα άλλα είναι η αρχιτεκτονική. Για το μοντέλο που μετριέται σε όλο αυτό το κεφάλαιο — 24 layers, 14 query heads, 2 key-value heads, head dimension 64 — σε fp16 αυτό είναι bytes ανά token.
Οι φόρμουλες σε αυτό το πεδίο έχουν την τάση να πέφτουν έξω κατά έναν παράγοντα δύο, οπότε ελέγξτε το απέναντι στον allocator αντί να το πιστέψετε:
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 που δοκιμάστηκε:
| batch | context | measured cache | predicted | peak working memory |
|---|---|---|---|---|
| 1 | 512 | 6.0 MB | 6.0 MB | 15.4 MB |
| 1 | 16,384 | 192.0 MB | 192.0 MB | 207.3 MB |
| 1 | 65,536 | 768.0 MB | 768.0 MB | 793.7 MB |
| 8 | 4,096 | 384.0 MB | 384.0 MB | 401.5 MB |
| 32 | 2,048 | 768.0 MB | 768.0 MB | 794.2 MB |
| 64 | 1,024 | 768.0 MB | 768.0 MB | 797.0 MB |
| 128 | 512 | 768.0 MB | 768.0 MB | 816.4 MB |
Οι τρεις τελευταίες γραμμές αξίζουν δεύτερη ματιά. Τριάντα δύο χρήστες με 2.048 tokens ο καθένας, εξήντα τέσσερις με 1.024, εκατόν είκοσι οκτώ με 512 — το cache είναι 768 MB σε κάθε περίπτωση, επειδή και οι τρεις κρατούν 65.536 tokens. Το cache εξαρτάται μόνο από τον συνολικό αριθμό resident tokens, όχι από το πώς κατανέμονται στους χρήστες. Αυτό το γεγονός είναι το θεμέλιο της ενότητας για το batching.
Από πού έρχονται τα MQA και GQA
Σύνδεσμος στην ενότητα: Από πού έρχονται τα MQA και GQAΤο Κεφάλαιο 9 εισήγαγε multi-query και grouped-query attention και ανέβαλε τον λόγο για αυτό το κεφάλαιο. Ο λόγος είναι αυτή η φόρμουλα, και συγκεκριμένα το μέσα της.
Το standard multi-head attention δίνει σε κάθε query head τα δικά του key και value heads. Το μοντέλο εδώ έχει 14 query heads· με πλήρες multi-head attention, το cache του θα ήταν 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 — επειδή η απώλεια ποιότητας του MQA ήταν πραγματική και του GQA όχι. Κανένα δεν αγοράζει αριθμητική. Υπάρχουν για να διαιρούν εκείνη τη φόρμουλα με έναν ακέραιο, και εξαπλώθηκαν στη βιομηχανία τη στιγμή που τα μεγάλα contexts έκαναν το cache τον δεσμευτικό περιορισμό.
Και το κάνει, γρήγορα. Για ένα μοντέλο κλάσης 7B με 32 layers και 8 key-value heads διάστασης 128, το cache είναι 128 KB ανά token σε fp16:
| context tokens | ένας χρήστης | 8 χρήστες | 64 χρήστες |
|---|---|---|---|
| 4,000 | 0.49 GB | 3.91 GB | 31.2 GB |
| 32,000 | 3.91 GB | 31.25 GB | 250.0 GB |
| 128,000 | 15.62 GB | 125.00 GB | 1,000.0 GB |
| 1,000,000 | 122.07 GB | 976.56 GB | 7,812.5 GB |
Τα ίδια weights του μοντέλου είναι 13.0 GB σε fp16, ο αριθμός στον πίνακα στο τέλος αυτού του κεφαλαίου. Άρα σε context 128.000 tokens, το cache ενός χρήστη είναι μεγαλύτερο από το μοντέλο. Αυτή είναι η αριθμητική που το Κεφάλαιο 16 μετατρέπει σε χρήμα, και γι’ αυτό μια μεγάλη συνομιλία δεν είναι απλώς αργή — καταλαμβάνει ένα σταθερό κομμάτι ενός μηχανήματος όσο το αίτημα είναι ζωντανό.
Batching: ο αριθμός που ανεβαίνει και ο αριθμός που πέφτει
Σύνδεσμος στην ενότητα: Batching: ο αριθμός που ανεβαίνει και ο αριθμός που πέφτειΤο decode είναι memory-bound: τα weights σύρονται μέσα από το bus για να παραγάγουν ένα token, και οι αριθμητικές μονάδες μένουν αδρανείς. Βάλτε λοιπόν περισσότερη δουλειά στο ίδιο βήμα. Τρέξτε αρκετά αιτήματα μαζί, και τα weights, διαβασμένα μία φορά, εξυπηρετούν όλα τους. Μετρημένο στο ίδιο μοντέλο, με κάθε αίτημα να κρατά 64-token cache και να κάνει decode ένα token:
| batch | latency ανά βήμα | throughput | latency vs B=1 |
|---|---|---|---|
| 1 | 0.1286 s | 7.78 tok/s | 1.00x |
| 2 | 0.1839 s | 10.88 tok/s | 1.43x |
| 4 | 0.1909 s | 20.95 tok/s | 1.49x |
| 8 | 0.2781 s | 28.76 tok/s | 2.16x |
| 16 | 0.3430 s | 46.64 tok/s | 2.67x |
| 32 | 0.6302 s | 50.78 tok/s | 4.90x |
Διαβάστε τις δύο δεξιές στήλες τη μία απέναντι στην άλλη, επειδή αυτό είναι όλο το νόημα. Η μετάβαση από ένα αίτημα σε δεκαέξι πολλαπλασιάζει το throughput επί 6,0 και πολλαπλασιάζει την αναμονή για κάθε μεμονωμένο αίτημα επί 2,67. Το batch έκανε τον server καλύτερο και κάθε χρήστη χειρότερο.
Αυτό δεν είναι bug που θα φύγει με tuning· είναι το ίδιο το trade-off, και έχει όνομα σε κάθε πλευρά. Latency είναι αυτό που βιώνει ένας άνθρωπος που περιμένει απάντηση. Throughput είναι αυτό με το οποίο διαιρείται το τιμολόγιο. Καμία ρύθμιση δεν βελτιώνει και τα δύο.
Σημειώστε επίσης πού σταματά. Από 16 σε 32, το throughput κερδίζει 9 % ενώ το latency σχεδόν διπλασιάζεται: το βήμα έπαψε να είναι memory-bound και έγινε compute-bound, και πέρα από εκείνο το knee το batch δεν αγοράζει τίποτα. Κάθε deployment έχει ένα τέτοιο knee· η θέση του πρέπει να μετρηθεί στο δικό σας, αλλά η ύπαρξή του όχι.
Το static batching σπαταλά το μεγαλύτερο μέρος όσων κερδίζει
Σύνδεσμος στην ενότητα: Το static batching σπαταλά το μεγαλύτερο μέρος όσων κερδίζειΟ αφελής τρόπος για batching είναι να συλλέξετε αιτήματα, να τα τρέξετε μαζί και να επιστρέψετε όταν ολοκληρωθούν όλα. Αλλά δεν τελειώνουν μαζί: κάποιες απαντήσεις είναι είκοσι tokens και κάποιες πεντακόσια. Ένα fixed batch τρέχει μέχρι να τελειώσει το μεγαλύτερο μέλος του, και κάθε αίτημα που έχει ήδη ολοκληρωθεί συνεχίζει να καταλαμβάνει τη θέση του, συνεισφέροντας padding, μέχρι τότε.
Πάρτε 64 αιτήματα με ρεαλιστική ασυμμετρία στα μήκη εξόδου — median 18 tokens, longest 231, 1.874 συνολικά — και προσομοιώστε και τις δύο πολιτικές με το μετρημένο per-step cost για οκτώ slots:
| πολιτική | wall clock | throughput | mean latency ανά αίτημα | wasted slot-steps |
|---|---|---|---|---|
| static batches των 8 | 176.9 s | 10.6 tok/s | 83.2 s | 3,214 |
| continuous, 8 slots | 109.0 s | 17.2 tok/s | 8.1 s | 0 |
Το throughput βελτιώνεται 1,6x. Το mean latency βελτιώνεται πάνω από δέκα φορές, επειδή στο static batching ένα αίτημα που τελείωσε σε τέσσερα βήματα εξακολουθεί να περιμένει έναν γείτονα 231 tokens πριν το ακούσει κανείς.
Το continuous batching3 είναι η λύση, και είναι τόσο απλό όσο ακούγεται: το batch δεν είναι ομάδα αλλά σύνολο slots, και ένα slot που ελευθερώνεται δέχεται το επόμενο queued αίτημα στο αμέσως επόμενο βήμα. Ο scheduler δουλεύει στο granularity ενός token αντί ενός αιτήματος. Κάθε serving stack σε production το κάνει πλέον αυτό.
Έχει και δεύτερο μισό, που είναι το cache. Slots που έρχονται και φεύγουν αφήνουν τη cache memory fragmented, και το να δεσμεύεις για κάθε slot το μέγιστο δυνατό context σπαταλά το μεγαλύτερο μέρος της δέσμευσης. Το PagedAttention4 δανείζεται την απάντηση από τα λειτουργικά συστήματα: αποθηκεύει το cache σε fixed-size blocks με block table ανά sequence, ώστε το cache μιας sequence να μπορεί να είναι φυσικά διάσπαρτο ενώ παραμένει λογικά συνεχόμενο — κάτι που επίσης επιτρέπει σε δύο sequences με shared prefix να μοιραστούν τα blocks που το κρατούν. Αυτό είναι το θεμέλιο του vLLM, και ο λόγος που ένα serving engine είναι memory allocator με έναν transformer προσαρτημένο.
Quantization, και το πρώτο πράγμα που πάει στραβά
Σύνδεσμος στην ενότητα: Quantization, και το πρώτο πράγμα που πάει στραβάΤο άλλο μισό του λογαριασμού είναι τα ίδια τα weights. Μισό δισεκατομμύριο parameters σε τέσσερα bytes το καθένα είναι 1.98 GB· σε δύο bytes, 0.99 GB· σε ένα byte, 0.49 GB. Λιγότερα bits ανά weight μικραίνουν το μοντέλο στον δίσκο, το μικραίνουν στη μνήμη και — επειδή το decode είναι bandwidth-bound — κάνουν κάθε βήμα γρηγορότερο, αφού υπάρχουν λιγότερα bytes για μετακίνηση.
Το πιο απλό σχήμα είναι το symmetric absolute-maximum quantization, και χωρά σε τρεις γραμμές:
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 να αντιστοιχεί στον μεγαλύτερο ακέραιο, διαιρέστε, στρογγυλοποιήστε, αποθηκεύστε τους ακεραίους και το scale. Ανακατασκευάστε πολλαπλασιάζοντας πίσω. Δεν έχει τίποτα έξυπνο, και δουλεύει — μέχρι τη στιγμή που δεν δουλεύει.
Μετρημένο στα πραγματικά weights του μοντέλου: και τα 168 projection matrices, 357.8 εκατομμύρια parameters, relative error :
| scheme | mean relative error | worst matrix |
|---|---|---|
| INT8, ένα scale για όλο το matrix | 0.0400 | 0.1487 |
| INT8, ένα scale ανά output row | 0.0100 | 0.0149 |
| INT4, ένα scale για όλο το matrix | 0.6026 | 0.9931 |
| INT4, ένα scale ανά output row | 0.1790 | 0.2589 |
| INT4, ένα scale ανά group των 128 | 0.1323 | 0.1992 |
| NF4, ένα scale ανά block των 64 | 0.0952 | 0.1205 |
| INT3, ένα scale ανά group των 128 | 0.3044 | 0.4123 |
| INT2, ένα scale ανά group των 128 | 0.7790 | 0.8076 |
Η τέταρτη γραμμή είναι η κατάρρευση. Relative error 0,99 στο worst matrix σημαίνει ότι η ανακατασκευή δεν κρατά ουσιαστικά τίποτα από το αρχικό — το matrix έχει αντικατασταθεί από θόρυβο περίπου σωστού μεγέθους. Η αιτία φαίνεται στο ίδιο πείραμα σε ένα μόνο matrix:
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 βήματα και το τυπικό weight εξακολουθεί να πέφτει σε ένα ουσιαστικό. Στα 4 bits υπάρχουν 16, το εξωτερικότερο δεσμεύεται για μια τιμή που σχεδόν τίποτα δεν έχει, και τα συνηθισμένα weights — δηλαδή όλα τους — στρογγυλοποιούνται σε δύο ή τρία distinct levels.
Όλα μετά από εκείνη τη γραμμή είναι η ίδια επισκευή σε διαφορετικά granularities: δώστε στο scale μικρότερη επικράτεια. Ανά output row διαιρεί το error με 3,4· ανά group 128 διαδοχικών weights το διαιρεί ξανά. Το κόστος είναι bookkeeping — ένα 16-bit scale ανά group των 128 είναι bits ανά weight αντί για 4 — και αγοράζει πίσω το μεγαλύτερο μέρος του κενού.
Το NF4 το προσεγγίζει από την άλλη πλευρά.5 Τα levels δεν χρειάζεται να απέχουν ίσα. Τα weights μέσα σε ένα block είναι περίπου κανονικά κατανεμημένα, οπότε διαλέξτε τα δεκαέξι levels ως τα quantiles μιας κανονικής κατανομής: πυκνά κοντά στο μηδέν όπου πράγματι βρίσκονται τα weights, αραιά στις ουρές όπου δεν βρίσκονται. Ίδια τέσσερα bits, ίδιο block scaling, σε μικρότερο block — 4.25 bits ανά weight έναντι 4.125 του group-128 — και το μετρημένο error πέφτει από 0.1323 σε 0.0952, 28 % χαμηλότερα. Μέρος αυτού είναι το λεπτότερο block και το υπόλοιπο είναι η τοποθέτηση των levels εκεί όπου βρίσκεται η μάζα, και ο διαχωρισμός των δύο θα χρειαζόταν τρίτη γραμμή.
Τα outlier features
Σύνδεσμος στην ενότητα: Τα outlier featuresΤο floating-point πλαίσιο του Κεφαλαίου 2 έκλεισε με μια υπόσχεση: ότι αυτό το κεφάλαιο θα έκανε quantize τα weights σε 8 και 4 bits και θα έβρισκε μια χούφτα outlier features που αρνούνται να συμπιεστούν. Εδώ είναι, και εξηγούν γιατί το «απλώς στρογγυλοποιήστε τους αριθμούς» δεν επρόκειτο ποτέ να δουλέψει στα activations.
Τα weights παραπάνω είχαν κακή συμπεριφορά. Τα activations είναι σε άλλη κατηγορία. Πάρτε ένα συνηθισμένο prompt 84 tokens, καταγράψτε το residual stream σε κάθε layer και μετρήστε το μεγαλύτερο magnitude που φτάνει καθεμία από τις 896 dimensions:
| layer | largest |h| | median dimension's largest |h| | ratio | dimensions above 6x the median |
|---|---|---|---|---|
| 1 | 6.19 | 0.339 | 18x | 2 |
| 4 | 1543.48 | 1.550 | 996x | 34 |
| 8 | 1571.63 | 1.498 | 1049x | 36 |
| 12 | 1575.03 | 1.546 | 1019x | 34 |
| 16 | 1579.60 | 1.617 | 977x | 32 |
| 20 | 1577.98 | 2.361 | 668x | 24 |
| 24 | 204.44 | 10.760 | 19x | 12 |
Η dimension 62 φτάνει 1.579,6 ενώ η median dimension δεν ξεπερνά ποτέ το 1,6. Δεν είναι τυχαίο συμβάν ενός token ή ενός layer: η ίδια dimension βρίσκεται εκεί στο layer 4 και παραμένει εκεί στο layer 20, σχεδόν με την ίδια τιμή. Αυτά είναι τα outlier features,6 και είναι συστηματικά — ιδιότητα του trained μοντέλου, όχι του input.
Το histogram αυτών των 896 per-dimension maxima στο layer 16 κάνει το σχήμα αδιαμφισβήτητο:
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, τίποτα απολύτως για τρεις οκτάβες, και έπειτα μία dimension μόνη της στο μακρινό άκρο. Τώρα κάντε quantize αυτό το tensor σε INT8 και μετρήστε τι συμβαίνει:
| scheme | relative error | distinct integer levels used, whole tensor |
|---|---|---|
| ένα scale για όλο το tensor | 0.1083 | 14 of 256 |
| ένα scale ανά token (ανά row) | 0.0433 | 158 |
| whole tensor, 1 outlier dimension κρατημένη σε fp32 | 0.0442 | 48 |
| whole tensor, 4 outlier dimensions κρατημένες σε fp32 | 0.0279 | 57 |
| whole tensor, 16 outlier dimensions κρατημένες σε fp32 | 0.0085 | 102 |
Δεκατέσσερα levels από 256. Το scale καθορίστηκε από το 1.579,6, άρα κάθε βήμα έχει πλάτος 12,44, και το τυπικό activation — median magnitude 0,26, ninety-ninth percentile 2,51 — δεν έχει πού να προσγειωθεί. Ανά dimension είναι ακόμη πιο έντονο:
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, έγινε quantized στον ίδιο αριθμό. Οκτώ bits διατέθηκαν και χρησιμοποιήθηκαν περίπου μηδέν, και το μοντέλο που διαβάζει αυτά τα activations λαμβάνει μια σταθερά.
Αυτή η μέτρηση είναι η δικαιολόγηση για κάθε τεχνική που οι άνθρωποι χρησιμοποιούν πραγματικά:
Κρατήστε τα outliers έξω από αυτό. Το LLM.int8()6 αποσυνθέτει το matrix multiply: οι dimensions με extreme magnitudes υπολογίζονται σε 16 bits, όλα τα υπόλοιπα σε INT8, και τα δύο μισά αθροίζονται. Ο παραπάνω πίνακας είναι η απόδειξη — η αφαίρεση τεσσάρων dimensions μειώνει το error κατά έναν παράγοντα σχεδόν τεσσάρων. Το SmoothQuant7 μεταφέρει αντίθετα τη δυσκολία: διαιρεί τα activations με έναν per-channel factor και πολλαπλασιάζει την αντίστοιχη weight column με αυτόν, κάτι που αφήνει το product αμετάβλητο και μετακινεί το outlier έξω από το tensor που δεν μπορεί να το απορροφήσει, μέσα σε εκείνο που μπορεί.
Επιλέξτε τη στρογγυλοποίηση, μην κάνετε απλώς round. Τίποτα από τα παραπάνω δεν ρωτά τι κάνει το matrix. Το GPTQ8 κάνει quantize column by column και, μετά από κάθε column, προσαρμόζει τις υπόλοιπες full-precision columns για να αντισταθμίσει το error που ήδη δεσμεύτηκε — ελαχιστοποιώντας το error του output του layer σε πραγματικά inputs αντί για το error των weights του. Το AWQ9 σημειώνει ότι ένα μικρό κλάσμα weight channels έχει πολύ μεγαλύτερη σημασία από τα υπόλοιπα, τα βρίσκει από activation statistics και τα κλιμακώνει προς τα πάνω πριν το quantizing ώστε να πέσουν σε λεπτότερα levels. Και τα δύο χρειάζονται calibration set· κανένα δεν χρειάζεται gradients.
Εμφάνιση λεπτομερειών
GGUF, και τι σχέση έχει ένα file format με όλα αυτά.
Το GGUF δεν είναι μέθοδος quantization· είναι το container που χρησιμοποιεί το llama.cpp, και η σύγχυση στις συγκρίσεις gguf vs gptq προέρχεται από το να αντιμετωπίζονται τα δύο σαν να είναι το ίδιο είδος πράγματος. Το GGUF κρατά tensors, tokenizer, architecture metadata και chat template σε ένα memory-mappable file, και μεταφέρει μέσα του μια οικογένεια block schemes — ονόματα όπως Q4_K_M κωδικοποιούν bits ανά weight, block size και αν κάποια tensors κρατούνται σε υψηλότερο precision.
Η engineering διαφορά που έχει σημασία: GPTQ και AWQ παράγουν weights optimized για GPU kernel, ενώ τα schemes του GGUF αποκωδικοποιούνται φθηνά σε CPU με το file mapped αντί loaded. Γι’ αυτό το ίδιο ονομαστικά «4-bit 7B model» υπάρχει και στους δύο κόσμους με διαφορετικά sizes και διαφορετική ποιότητα, και γι’ αυτό η ειλικρινής σύγκριση δεν είναι ποτέ το format — είναι η παρακάτω μέτρηση, τρεγμένη στο δικό σας task.
Τι κοστίζει πραγματικά το quantization, μετρημένο
Σύνδεσμος στην ενότητα: Τι κοστίζει πραγματικά το quantization, μετρημένοΣχεδόν κάθε άρθρο για quantization σταματά στην προηγούμενη ενότητα: εξηγεί τη μέθοδο, παραθέτει ένα compression ratio και ισχυρίζεται ότι η ποιότητα «διατηρείται σε μεγάλο βαθμό». Το Κεφάλαιο 4 αφορούσε το να μη γελάτε τον εαυτό σας, οπότε ας το διαπιστώσουμε.
Ίδιο μοντέλο, weights quantized in place με κάθε scheme, έπειτα τρεις μετρήσεις: perplexity σε 2.048 tokens held-out αγγλικής πρόζας — εδώ, το draft αυτού του course, γι’ αυτό το repository το αντικαθιστά με ένα σταθερό public-domain βιβλίο και τυπώνει πίνακα ίδιου σχήματος με διαφορετικούς αριθμούς — μια συστοιχία 16 σύντομων factual questions με γνωστές απαντήσεις υπό greedy decoding, και το κλάσμα των tokens στα οποία το quantized model συμφωνεί με το full-precision model με δεδομένο identical context.
| scheme | mean weight error | perplexity | question battery | agrees with fp32 |
|---|---|---|---|---|
| fp32 (reference) | 0.0000 | 23.08 | 13/16 | 100.0 % |
| INT8 per tensor | 0.0400 | 23.58 | 13/16 | — |
| INT8 per row | 0.0100 | 22.96 | 13/16 | 98.6 % |
| INT4 per tensor | 0.6026 | 365,416,000 | 0/16 | — |
| INT4 per row | 0.1790 | 46.18 | 6/16 | 58.3 % |
| INT4 group 128 | 0.1323 | 31.08 | 10/16 | 71.5 % |
| NF4 block 64 | 0.0952 | 24.55 | 11/16 | 84.7 % |
| INT3 group 128 | 0.3044 | 213.09 | 0/16 | 5.6 % |
| INT2 group 128 | 0.7790 | 26,325,436 | 0/16 | 0.0 % |
Τέσσερα πράγματα σε αυτόν τον πίνακα αξίζει να ειπωθούν καθαρά.
Το INT8 όταν γίνεται σωστά είναι δωρεάν. Το per-row INT8 σκοράρει 22.96 απέναντι στο 23.08 του reference — ένα κενό ενός μέρους στα διακόσια, που είναι θόρυβος και πρέπει να διαβαστεί ως «πανομοιότυπο». Η κατεύθυνση προς την οποία δείχνει ο θόρυβος δεν είναι σταθερή: στο public-domain corpus του repository, τα ίδια δύο schemes βγαίνουν 22.24 έναντι 22.18: μισή απόσταση, και προς την άλλη κατεύθυνση. Συμφωνεί με το full-precision model σε 142 από 144 generated tokens. Ένα τέταρτο της μνήμης απέναντι στο fp32 reference, μισή απέναντι στο fp16 που θα κάνατε πραγματικά deploy, και κανένα ανιχνεύσιμο κόστος. Το INT8 όταν γίνεται απρόσεκτα είναι σχεδόν δωρεάν επίσης: ένα scale ανά matrix κοστίζει 0,5 perplexity points και καμία απάντηση στο battery. Τα οκτώ bits συγχωρούν αρκετά ώστε το granularity σχεδόν να μη μετρά, ακριβώς γι’ αυτό οι άνθρωποι γενικεύουν από INT8 σε INT4 και πληγώνονται.
Το INT4 με ένα scale ανά tensor καταστρέφει το μοντέλο. Perplexity 365 εκατομμύρια: όχι υποβαθμισμένο, αφανισμένο. Το 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 gap είναι 6,5 points και το battery διαφέρει κατά μία ερώτηση — και το confidence interval του Κεφαλαίου 4 λέει ότι μία ερώτηση στις δεκαέξι δεν ξεχωρίζει απολύτως τίποτα. Υπάρχει πιο αιχμηρή επίδειξη από το interval: τρέξτε το ίδιο battery με το stock repetition penalty του μοντέλου απενεργοποιημένο, δηλαδή αυτό που σημαίνει πραγματικά greedy decoding, και αυτές οι δύο γραμμές αλλάζουν θέσεις. Μία ερώτηση στις δεκαέξι δεν είναι μικρό effect, είναι κανένα effect. Ισχύει και η προειδοποίηση του Κεφαλαίου 8: το perplexity είναι συγκρίσιμο μόνο μεταξύ μοντέλων που μοιράζονται tokenizer, άρα ένας αριθμός από το write-up κάποιου άλλου δεν μπορεί να συγκριθεί με τον δικό σας.
Η στήλη agreement είναι η πιο αιχμηρή από τις τρεις, και σχεδόν δωρεάν: τρέξτε το full-precision model greedily, έπειτα ρωτήστε το quantized one, σε κάθε θέση, τι θα είχε επιλέξει με δεδομένο το ίδιο prefix. Έχει 144 ανεξάρτητες παρατηρήσεις αντί για 16, δεν χρειάζεται ground truth, και υποβαθμίζεται ομαλά εκεί όπου το battery υποβαθμίζεται σε άλματα. Είναι επίσης ακριβώς η ποσότητα που χρειάζεται η επόμενη ενότητα.
Αυτή είναι η υπόσχεση που έδωσε το Κεφάλαιο 1 για αυτό το κεφάλαιο, φτάνοντας στην ώρα της: τα μαθηματικά λένε ότι ένα 4-bit model είναι δυνατό, και η engineering αποφασίζει αν είναι usable.
Speculative decoding
Σύνδεσμος στην ενότητα: Speculative decodingΤο Κεφάλαιο 12 το ανήγγειλε και άφησε τον λογαριασμό εδώ.
Η ιδέα έρχεται κατευθείαν από τον διαχωρισμό prefill/decode. Η επαλήθευση μιας προτεινόμενης ακολουθίας tokens κοστίζει ένα forward pass πάνω σε θέσεις — ένα matrix-matrix product, ελάχιστα πιο ακριβό από το pass πάνω σε μία. Άρα:
Ένα μικρό, φθηνό μοντέλο παράγει candidate tokens autoregressively.
Το μεγάλο μοντέλο τρέχει ένα forward pass πάνω σε όλα τα candidates μαζί, παράγοντας τι θα έλεγε σε κάθε θέση.
Κρατήστε το μεγαλύτερο prefix στο οποίο τα δύο συμφωνούν, συν το token που παρέχει δωρεάν το μεγάλο μοντέλο στην πρώτη διαφωνία. Πετάξτε τα υπόλοιπα και ξεκινήστε ξανά.
Η output distribution παραμένει αμετάβλητη. Με greedy decoding αυτό είναι προφανές — ένα token γίνεται accepted μόνο αν το target θα το είχε παραγάγει. Με sampling απαιτεί modified acceptance rule, και οι Leviathan et al. αποδεικνύουν ότι η προκύπτουσα distribution είναι ακριβώς του target.10 Αυτή είναι η δεύτερη exact optimization σε αυτό το κεφάλαιο.
Επομένως όλα εξαρτώνται από το acceptance rate , το οποίο είναι μετρήσιμο — είναι η agreement column παραπάνω, γι’ αυτό υπολογίστηκε εκεί. Χρησιμοποιώντας κάθε quantized model ως draft για το full-precision target, σε 144 generated positions:
| draft model | acceptance | longest accepted run | expected tokens per target pass, |
|---|---|---|---|
| fp32 (the target itself) | 100.0 % | 48 | 5.00 |
| INT8 per row | 98.6 % | 48 | 4.86 |
| NF4 block 64 | 84.7 % | 20 | 3.69 |
| INT4 group 128 | 71.5 % | 13 | 2.85 |
| INT4 per row | 58.3 % | 7 | 2.24 |
| INT3 group 128 | 5.6 % | 2 | 1.06 |
| INT2 group 128 | 0.0 % | 0 | 1.00 |
Τα expected tokens accepted ανά verification pass, σε draft length , είναι
και το καθαρό speedup το διαιρεί με το ίδιο το κόστος του draft, ένα κλάσμα του target ανά token:
| acceptance | , | , | , | , |
|---|---|---|---|---|
| 30 % | 1.19x | 1.02x | 0.79x | 0.79x |
| 50 % | 1.61x | 1.38x | 1.08x | 1.11x |
| 70 % | 2.31x | 1.98x | 1.54x | 1.78x |
| 90 % | 3.41x | 2.93x | 2.28x | 3.40x |
Το bold entry είναι αυτό που πρέπει να θυμάστε: το speculative decoding μπορεί να κάνει το generation πιο αργό. Σε 30 % acceptance με draft που κοστίζει το ένα πέμπτο του target, πληρώνετε για πέντε forward passes και κρατάτε 1,4 tokens. Η τελευταία στήλη είναι η άλλη παγίδα — ένα longer draft βοηθά μόνο όταν το acceptance είναι υψηλό, επειδή η ουρά μιας εικασίας tokens σχεδόν ποτέ δεν φτάνεται. Σε 90 % acceptance το αξίζει 3.40x και σε 30 % αξίζει 0.79x: η ίδια configuration, κέρδος ή ζημία ανάλογα με έναν αριθμό μετρημένο στο δικό σας traffic.
Distillation, και τι μεταφέρει ένα soft label
Σύνδεσμος στην ενότητα: Distillation, και τι μεταφέρει ένα soft labelΤο quantization μικραίνει ένα μοντέλο αποθηκεύοντας την ίδια function σε λιγότερα bits. Το distillation το μικραίνει εκπαιδεύοντας ένα μικρότερο μοντέλο να μιμείται ένα μεγαλύτερο11 — μια ιδέα που προηγείται του deep learning σχεδόν κατά μία δεκαετία.12
Το λεπτό σημείο είναι από τι μαθαίνει ο student. Όχι από τη σωστή απάντηση: θα μπορούσε να είχε εκπαιδευτεί απευθείας πάνω σε αυτή. Αυτό που προσθέτει ο teacher είναι η ολόκληρη distribution. Ρωτήστε το μοντέλο τι ακολουθεί μια φράση και κοιτάξτε πέρα από το argmax:
"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, μια εντελώς διαφορετική γραμματική συνέχεια — ακόμη ζωντανό. Αυτό είναι το αρχικό επιχείρημα: αυτό είναι 7, αλλά μοιάζει αρκετά με 1, και η ομοιότητα είναι πληροφορία που το hard label πετά.
Γι’ αυτό επίσης το distillation χρησιμοποιεί temperature. Η διαίρεση των logits με πριν από το softmax ισοπεδώνει τη distribution και αυξάνει το σχετικό βάρος των runners-up: σε αυτή τη φράση, το ratio ανάμεσα στο top token και στο τρίτο πέφτει από 2.24 στο σε 1.50 στο — η τετραγωνική ρίζα του πρώτου, που είναι αυτό που κάνει η διαίρεση των logits με δύο σε ένα ratio. Ίδια σειρά, περισσότερη attention του loss στα near misses. Το gradient του student μεταφέρει την αβεβαιότητα του teacher και όχι μόνο την ετυμηγορία του.
Τι χωρά σε 8, 16 και 24 GB
Σύνδεσμος στην ενότητα: Τι χωρά σε 8, 16 και 24 GBΌλα σε αυτό το κεφάλαιο είναι πλέον ένα άθροισμα:
όπου είναι τα συνολικά resident tokens σε όλα τα concurrent requests. Εφαρμόζοντάς το: οι γραμμές 7B και 70B υποθέτουν 8 key-value heads διάστασης 128, η γραμμή 13B πλήρες multi-head attention με 40 heads, όπως είχαν φτιαχτεί εκείνες οι γενιές μοντέλων — και φαίνεται.
8 GB
| model | precision | weights | free after overhead | context tokens that fit |
|---|---|---|---|---|
| 7B | fp16 | 13.0 GB | δεν χωρά | — |
| 7B | int8 | 6.5 GB | δεν χωρά | — |
| 7B | int4 (g128) | 3.4 GB | 3.1 GB | 25,710 |
| 13B | int4 (g128) | 6.2 GB | 0.3 GB | 337 |
| 70B | int4 (g128) | 33.6 GB | δεν χωρά | — |
16 GB
| model | precision | weights | free after overhead | context tokens that fit |
|---|---|---|---|---|
| 7B | fp16 | 13.0 GB | 1.5 GB | 11,972 |
| 7B | int8 | 6.5 GB | 8.0 GB | 65,378 |
| 7B | int4 (g128) | 3.4 GB | 11.1 GB | 91,246 |
| 13B | int8 | 12.1 GB | 2.4 GB | 3,136 |
| 13B | int4 (g128) | 6.2 GB | 8.3 GB | 10,822 |
24 GB
| model | precision | weights | free after overhead | context tokens that fit |
|---|---|---|---|---|
| 7B | fp16 | 13.0 GB | 9.5 GB | 77,508 |
| 7B | int8 | 6.5 GB | 16.0 GB | 130,914 |
| 7B | int4 (g128) | 3.4 GB | 19.1 GB | 156,782 |
| 13B | int8 | 12.1 GB | 10.4 GB | 13,622 |
| 13B | int4 (g128) | 6.2 GB | 16.3 GB | 21,308 |
| 70B | int4 (g128) | 33.6 GB | δεν χωρά | — |
Κοιτάξτε τη γραμμή 13B στον πίνακα των 8 GB. Τα weights χωρούν — 6.2 GB από 8 — οπότε με τον συνηθισμένο τρόπο ομιλίας, ένα 13B model «τρέχει σε κάρτα 8 GB». Έχει 337 tokens context, που δεν είναι συνομιλία αλλά μετά βίας prompt. Το «χωράει;» είναι η λάθος ερώτηση. Η σωστή είναι «με πόσο context, και για πόσους χρήστες ταυτόχρονα;».
Κοιτάξτε επίσης τις δύο int8 γραμμές των 16 GB. Το 7B παίρνει 65.378 tokens και το 13B παίρνει 3.136 — εικοσαπλάσια διαφορά από 5.6 GB επιπλέον weights, επειδή το 13B εδώ έχει multi-head attention και το cache του κοστίζει 800 KB ανά token έναντι 128 KB του 7B. Δύο μοντέλα παρόμοιου μεγέθους, το ένα μη usable για long context, για έναν λόγο που δεν εμφανίζεται στον τίτλο κανενός model card.
Πού πηγαίνει αυτό μετά
Σύνδεσμος στην ενότητα: Πού πηγαίνει αυτό μετάΠριν από δεκατρία κεφάλαια, αυτό ήταν ένα perceptron με δύο weights και ένα bias. Τώρα είναι ένας transformer που έχει σχεδιαστεί, εκπαιδευτεί, ευθυγραμμιστεί, διδαχθεί να ξοδεύει compute σε δύσκολες ερωτήσεις, και γίνει served με μετρημένο κόστος ανά token — χωρίς να έχει μείνει μέσα του κανένα κλειστό κουτί.
Αυτό τελειώνει εδώ, και τελειώνει επίτηδες.
Το Κεφάλαιο 14 αρχίζει με το μοντέλο κάπου αλλού. Όχι στο process σας, όχι στη μνήμη σας, όχι σε μια variable που μπορείτε να εκτυπώσετε: σε ένα μηχάνημα που δεν διαχειρίζεστε, πίσω από ένα API key, ένα port και έναν λογαριασμό. Όλα όσα μετρήθηκαν εδώ συνεχίζουν να συμβαίνουν — το prefill εξακολουθεί να τρέχει πριν από το πρώτο token, το cache εξακολουθεί να μεγαλώνει με τη συνομιλία, το batch στο οποίο βρίσκεστε εξακολουθεί να ανήκει σε κάποιον άλλο και εξακολουθεί να αποφασίζει το latency σας — αλλά από εδώ και πέρα το παρατηρείτε μέσα από stream Server-Sent Events, ένα finish_reason, και ένα HTTP 429 με header Retry-After. Οι ερωτήσεις αλλάζουν με το vantage point: όχι πώς υπολογίζεται αυτό το gradient αλλά γιατί τριπλασιάστηκε το τιμολόγιό μου. Το ίδιο και η γλώσσα, και το Κεφάλαιο 14 εξηγεί αυτόν τον κανόνα αντί να τον ανακοινώνει — μέχρι εδώ ο κώδικας κρατούσε weights, gradients, logits και tokenizer bytes· από εκεί και πέρα κρατά connection, retry, cancellation και accumulated state. Τα δεκατρία κεφάλαια πίσω σας δεν απορρίπτονται με το πέρασμα. Είναι η περιγραφή αυτού που τρέχει στην άλλη πλευρά του port.
Πηγές και μέθοδος
Σύνδεσμος στην ενότητα: Πηγές και μέθοδοςΔύο παραλείψεις είναι σκόπιμες. Το FlashAttention (Dao et al., arXiv:2205.14135) δεν είναι διαφορετικό attention — υπολογίζει την ίδια function κάνοντας tiling την πράξη ώστε το score matrix να μη γράφεται ποτέ στη μνήμη, γι’ αυτό τα 67 MB στον δεύτερο πίνακα αυτού του κεφαλαίου είναι στην πράξη μικρότερα απ’ ό,τι υποδεικνύει η αριθμητική. Και τα ίδια τα kernels ανατίθενται αλλού: η διάλεξη 10 του CS336 του Stanford καλύπτει inference systems σε βάθος που αυτό δεν επιχειρεί, και το repository llama.cpp και το GGUF specification είναι οι primary sources για την CPU πλευρά.
Παραπομπές
Σύνδεσμος στην ενότητα: Παραπομπές-
Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019). Το paper είναι σε μεγάλο βαθμό επιχείρημα memory-bandwidth, και έτσι διαβάζεται. ↩
-
Ainslie, J. et al. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023). Περιλαμβάνει τη συνταγή uptraining που μετατρέπει ένα υπάρχον multi-head checkpoint, γι’ αυτό το GQA εξαπλώθηκε τόσο γρήγορα. ↩
-
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. ↩
-
Kwon, W. et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. arXiv:2309.06180 (2023), SOSP 2023. Το paper πάνω στο οποίο χτίστηκε το vLLM· η §3 είναι η πλήρης αναλογία με τα λειτουργικά συστήματα. ↩
-
Dettmers, T., Pagnoni, A., Holtzman, A. and Zettlemoyer, L. QLoRA: Efficient Finetuning of Quantized LLMs. arXiv:2305.14314 (2023). Το NF4 ορίζεται στη §3· οι δεκαέξι level values που χρησιμοποιήθηκαν στην παραπάνω μέτρηση είναι αυτές που παράγει αυτό το paper. ↩
-
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
-
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). ↩
-
Frantar, E., Ashkboos, S., Hoefler, T. and Alistarh, D. GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers. arXiv:2210.17323 (2022). ↩
-
Lin, J. et al. AWQ: Activation-aware Weight Quantization for LLLM Compression and Acceleration. arXiv:2306.00978 (2023). ↩
-
Leviathan, Y., Kalman, M. and Matias, Y. Fast Inference from Transformers via Speculative Decoding. arXiv:2211.17192 (2022). Το Theorem 1 είναι η απόδειξη ότι η output distribution παραμένει αμετάβλητη· οι Chen et al. (arXiv:2302.01318) δημοσίευσαν την ίδια ιδέα ανεξάρτητα. ↩
-
Hinton, G., Vinyals, O. and Dean, J. Distilling the Knowledge in a Neural Network. arXiv:1503.02531 (2015). Το temperature και το επιχείρημα «dark knowledge». ↩
-
Buciluă, C., Caruana, R. and Niculescu-Mizil, A. Model Compression. KDD 2006. Distillation, εννέα χρόνια νωρίτερα, για ensembles αντί για transformers. ↩