پرش به محتوا
13/30فصل 13 از 30

ارزان‌کردن Inference: KV cache، Batching و Quantization

یک مدل، خروجی byte-identical در 8.8 و 78.9 ثانیه؛ سپس INT4 با سه اندازه‌گیری واقعی، نه ادعا.

در این صفحه

همان مدل، روی همان ماشین، در پاسخ به همان پرسش با همان 48 token. دو خروجی 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، وزن‌های quantized — تلاشی است برای این‌که هزینه کاری را نپردازیم که پاسخ را تغییر نمی‌دهد، یا بفهمیم پاسخ ارزان‌تر چه هزینه‌ای دارد. فصل 10 فهرست قیمت training را مشخص کرد. این فهرست قیمت بخشی است که تا ابد برایش پول می‌دهید: یک مدل deploy‌شده برای هر token که تولید می‌کند، در هر request، تا پایان عمرش تقریباً 2N2N FLOPs خرج می‌کند.

برای generate کردن یک token، یک decoder-only transformer کل sequence تا این لحظه را می‌گیرد، آن را از همه layerها عبور می‌دهد و probability distribution را از position آخر می‌خواند. بعد token انتخاب‌شده را append می‌کند و دوباره همین کار را انجام می‌دهد. این توصیف درست است، و اجرای کند دقیقاً همین کار را می‌کند.

اما به‌شدت اتلافی هم هست، و دلیلش causal mask از فصل 9 است. بردارهای key و value برای position 7 از input همان position 7 و positionهای قبل از آن محاسبه می‌شوند. وقتی position 8 می‌رسد، position 7 نمی‌تواند آن را ببیند — causal یعنی همین — پس key و value در position 7 دقیقاً همان اعداد قبلی‌اند. اجرای کند بااین‌حال در هر step دوباره آن‌ها را محاسبه می‌کند.

پس آن‌ها را ذخیره کنید. آن ذخیره همان key-value cache است؛ مهم‌ترین optimisation در 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 جدید به همه keyهای cached شده attention می‌دهد، و keyهای cached هرگز قرار نبود تغییر کنند. این یک approximation نیست — نکته همان بررسی خروجی یکسان در بالا است. cache کیفیت را با سرعت معامله نمی‌کند؛ محاسبات تکراری را حذف می‌کند.

برای دیدن scaling به‌شکل تمیز، transformer را کنار بگذارید و زمان یک attention head با d=64d = 64 را بگیرید؛ یک step از generation که به هر دو روش محاسبه شده است:

tokenها در contextمحاسبه دوباره همه‌چیزبا cacheنسبتماتریس score
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

ستون سمت راست علت است. محاسبه دوباره، در هر step کل ماتریس attention با اندازه n×nn \times n را می‌سازد — همان O(n2)O(n^2) از جعبه asymptotic notation فصل 9، که برای هر token یک بار پرداخت می‌شود. با cache به‌جای آن یک ردیف 1×n1 \times n می‌سازید: در 4,096 token، 67 MB از scoreها در برابر 16 KB.

شمردن multiply-accumulateها به‌جای میلی‌ثانیه، ماشین را از بحث حذف می‌کند. برای generate کردن TT token از cold start:

tokenهای generate‌شدهبا cacheمحاسبه دوبارهنسبت
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) است، با نسبتی که بی‌نهایت رشد می‌کند. تفاوت نه‌برابری ابتدای فصل روی 48 token اندازه‌گیری شده بود — حتی کمتر از ردیف اول این جدول.

cache همچنین چیزی را که باید در memory باشد تغییر می‌دهد. روی یک GPU لپ‌تاپی 8 GB که 256 token در fp16 generate می‌کند، اگر peak allocator را بگیرید و وزن‌های resident را کم کنید:

peak working memory
با cache21.8 MB
محاسبه دوباره181.7 MB

8.3 برابر memory بیشتر، خرج‌شده برای تولید همان tokenها با سرعت کمتر. این همان وعده‌ای است که در فصل 5 داده شد، اما از جهتی غیرمنتظره می‌رسد: آن‌جا reverse-mode autodiff باید هر intermediate را برای backward pass زنده نگه می‌داشت، و activationها بر memory training غالب بودند. در inference هیچ backward passی وجود ندارد و چیزی برای نگه‌داشتن به‌خاطر آن نیست — پس چیزی که بر memory غالب می‌شود cache است، و این یک انتخاب آگاهانه است نه هزینه‌ای اجتناب‌ناپذیر.

Prefill و decode دو ماشین متفاوت‌اند

لینک به بخش: Prefill و decode دو ماشین متفاوت‌اند

دوباره به اجرای سریع نگاه کنید: 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 به‌ازای هر token 25.6 ms هزینه داشت و هر token generate‌شده 166 ms. همان مدل، همان hardware، همان وزن‌ها، تفاوت شش‌برابری به‌ازای هر token — و جهتش همان چیزی نیست که بیشتر مردم انتظار دارند. prompt بخش ارزان است. Generation به دو phase با فیزیک واقعاً متفاوت تقسیم می‌شود:

یک forward pass روی کل prompt. هر token به‌صورت parallel پردازش می‌شود، بنابراین هر ماتریس وزن یک بار از memory load می‌شود و در ماتریسی از صدها بردار token ضرب می‌شود — یک matrix-matrix product، با محاسبات زیاد به‌ازای هر byte جابه‌جاشده؛ دقیقاً همان چیزی که GPU برایش ساخته شده است. Prefill compute-bound است، و هزینه‌اش تقریباً با طول prompt خطی است.

یک forward pass به‌ازای هر token، batch یک و sequence یک. هر ماتریس وزن همچنان به‌طور کامل از memory load می‌شود و در یک بردار منفرد ضرب می‌شود — یک matrix-vector product، با تقریباً هیچ محاسبه‌ای به‌ازای هر byte جابه‌جاشده. Decode memory-bandwidth-bound است، و هزینه هر token تقریباً به طول context وابسته نیست.

هر دو نیمه قابل اندازه‌گیری‌اند. Prefill، یک pass روی PP token:

tokenهای promptثانیهms به‌ازای هر token
160.351521.97
320.525416.42
641.049116.39
1281.655212.93
2563.096512.10

Decode، یک token در برابر cache با اندازه CC:

tokenهای cachedms برای یک token
16110.05
6497.57
256108.53
1024103.86

جدول دوم را دو بار بخوانید. رفتن از 16 token context به 1,024 — شصت‌وچهار برابر history بیشتر برای attention — هزینه یک step را به‌قدر قابل اندازه‌گیری تغییر نداد. attention روی cache کار واقعی است، اما زیر سایه هزینه ثابت کشیدن نیم‌میلیارد وزن از memory bus برای تولید یک بردار محو می‌شود. همین هزینه ثابت دلیل همه‌چیز در بخش بعدی است.

این دو phase منشأ دو عددی‌اند که هر سیستم serving گزارش می‌کند. Time to first token اساساً prefill است، و با prompt رشد می‌کند؛ برای همین یک conversation طولانی کند شروع می‌شود. Tokens per second برابر 1/decode step1/\text{decode step} است، و تقریباً ثابت می‌ماند؛ برای همین reply بعد از شروع، یکنواخت جریان پیدا می‌کند. chatی که کند شروع می‌شود و بعد روان stream می‌شود ترفند rendering نیست. همین دو جدول است.

cache خودش هم صورت‌حساب است

لینک به بخش: cache خودش هم صورت‌حساب است

cache محاسبات را با memory معامله می‌کند، و memory که می‌خواهد کم نیست. برای هر token در context، هر layer برای هر key-value head یک بردار key و یک بردار value نگه می‌دارد:

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 برای keyها و valueهاست؛ بقیه معماری است. برای مدلی که در سراسر این فصل اندازه‌گیری شده — 24 layer، 14 query head، 2 key-value head، head dimension برابر 64 — در fp16 این مقدار 2×24×2×64×2=12,2882 \times 24 \times 2 \times 64 \times 2 = 12{,}288 byte به‌ازای هر token است.

فرمول‌ها در این حوزه عادت دارند با ضریب دو اشتباه باشند، پس به‌جای باورکردن، آن را با 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های امتحان‌شده دقیق می‌ماند:

batchcontextcache اندازه‌گیری‌شدهپیش‌بینی‌شدهpeak 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 برای هرکدام، شصت‌وچهار user با 1,024، صدوبیست‌وهشت user با 512 — cache در همه موارد 768 MB است، چون هر سه 65,536 token را نگه می‌دارند. cache فقط به تعداد کل tokenهای resident وابسته است، نه به این‌که بین userها چطور توزیع شده‌اند. همین واقعیت پایه بخش batching است.

فصل 9 multi-query و grouped-query attention را معرفی کرد و دلیلش را به این فصل موکول کرد. دلیل همان فرمول است، و به‌طور خاص HkvH_{kv} داخل آن.

attention چندسری استاندارد به هر query head، key و value head مخصوص خودش را می‌دهد. مدل این‌جا 14 query head دارد؛ با multi-head attention کامل، cache آن 2×24×14×64×2=86,0162 \times 24 \times 14 \times 64 \times 2 = 86{,}016 byte به‌ازای هر token می‌شد — 84 KB به‌جای 12 KB، دقیقاً هفت برابر بیشتر، یعنی نسبت query headها به key-value headها.

Multi-query attention1 این را تا حد نهایی می‌برد: همه query headها یک key-value head مشترک دارند. Grouped-query attention2 مصالحه‌ای است که برنده شد — چند key-value head، که هرکدام توسط گروهی از query headها share می‌شوند — چون افت کیفیت MQA واقعی بود و افت کیفیت GQA نیست. هیچ‌کدام arithmetic نمی‌خرند. وجود دارند تا آن فرمول را بر یک عدد صحیح تقسیم کنند، و همان لحظه‌ای که contextهای طولانی cache را به constraint اصلی تبدیل کردند، در سراسر صنعت پخش شدند.

و این اتفاق سریع می‌افتد. برای یک مدل 7B-class با 32 layer و 8 key-value head با dimension 128، cache در fp16 برابر 128 KB به‌ازای هر token است:

tokenهای contextیک user8 user64 user
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

وزن‌های خود آن مدل در fp16 برابر 13.0 GB است، همان عددی که در جدول انتهای این فصل می‌آید. پس در context با 128,000 token، cache یک user از خود مدل بزرگ‌تر است. این همان arithmeticی است که فصل 16 به پول تبدیل می‌کند، و دلیل این‌که یک conversation طولانی فقط کند نیست — تا وقتی request زنده است، یک slice ثابت از یک ماشین را اشغال می‌کند.

Batching: عددی که بالا می‌رود و عددی که پایین می‌آید

لینک به بخش: Batching: عددی که بالا می‌رود و عددی که پایین می‌آید

Decode memory-bound است: وزن‌ها از bus کشیده می‌شوند تا یک token تولید شود، و واحدهای arithmetic idle می‌مانند. پس در همان step کار بیشتری بگذارید. چند request را هم‌زمان اجرا کنید، و وزن‌هایی که یک بار خوانده شده‌اند به همه آن‌ها خدمت می‌کنند. اندازه‌گیری روی همان مدل، با cache 64-token برای هر request و decoding یک token:

batchlatency هر stepthroughputlatency نسبت به 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 سرور را بهتر کرد و هر user را بدتر.

این bugی نیست که با tuning از بین برود؛ خود tradeoff است، و هر سمتش نامی دارد. Latency چیزی است که شخص منتظر reply تجربه می‌کند. Throughput چیزی است که invoice بر آن تقسیم می‌شود. هیچ تنظیمی هر دو را بهتر نمی‌کند.

همچنین توجه کنید کجا متوقف می‌شود. از 16 به 32، throughput فقط 9 % بهتر می‌شود درحالی‌که latency تقریباً دو برابر می‌شود: step از memory-bound بودن خارج شده و compute-bound شده است، و بعد از آن knee، batch چیزی نمی‌خرد. هر deployment چنین kneeای دارد؛ جای آن باید روی سیستم خودتان اندازه‌گیری شود، اما وجودش محل تردید نیست.

Static batching بیشتر چیزی را که می‌برد هدر می‌دهد

لینک به بخش: Static batching بیشتر چیزی را که می‌برد هدر می‌دهد

روش naive برای batch این است که BB request جمع کنید، آن‌ها را با هم اجرا کنید، و وقتی همه تمام شدند برگردانید. اما آن‌ها با هم تمام نمی‌شوند: بعضی replyها بیست token هستند و بعضی پانصد. یک batch ثابت تا زمانی اجرا می‌شود که طولانی‌ترین عضو آن تمام شود، و هر request تمام‌شده تا آن زمان slot خود را اشغال نگه می‌دارد و padding اضافه می‌کند.

64 request را با skew واقع‌گرایانه در طول خروجی بگیرید — median برابر 18 token، طولانی‌ترین 231، مجموعاً 1,874 — و هر دو policy را با هزینه per-step اندازه‌گیری‌شده برای هشت slot شبیه‌سازی کنید:

policywall clockthroughputمیانگین latency هر requestslot-stepهای هدررفته
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 بهتر می‌شود. میانگین latency بیش از ده برابر بهتر می‌شود، چون در static batching، requestی که در چهار step تمام شده هنوز باید منتظر همسایه 231-token بماند تا کسی چیزی بشنود.

Continuous batching3 راه‌حل است، و به همان سادگی‌ای است که به نظر می‌رسد: batch یک گروه نیست، بلکه مجموعه‌ای از slotهاست، و slotی که آزاد می‌شود در همان step بعدی request بعدی queue را می‌پذیرد. scheduler با granularity یک token کار می‌کند نه یک request. اکنون هر serving stack در production همین کار را می‌کند.

نیمه دومش cache است. slotهایی که می‌آیند و می‌روند cache memory را fragmented می‌کنند، و رزروکردن حداکثر context ممکن برای هر slot بیشتر reservation را هدر می‌دهد. PagedAttention4 پاسخ را از operating systemها قرض می‌گیرد: cache را در blockهای fixed-size با یک block table برای هر sequence ذخیره کنید، تا cache یک sequence بتواند از نظر physical پراکنده باشد اما از نظر logical contiguous بماند — و همچنین به دو sequence با prefix مشترک اجازه دهد blockهای نگه‌دارنده آن را share کنند. vLLM بر همین ساخته شده، و به همین دلیل یک serving engine در اصل memory allocatorی است که یک transformer به آن وصل شده است.

Quantization، و اولین چیزی که خراب می‌شود

لینک به بخش: Quantization، و اولین چیزی که خراب می‌شود

نیمه دیگر صورت‌حساب خود وزن‌ها هستند. نیم‌میلیارد parameter با چهار byte برای هرکدام 1.98 GB است؛ با دو byte، 0.99 GB؛ با یک byte، 0.49 GB. bitهای کمتر به‌ازای هر وزن، مدل را روی disk کوچک‌تر می‌کند، در memory کوچک‌تر می‌کند، و — چون decode bandwidth-bound است — هر step را سریع‌تر می‌کند، چون byteهای کمتری باید جابه‌جا شوند.

ساده‌ترین 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 انتخاب کنید تا بزرگ‌ترین وزن به بزرگ‌ترین integer map شود، تقسیم کنید، round کنید، integerها و scale را ذخیره کنید. بازسازی با ضرب‌کردن دوباره انجام می‌شود. هیچ‌چیز هوشمندانه‌ای در آن نیست، و کار می‌کند — درست تا جایی که دیگر کار نمی‌کند.

اندازه‌گیری روی وزن‌های واقعی مدل: همه 168 ماتریس projection، 357.8 میلیون parameter، relative error برابر WW^/W\lVert W - \hat{W}\rVert / \lVert W \rVert:

schemeمیانگین relative errorبدترین ماتریس
INT8، یک scale برای کل ماتریس0.04000.1487
INT8، یک scale برای هر output row0.01000.0149
INT4، یک scale برای کل ماتریس0.60260.9931
INT4، یک scale برای هر output row0.17900.2589
INT4، یک scale برای هر گروه 128تایی0.13230.1992
NF4، یک scale برای هر block 64تایی0.09520.1205
INT3، یک scale برای هر گروه 128تایی0.30440.4123
INT2، یک scale برای هر گروه 128تایی0.77900.8076

ردیف چهارم collapse است. relative error برابر 0.99 روی بدترین ماتریس یعنی reconstruction عملاً هیچ‌چیزی از original را نگه نداشته — ماتریس با noiseی تقریباً در magnitude درست جایگزین شده است. علت در همان آزمایش روی یک ماتریس منفرد دیده می‌شود:

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 %)

یک وزن از هر شش هزار فراتر از شش standard deviation می‌نشیند، و بزرگ‌ترینشان 24 تا دورتر است. با یک scale برای کل ماتریس، همان یک وزن step size را برای همه 4.3 میلیون وزن تعیین می‌کند. در 8 bit، 256 step وجود دارد و وزن معمولی هنوز روی step معناداری می‌افتد. در 4 bit، 16 step وجود دارد، بیرونی‌ترینشان برای مقداری رزرو شده که تقریباً هیچ‌چیز ندارد، و وزن‌های معمولی — یعنی همه آن‌ها — به دو یا سه level متمایز round می‌شوند.

هر چیزی بعد از آن ردیف، همان repair در granularityهای متفاوت است: به scale قلمرو کوچک‌تری بدهید. Per output row خطا را بر 3.4 تقسیم می‌کند؛ per group of 128 consecutive weights دوباره آن را تقسیم می‌کند. هزینه bookkeeping است — یک scale 16-bit برای هر گروه 128تایی برابر 4+16/128=4.1254 + 16/128 = 4.125 bit به‌ازای هر وزن است نه 4 — و بیشتر شکاف را برمی‌گرداند.

NF4 از سمت دیگر سراغ مسئله می‌رود.5 لازم نیست levelها فاصله برابر داشته باشند. وزن‌ها داخل یک block تقریباً normal distributed هستند، پس شانزده level را به‌صورت quantileهای یک normal distribution انتخاب کنید: متراکم نزدیک zero که وزن‌ها واقعاً آن‌جا هستند، پراکنده در tailها که نیستند. همان چهار bit، همان block scaling، در blockی کوچک‌تر — 4.25 bit به‌ازای هر وزن در برابر 4.125 برای group-128 — و خطای اندازه‌گیری‌شده از 0.1323 به 0.0952 می‌افتد، 28 % کمتر. بخشی از آن block ریزتر است و بقیه گذاشتن levelها جایی که mass هست؛ جداکردن این دو به ردیف سوم نیاز دارد.

جعبه floating-point در فصل 2 با یک وعده تمام شد: این‌که این فصل وزن‌ها را به 8 و 4 bit quantize کند و چند outlier feature پیدا کند که حاضر نیستند فشرده شوند. این‌ها همان‌ها هستند، و توضیح می‌دهند چرا «فقط عددها را round کن» هرگز برای activationها قرار نبود جواب بدهد.

وزن‌های بالا بدرفتار بودند. activationها در لیگ دیگری‌اند. یک prompt معمولی 84-token را بگیرید، residual stream را در هر layer capture کنید، و بزرگ‌ترین magnitudeی را که هرکدام از 896 dimension به آن می‌رسد اندازه بگیرید:

layerبزرگ‌ترین |h|بزرگ‌ترین |h| در dimension میانهنسبتdimensionهای بالاتر از 6x میانه
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 می‌رسد، درحالی‌که dimension میانه هرگز از 1.6 عبور نمی‌کند. این تصادف یک token یا یک layer نیست: همان dimension در layer 4 هست و در layer 20 هم هنوز آن‌جاست، با تقریباً همان مقدار. این‌ها outlier featureها هستند،6 و systematic هستند — ویژگی مدل trained، نه input.

Histogram آن 896 maximum per-dimension در 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

نهصد dimension در توده‌ای مرتب زیر 8، هیچ‌چیز برای سه octave، و بعد یک dimension تنها در انتهای دور. حالا آن tensor را به INT8 quantize کنید و ببینید چه می‌شود:

schemerelative errorlevelهای integer متمایز استفاده‌شده، کل tensor
یک scale برای کل tensor0.108314 از 256
یک scale برای هر token (per row)0.0433158
کل tensor، 1 outlier dimension نگه‌داشته‌شده در fp320.044248
کل tensor، 4 outlier dimension نگه‌داشته‌شده در fp320.027957
کل tensor، 16 outlier dimension نگه‌داشته‌شده در fp320.0085102

چهارده level از 256. scale با 1,579.6 تعیین شد، پس هر step عرض 12.44 دارد، و activation معمولی — median magnitude برابر 0.26، نودونهمین percentile برابر 2.51 — جایی برای فرود ندارد. در سطح per 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

یک level. کل dimension، هر token، به یک عدد یکسان quantize شده است. هشت bit اختصاص داده شد و تقریباً صفر bit استفاده شد، و مدلی که این activationها را می‌خواند یک constant تحویل می‌گیرد.

این اندازه‌گیری توجیه هر تکنیکی است که مردم واقعاً استفاده می‌کنند:

outlierها را بیرون نگه دارید. LLM.int8()6 matrix multiply را تجزیه می‌کند: dimensionهایی با magnitudeهای extreme در 16 bit محاسبه می‌شوند، بقیه در INT8، و دو نیمه جمع می‌شوند. جدول بالا رسید است — حذف چهار dimension خطا را تقریباً چهار برابر کم می‌کند. SmoothQuant7 در عوض سختی را جابه‌جا می‌کند: activationها را بر یک factor per-channel تقسیم کنید و ستون وزن متناظر را در آن ضرب کنید؛ product unchanged می‌ماند و outlier از tensorی که نمی‌تواند جذبش کند به tensorی منتقل می‌شود که می‌تواند.

rounding را انتخاب کنید، صرفاً round نکنید. هیچ‌کدام از بالا نمی‌پرسد ماتریس برای چه است. GPTQ8 ستون به ستون quantize می‌کند و بعد از هر ستون، ستون‌های full-precision باقی‌مانده را adjust می‌کند تا errorی را که قبلاً committed شده جبران کند — یعنی error output layer روی inputهای واقعی را کمینه می‌کند، نه error وزن‌هایش را. AWQ9 توجه می‌کند که بخش کوچکی از channelهای وزن بسیار مهم‌تر از بقیه‌اند، آن‌ها را از activation statistics پیدا می‌کند، و قبل از quantizing scaleشان را بالا می‌برد تا روی levelهای finer فرود بیایند. هر دو به calibration set نیاز دارند؛ هیچ‌کدام gradient نمی‌خواهد.

نمایش جزئیات

GGUF، و این‌که یک file format چه ربطی به همه این‌ها دارد.

GGUF روش quantization نیست؛ containerی است که llama.cpp استفاده می‌کند، و سردرگمی در مقایسه‌های gguf vs gptq از این می‌آید که این دو را از یک جنس فرض می‌کنند. GGUF tensorها، tokenizer، metadata معماری و chat template را در یک فایل memory-mappable نگه می‌دارد، و یک خانواده از block schemeها را داخل خود حمل می‌کند — نام‌هایی مثل Q4_K_M bit به‌ازای هر وزن، block size، و این‌که بعضی tensorها در precision بالاتر نگه داشته شده‌اند یا نه را encode می‌کنند.

تفاوت engineering مهم این است: GPTQ و AWQ وزن‌هایی تولید می‌کنند که برای GPU kernel optimize شده‌اند، درحالی‌که schemeهای GGUF روی CPU با فایل map‌شده، نه load‌شده، به‌ارزانی decode می‌شوند. برای همین همان «مدل 4-bit 7B» اسمی در هر دو جهان با size و quality متفاوت وجود دارد، و مقایسه صادقانه هرگز format نیست — اندازه‌گیری پایین است، روی task خودتان.

Quantization واقعاً چه هزینه‌ای دارد، با اندازه‌گیری

لینک به بخش: Quantization واقعاً چه هزینه‌ای دارد، با اندازه‌گیری

تقریباً هر مقاله‌ای درباره quantization در بخش قبلی متوقف می‌شود: method را توضیح می‌دهد، compression ratio نقل می‌کند، و ادعا می‌کند quality «تا حد زیادی حفظ شده» است. فصل 4 درباره فریب‌ندادن خودتان بود، پس بیایید بفهمیم.

همان مدل، وزن‌ها in place با هر scheme quantize شده‌اند، سپس سه measurement: perplexity روی 2,048 token از prose انگلیسی held-out — این‌جا draft همین دوره، و برای همین repository یک کتاب public-domain ثابت را جایگزین می‌کند و جدولی با همان shape و numberهای متفاوت چاپ می‌کند — یک battery از 16 پرسش factual کوتاه با پاسخ‌های known زیر greedy decoding، و fraction از tokenهایی که مدل quantized با مدل full-precision در context یکسان روی آن‌ها agree می‌کند.

schemeمیانگین weight errorperplexityquestion batteryتوافق با 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 در برابر 23.08 مرجع، 22.96 می‌گیرد — شکافی به اندازه یک بخش از دویست، که noise است و باید «یکسان» خوانده شود. جهت noise پایدار نیست: روی corpus public-domain در repository، همین دو scheme 22.24 در برابر 22.18 درمی‌آیند؛ نصف آن فاصله، و در جهت دیگر. روی 142 از 144 token generate‌شده با مدل full-precision agree می‌کند. یک‌چهارم memory در برابر reference fp32، نصف در برابر fp16ی که واقعاً deploy می‌کنید، و بدون هزینه detectable. INT8 اگر بی‌دقت انجام شود هم تقریباً رایگان است: یک scale به‌ازای هر ماتریس 0.5 نقطه perplexity و هیچ پاسخ battery را هزینه می‌کند. هشت bit آن‌قدر forgiving است که granularity تقریباً اهمیتی ندارد، و دقیقاً برای همین است که مردم از INT8 به INT4 تعمیم می‌دهند و ضربه می‌خورند.

INT4 با یک scale per tensor مدل را نابود می‌کند. Perplexity برابر 365 میلیون: نه degraded، بلکه annihilated. بعد از آن granularity کل بازی است — per-tensor برابر 365,416,000، per-row برابر 46.18، per-group-of-128 برابر 31.08، NF4 برابر 24.55. همان چهار bit به‌ازای هر وزن، فاصله‌ای پانزده‌میلیون‌برابری بین بدترین و بهترین.

Perplexity ابزار زمختی است و battery از آن هم زمخت‌تر. بین NF4 و group-128 INT4 شکاف perplexity برابر 6.5 point است و battery یک question فرق دارد — و confidence interval فصل 4 می‌گوید یک question از شانزده هیچ‌چیز را اصلاً متمایز نمی‌کند. نمایش تیزتری از interval وجود دارد: همان battery را با repetition penalty پیش‌فرض مدل خاموش اجرا کنید، که greedy decoding واقعاً همین معنی را دارد، و آن دو ردیف جای خود را عوض می‌کنند. یک question از شانزده اثر کوچک نیست، بی‌اثر است. هشدار فصل 8 هم applies: perplexity فقط بین مدل‌هایی که tokenizer مشترک دارند قابل مقایسه است، پس عددی از نوشته دیگران را نمی‌توان با عدد خودتان مقایسه کرد.

ستون agreement تیزترینِ این سه است، و تقریباً رایگان: مدل full-precision را greedily اجرا کنید، بعد از quantized بپرسید در هر position، با همان prefix، چه چیزی انتخاب می‌کرد. به‌جای 16 observation مستقل، 144 observation دارد، ground truth نمی‌خواهد، و جایی که battery پله‌ای خراب می‌شود، smoothly افت می‌کند. همچنین دقیقاً همان quantity است که بخش بعدی لازم دارد.

این همان وعده‌ای است که فصل 1 درباره این فصل داده بود و به‌موقع می‌رسد: ریاضیات می‌گوید یک مدل 4-bit ممکن است، و engineering تصمیم می‌گیرد آیا usable هست یا نه.

فصل 12 این را اعلام کرد و صورت‌حساب را این‌جا گذاشت.

ایده مستقیماً از شکاف prefill/decode می‌آید. verify کردن یک sequence پیشنهادی از γ\gamma token، یک forward pass روی γ\gamma position هزینه دارد — یک matrix-matrix product، فقط کمی گران‌تر از pass روی یک position. پس:

یک مدل کوچک و ارزان γ\gamma candidate token را autoregressively generate می‌کند.

مدل بزرگ یک forward pass روی همه γ\gamma candidate به‌صورت هم‌زمان اجرا می‌کند، و آن‌چه را در هر position می‌گفت تولید می‌کند.

طولانی‌ترین prefixی را که دو مدل روی آن agree دارند نگه دارید، به‌علاوه tokenی که مدل بزرگ در اولین disagreement رایگان supply می‌کند. بقیه را دور بریزید و دوباره شروع کنید.

Output distribution تغییر نمی‌کند. با greedy decoding این واضح است — token فقط وقتی accepted می‌شود که target همان را تولید می‌کرد. با sampling به rule پذیرش modified نیاز دارد، و Leviathan و همکاران ثابت می‌کنند distribution حاصل دقیقاً همان target است.10 این دومین optimisation دقیق در این فصل است.

بنابراین همه‌چیز به acceptance rate α\alpha وابسته است، که قابل اندازه‌گیری است — همان ستون agreement بالا است، و برای همین آن‌جا محاسبه شد. استفاده از هر مدل quantized به‌عنوان draft برای target full-precision، روی 144 position generate‌شده:

draft modelacceptanceطولانی‌ترین run acceptedtokenهای مورد انتظار به‌ازای هر target pass، γ=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

tokenهای expected که در هر verification pass accepted می‌شوند، در draft length برابر γ\gamma، برابر است با

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

و 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

ورودی bold همان چیزی است که باید به خاطر سپرد: speculative decoding می‌تواند generation را کندتر کند. در acceptance برابر 30 % با draftی که یک‌پنجم target هزینه دارد، برای پنج forward pass پول می‌دهید و 1.4 token نگه می‌دارید. ستون آخر trap دیگر است — draft طولانی‌تر فقط وقتی کمک می‌کند که acceptance بالا باشد، چون tail یک حدس γ\gamma-token تقریباً هرگز reached نمی‌شود. در acceptance 90 %، γ=8\gamma = 8 ارزش 3.40x دارد و در 30 % ارزش 0.79x: همان configuration، بسته به عددی که روی traffic خودتان اندازه‌گیری شده، برد یا باخت است.

Distillation، و این‌که soft label چه چیزی حمل می‌کند

لینک به بخش: Distillation، و این‌که soft label چه چیزی حمل می‌کند

Quantization مدل را با ذخیره همان function در bitهای کمتر کوچک می‌کند. Distillation با train کردن یک مدل کوچک‌تر برای imitate کردن یک مدل بزرگ‌تر آن را کوچک می‌کند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 — یک صفت، ادامه‌ای از نظر grammatical کاملاً متفاوت — هنوز زنده است. استدلال original همین است: این یک 7 است، اما خیلی شبیه 1 هم هست، و این شباهت اطلاعاتی است که hard label دور می‌ریزد.

به همین دلیل distillation از temperature استفاده می‌کند. تقسیم logits بر TT قبل از softmax، distribution را flatten می‌کند و وزن نسبی runner-upها را بالا می‌برد: در این phrase، نسبت بین top token و سومین token از 2.24 در T=1T = 1 به 1.50 در T=2T = 2 می‌افتد — ریشه دوم اولی، که همان کاری است که تقسیم logits بر دو با یک ratio می‌کند. همان ordering، اما attention بیشتری از loss روی near missها. gradient مربوط به student، uncertainty معلم را حمل می‌کند و نه فقط verdict او را.

چه چیزی در 8، 16 و 24 GB جا می‌شود

لینک به بخش: چه چیزی در 8، 16 و 24 GB جا می‌شود

همه‌چیز در این فصل حالا یک جمع است:

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 در همه requestهای concurrent است. اعمالش کنیم: ردیف‌های 7B و 70B فرض می‌کنند 8 key-value head با dimension 128 داریم، ردیف 13B full multi-head attention با 40 head، چون آن generationهای model این‌طور ساخته شده بودند — و خودش را نشان می‌دهد.

8 GB

modelprecisionweightsfree after overheadtokenهای context که جا می‌شوند
7Bfp1613.0 GBجا نمی‌شود
7Bint86.5 GBجا نمی‌شود
7Bint4 (g128)3.4 GB3.1 GB25,710
13Bint4 (g128)6.2 GB0.3 GB337
70Bint4 (g128)33.6 GBجا نمی‌شود

16 GB

modelprecisionweightsfree after overheadtokenهای context که جا می‌شوند
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 overheadtokenهای context که جا می‌شوند
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 GBجا نمی‌شود

به ردیف 13B در جدول 8 GB نگاه کنید. وزن‌ها جا می‌شوند — 6.2 GB از 8 — پس طبق شیوه معمول صحبت‌کردن، یک مدل 13B «روی یک کارت 8 GB اجرا می‌شود». 337 token context دارد، که conversation نیست و به‌زحمت prompt است. «آیا جا می‌شود» سؤال غلط است. سؤال درست این است: «با چقدر context، و برای چند user به‌طور هم‌زمان».

به دو ردیف int8 در 16 GB هم نگاه کنید. 7B به 65,378 token می‌رسد و 13B به 3,136 — تفاوتی بیست‌برابری از 5.6 GB وزن اضافه، چون 13B این‌جا multi-head attention دارد و cache آن 800 KB به‌ازای هر token هزینه دارد در برابر 128 KB برای 7B. دو مدل با size مشابه، یکی برای long context غیرقابل استفاده، به دلیلی که در headline هیچ model cardی ظاهر نمی‌شود.

سیزده فصل پیش این یک perceptron با دو وزن و یک bias بود. حالا transformerی است که طراحی، trained، aligned و یاد داده شده که روی پرسش‌های سخت compute خرج کند، و با هزینه اندازه‌گیری‌شده به‌ازای هر token served می‌شود — بدون هیچ جعبه‌ای که درونش باز نشده باشد.

اینجا تمام می‌شود، و عامدانه تمام می‌شود.

فصل 14 با مدل در جایی دیگر شروع می‌شود. نه در process شما، نه در memory شما، نه در variableی که بتوانید print کنید: روی ماشینی که شما administer نمی‌کنید، پشت یک API key، یک port و یک bill. همه‌چیزهایی که این‌جا اندازه‌گیری شدند همچنان رخ می‌دهند — prefill هنوز قبل از token اول اجرا می‌شود، cache هنوز با conversation رشد می‌کند، batchی که شما در آن هستید هنوز مال کس دیگری است و هنوز latency شما را تعیین می‌کند — اما از این به بعد آن را از طریق streamی از Server-Sent Events، یک finish_reason، و یک HTTP 429 با header Retry-After مشاهده می‌کنید. پرسش‌ها با نقطه دید تغییر می‌کنند: نه این gradient چگونه محاسبه می‌شود بلکه چرا invoice من سه برابر شد. زبان هم همین‌طور، و فصل 14 به‌جای اعلام‌کردن این rule آن را توضیح می‌دهد — تا این‌جا code وزن‌ها، gradientها، logits و byteهای tokenizer را نگه می‌داشت؛ از آن‌جا به بعد connection، retry، cancellation و accumulated state را نگه می‌دارد. سیزده فصلی که پشت سر گذاشتید با این عبور دور ریخته نمی‌شوند. آن‌ها توصیف چیزی هستند که آن سوی port در حال اجراست.


دو omission عمدی‌اند. FlashAttention (Dao et al., arXiv:2205.14135) attention متفاوتی نیست — همان function را با tiling operation محاسبه می‌کند تا ماتریس score با اندازه n×nn \times n هرگز در memory نوشته نشود، و به همین دلیل 67 MB در جدول دوم این فصل در practice از چیزی که arithmetic نشان می‌دهد کوچک‌تر است. و خود kernelها delegated شده‌اند: lecture 10 از CS336 استنفورد inference systemها را با عمقی پوشش می‌دهد که این متن تلاش نمی‌کند، و repository مربوط به llama.cpp و specification مربوط به GGUF منابع primary برای سمت CPU هستند.

  1. Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019). مقاله عمدتاً یک استدلال memory-bandwidth است، و همین‌طور هم خوانده می‌شود.

  2. Ainslie, J. et al. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023). شامل recipe مربوط به uptraining است که یک checkpoint چندسری موجود را تبدیل می‌کند، و به همین دلیل 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. مقاله‌ای که vLLM بر آن ساخته شده؛ §3 قیاس operating-system را کامل توضیح می‌دهد.

  5. Dettmers, T., Pagnoni, A., Holtzman, A. and Zettlemoyer, L. QLoRA: Efficient Finetuning of Quantized LLMs. arXiv:2305.14314 (2023). NF4 در §3 تعریف شده؛ شانزده مقدار level استفاده‌شده در measurement بالا همان‌هایی هستند که این مقاله 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 منبع پدیده‌ای است که بالا اندازه‌گیری شد، از جمله یافته‌ای که outlierها در scale به‌صورت systematic ظاهر می‌شوند. 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 اثبات می‌کند که output distribution unchanged می‌ماند؛ Chen و همکاران (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، نه سال زودتر، برای ensembleها نه transformerها.


تهیه‌شده توسط

David Vicente Campos

بنیان‌گذار NeuraLIA Labs و هم‌بنیان‌گذار MyRealFood

من مهندس کامپیوتر و فارغ‌التحصیل دانشگاه لئون هستم. هم‌بنیان‌گذار MyRealFood بودم، جایی که به‌عنوان مدیر ارشد فناوری اپلیکیشنی را ساختم که میلیون‌ها نفر برای سالم‌تر غذا خوردن از آن استفاده کرده‌اند، و NeuraLIA Labs را بنیان‌گذاری کردم؛ جایی که محصولات هوش مصنوعی می‌سازم. اینجا از چیزهایی می‌نویسم که در طول مسیر باید می‌فهمیدم، همان‌طور که دوست داشتم کسی برایم توضیح می‌داد.

بیشتر درباره نویسنده

منتشرشده توسط NeuraLIA Labs.

پست‌های جدید را در ایمیل خود دریافت کنید

اخبار AI، راهنماها و به‌روزرسانی‌های محصول — هر وقت چیزی ارزشمند منتشر کنیم، یک ایمیل کوتاه می‌فرستیم.

فهرست دوره

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev12 دقیقه مطالعه

مدل هوش مصنوعی Jev برای تصمیم ساخته شده، نه نثر

Jev از TypeSafe AI توجه‌ها را جلب کرده چون هوشمندی نرم‌افزار را مسئله‌ای احتمالاتی می‌بیند: شاخه درست را انتخاب کنید، میزان اطمینان را کنار آن بگذارید، و وقتی کد به یک تصمیم نیاز دارد برای نوشتن متن به یک LLM پول ندهید.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering13 دقیقه مطالعه

مهندسی کانتکست برای عامل‌های AI بلندافق

عامل‌های طولانی‌اجرا فقط به‌خاطر کوچک بودن پنجره شکست نمی‌خورند. وقتی فایل‌ها، خروجی ابزارها و تاریخچهٔ کهنه وظیفه‌ای را که عامل قرار بود تمام کند کنار می‌زنند، شکست رخ می‌دهد.

آماده‌اید انتخاب مدل را به LIA بسپارید؟

با همه مدل‌های هوش مصنوعی در یک جا بسازید — همین امروز رایگان شروع کنید.