SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor

تفاوت Prefill و Decode در مدل‌های زبانی بزرگ

مرحله Prefill محدود به توان پردازشی و مسئول Time to first token است، در حالی که Decode محدود به پهنای باند حافظه و تعیین‌کننده Tokens per second است. این دو را جداگانه تحلیل کنید.

تفاوت Prefill و Decode در یک پاراگراف

تفاوت میان Prefill و Decode اصلی‌ترین عاملی است که اکثر پرسش‌های مربوط به تأخیر (latency) در مدل‌های زبانی بزرگ (LLM) خود-میزبانی‌شده را پاسخ می‌دهد. مرحله Prefill کل prompt را در یک گذر (pass) می‌خواند و محدود به توان پردازشی (compute) است. مرحله Decode پاسخ را توکن به توکن می‌نویسد و محدود به پهنای باند حافظه (memory bandwidth) است. شاخص Time to first token مربوط به مرحله Prefill و شاخص Tokens per second مربوط به مرحله Decode است.

هر دو مرحله روی یک GPU یکسان، با وزن‌های مشابه و در یک پردازش واحد اجرا می‌شوند؛ بنابراین طبیعی است که آن‌ها را به عنوان یک بار کاری واحد در نظر بگیریم. با این حال، آن‌ها مانند دو برنامه متفاوت رفتار می‌کنند که از یک دستگاه مشترک استفاده می‌کنند. اگر این دو را از هم تفکیک کنید، فهرست طولانی نتایج گیج‌کننده، دیگر مبهم نخواهد بود.

چرا مرحله prefill محدود به توان پردازشی (compute bound) است؟

مرحله prefill کل prompt را یک‌بار از تمام لایه‌ها عبور می‌دهد. یک prompt با 2,000 توکن، برای هر ضرب ماتریسی 2,000 ردیف کار ایجاد می‌کند؛ بنابراین GPU به ازای هر بایت از وزن‌هایی که بارگذاری می‌کند، حجم زیادی عملیات محاسباتی انجام می‌دهد. این نسبت، یعنی میزان محاسبات به ازای هر بایت جابه‌جا شده، شدت محاسباتی (arithmetic intensity) نامیده می‌شود و prefill دارای شدت بالایی است. در این حالت، دستگاه نزدیک به حد توان پردازشی خود کار می‌کند و گذرگاه حافظه (memory bus) دارای ظرفیت خالی است.

مرحله prefill دو خروجی تولید می‌کند: KV cache (تنسورهای کلید و مقدار) برای تمام توکن‌های prompt، و اولین توکن خروجی. تا زمانی که این مرحله به پایان نرسد، هیچ داده‌ای به کاربر نمی‌رسد؛ به همین دلیل است که زمان prefill و زمان رسیدن به اولین توکن (TTFT) تقریباً یکسان اندازه‌گیری می‌شوند.

هزینه prefill با افزایش طول prompt رشد می‌کند. بخش خطی آن مربوط به عملیات ماتریسی در هر لایه است. بخش درجه دوم (quadratic) مربوط به مکانیزم attention است که در آن هر توکن به تمام توکن‌های قبلی توجه می‌کند و این موضوع در contextهای طولانی اهمیت می‌یابد. بنابراین، دو برابر کردن طول prompt، حداقل TTFT را دو برابر می‌کند.

شما می‌توانید این موضوع را در یک دقیقه مشاهده کنید. یک prompt با 200 توکن به سرور خود بفرستید و سپس یک prompt با 2,000 توکن ارسال کنید، در حالی که از سرور می‌خواهید در هر دو حالت تعداد یکسانی توکن خروجی تولید کند. خواهید دید که TTFT به‌شدت افزایش می‌یابد، اما سرعت استریم کردن پس از تولید اولین توکن، تغییر چندانی نمی‌کند.

چرا عملیات Decode محدود به پهنای باند حافظه است؟

عملیات Decode در هر گام یک توکن تولید می‌کند. برای تولید آن یک توکن، GPU باید تمام وزن‌های مدل را از حافظه بخواند، از هر وزن برای چند عملیات استفاده کند و سپس آن را دور بریزد. شدت محاسباتی (Arithmetic intensity) نزدیک به 1 است، بنابراین واحدهای محاسباتی بیشتر وقت خود را در حالت انتظار سپری می‌کنند.

عملیات Decode کند است زیرا هر توکن نیازمند خواندن کل مدل از حافظه است؛ بنابراین گذرگاه حافظه سرعت را تعیین می‌کند و واحدهای محاسباتی بیکار می‌مانند.

این موضوع باعث می‌شود سقف سرعت Decode برای یک جریان واحد، صرفاً یک محاسبهٔ ریاضی ساده روی کاغذ باشد. پهنای باند حافظه را بر تعداد بایت‌های اشغال‌شده توسط وزن‌ها تقسیم کنید.

ChartPublished memory bandwidth and the decode ceiling it implies for a 16 GB model
The data behind this chart
[
  {
    "device": "CPU, dual channel DDR5-5600",
    "mem_bandwidth_gb_s": 90,
    "decode_ceiling_tok_s": 6
  },
  {
    "device": "NVIDIA A10G",
    "mem_bandwidth_gb_s": 600,
    "decode_ceiling_tok_s": 38
  },
  {
    "device": "NVIDIA L40S",
    "mem_bandwidth_gb_s": 864,
    "decode_ceiling_tok_s": 54
  },
  {
    "device": "NVIDIA RTX 4090",
    "mem_bandwidth_gb_s": 1008,
    "decode_ceiling_tok_s": 63
  },
  {
    "device": "NVIDIA A100 80GB SXM",
    "mem_bandwidth_gb_s": 2039,
    "decode_ceiling_tok_s": 127
  },
  {
    "device": "NVIDIA H100 SXM",
    "mem_bandwidth_gb_s": 3350,
    "decode_ceiling_tok_s": 209
  }
]

ستون پهنای باند شامل ارقام مشخصات فنی منتشرشده توسط هر سازنده است. ستون سقف (ceiling) همان رقم تقسیم بر 16 گیگابایت است؛ یعنی حجم یک مدل 8 میلیارد پارامتری که با دقت 16 بیت ذخیره شده است. این یک محاسبهٔ ریاضی است، نه نتیجهٔ بنچمارک. نرخ اندازه‌گیری‌شدهٔ شما پایین‌تر از این مقدار خواهد بود و دانستن میزان این اختلاف مفید است، زیرا به شما می‌گوید که آیا باید پشتهٔ سرویس‌دهی (serving stack) خود را اصلاح کنید یا سخت‌افزارتان را.

ردیف‌های 6 را به ترتیب بخوانید تا الگو مشخص شود. یک CPU روی DDR5 دوکاناله حدود 90 گیگابایت بر ثانیه جابه‌جا می‌کند که سقف Decode را برای آن مدل به حدود 6 توکن در ثانیه محدود می‌کند. یک L40S به عددی نزدیک به 54 می‌رسد. یک H100 SXM با پهنای باند منتشرشدهٔ 3350 گیگابایت بر ثانیه، در حدود 209 قرار می‌گیرد.

به همین دلیل است که کوانتایزیشن (quantization) قوی‌ترین اهرم برای افزایش سرعت Decode محسوب می‌شود. اگر همان مدل را به جای 16 بیت با 8 بیت ذخیره کنید، بایت‌های خوانده‌شده به ازای هر توکن نصف می‌شود و در نتیجه سقف سرعت تقریباً دو برابر می‌گردد. شما هیچ محاسبهٔ اضافه‌ای انجام نداده‌اید، فقط حافظهٔ کمتری را جابه‌جا کرده‌اید.

چگونه می‌توانم هر مرحله را روی سرور شخصی خود اندازه‌گیری کنم؟

Ollama تفکیک مراحل را در بدنه پاسخ برمی‌گرداند. یک completion غیر جریانی (non-streaming) درخواست کنید و شمارنده‌ها را بخوانید.

curl -s http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Explain memory bandwidth in two sentences.",
  "stream": false
}' | jq '{prompt_eval_count, prompt_eval_duration, eval_count, eval_duration}'

از تگ مدلی استفاده کنید که واقعاً آن را pull کرده‌اید؛ این مورد را ollama list به شما نشان می‌دهد. prompt_eval_count و prompt_eval_duration مربوط به prefill هستند: تعداد توکن‌های پرامپت و زمان صرف‌شده برای آن. eval_count و eval_duration مربوط به decode هستند. مدت‌زمان‌ها به نانوثانیه هستند، بنابراین سرعت decode برابر با eval_count / eval_duration * 1e9 و سرعت prefill برابر با prompt_eval_count / prompt_eval_duration * 1e9 است. انتظار داشته باشید که نرخ prefill در یک درخواست مشابه، بسیار بالاتر از نرخ decode باشد. این شکاف همان چیزی است که تمام مطالب این بخش توضیح می‌دهند.

برای یک سرور سازگار با OpenAI مانند vLLM، دستور curl می‌تواند زمان رسیدن اولین بایت را برای شما اندازه‌گیری کند.

curl -N -s -o /dev/null \
  -w 'pretransfer %{time_pretransfer}s  first_byte %{time_starttransfer}s\n' \
  http://localhost:8000/v1/completions \
  -H 'Content-Type: application/json' \
  -d '{"model": "meta-llama/Llama-3.1-8B-Instruct", "prompt": "Explain memory bandwidth.", "max_tokens": 128, "stream": true}'

time_starttransfer لحظه‌ای است که اولین بایت بدنه دریافت شده است، بنابراین با "stream": true این مقدار برابر با TTFT به علاوه زمان برقراری اتصال است. مقدار time_pretransfer را کسر کنید تا هزینه برقراری اتصال حذف شود. این دستور را دو بار اجرا کنید و نتیجه دوم را نگه دارید، زیرا فراخوانی اول ممکن است شامل بارگذاری سرد (cold load) مدل باشد.

vLLM همچنین تفکیک مراحل را به عنوان معیارهای Prometheus در /metrics منتشر می‌کند. curl -s http://localhost:8000/metrics | grep -E 'time_to_first_token|inter_token_latency' را اجرا کنید تا هیستوگرام‌های vllm:time_to_first_token_seconds و vllm:inter_token_latency_seconds را دریافت کنید. برای عمق صف vllm:num_requests_running و vllm:num_requests_waiting، و برای فشار روی حافظه کش vllm:kv_cache_usage_perc را اضافه کنید. این پنج نام، کل داشبورد شما را تشکیل می‌دهند.

تحت بار کاری، vllm bench serve --model <name> --num-prompts 200 --request-rate 4 سرور در حال اجرا را مدیریت کرده و زمان رسیدن به اولین توکن و تأخیر هر توکن خروجی را با صدک‌ها گزارش می‌دهد؛ این تنها راه برای مشاهده تقابل این دو مرحله با یکدیگر است. پیش از هرگونه بهینه‌سازی، یک baseline دقیق تهیه کنید: روش موجود در اندازه‌گیری توکن بر ثانیه در یک LLM محلی به شما نتیجه‌ای می‌دهد که پس از reboot نیز باقی می‌ماند.

چرا یک system prompt طولانی باعث تأخیر در تولید اولین توکن می‌شود اما سرعت استریم را تغییر نمی‌دهد؟

دلیل این امر آن است که system prompt صرفاً بخشی از عملیات prefill است. این متن یک‌بار و در همان مرحله‌ای پردازش می‌شود که مابقی prompt پردازش شده است، تا اولین توکن ظاهر شود. پس از آن مرحله، prompt فقط به صورت ورودی‌های KV cache وجود دارد و در مرحله decode، این ورودی‌ها به همراه سایر داده‌ها خوانده می‌شوند. بنابراین، یک system prompt با 3000 توکن، در هر درخواست به TTFT اضافه می‌کند، در حالی که تعداد توکن در ثانیه تقریباً بدون تغییر باقی می‌ماند.

البته «تقریباً» و نه دقیقاً. آن ورودی‌های اضافی KV در هر گام decode دوباره خوانده می‌شوند، بنابراین یک prompt بسیار طولانی، سرعت decode را اندکی کاهش می‌دهد. بخش بعدی به این موضوع می‌پردازد.

راه حل، متوقف کردن محاسبه مجدد یک پیشوند (prefix) یکسان است. سروری که از prefix caching استفاده می‌کند، KV cache مربوط به یک پیشوند مشترک را نگه می‌دارد و از آن مجدداً استفاده می‌کند؛ بنابراین درخواست دومی که همان system prompt را به همراه دارد، آن بخش از prefill را کاملاً نادیده می‌گیرد. vLLM این قابلیت را automatic prefix caching می‌نامد؛ وضعیت vllm serve --help را در نسخه خود بررسی کنید، چرا که تنظیمات پیش‌فرض در نسخه‌های مختلف تغییر کرده است. آن KV cache موجود در GPU با prompt cache که ارائه‌دهنده API برای آن از شما هزینه دریافت می‌کند متفاوت است، و مطالعه تفاوت بین KV cache و prompt cache پیش از بهینه‌سازی هر یک از آن‌ها، ارزشمند است.

چرا با پر شدن کانتکست، سرعت رمزگشایی (decode) کاهش می‌یابد؟

دو دلیل برای این موضوع وجود دارد که هر دو به KV cache مربوط می‌شوند.

دلیل اول پهنای باند است. در هر مرحله از رمزگشایی، مکانیزم توجه (attention) کلیدها و مقادیر تمام توکن‌های قبلی را می‌خواند. وزن‌ها هزینه ثابتی برای هر توکن دارند، اما حجم KV cache در حال رشد است. شما می‌توانید اندازه آن را از روی config.json مدل محاسبه کنید: بایت به ازای هر توکن برابر است با 2 ضرب در num_hidden_layers، ضرب در num_key_value_heads، ضرب در ابعاد هد (hidden_size تقسیم بر num_attention_heads)، ضرب در بایت به ازای هر عنصر. عدد 2 در ابتدا، نشان‌دهنده یک کلید و یک مقدار است.

برای یک مدل رایج 8 میلیارد پارامتری با 32 لایه، 8 هد کلید و مقدار تحت GQA (توجه گروهی)، ابعاد هد 128 و دقت 16 بیتی، این مقدار برابر است با 2 x 32 x 8 x 128 x 2 = 131,072 بایت، یعنی حدود 128 کیلوبایت به ازای هر توکن. بنابراین یک مکالمه 8,000 توکنی، تقریباً 1 گیگابایت KV cache به ازای هر درخواست اشغال می‌کند.

دلیل دوم ظرفیت است. آن 1 گیگابایت حافظه‌ای است که نمی‌تواند وزن‌ها یا کانتکست کاربر دیگری را در خود جای دهد. سرور اندازه استخر KV خود را یک‌بار در زمان راه‌اندازی تعیین می‌کند (در vLLM از طریق --gpu-memory-utilization) و وقتی استخر پر شود، درخواست‌های جدید منتظر می‌مانند. بالا رفتن vllm:num_requests_waiting در حالی که vllm:kv_cache_usage_perc نزدیک به 1 باقی می‌ماند، دقیقاً نشانه این وضعیت است. برخی پشته‌های نرم‌افزاری به جای قرار دادن درخواست در صف، یک درخواست در حال اجرا را متوقف (preempt) کرده و بعداً کش آن را دوباره محاسبه می‌کنند که کاربر آن را به صورت وقفه در وسط جریان پاسخ تجربه می‌کند.

کانتکست طولانی دو بار برای شما هزینه دارد: کار بیشتر برای prefill در ابتدا، و خواندن حافظه بیشتر به ازای هر توکن برای باقی پاسخ.

چرا دسته‌بندی (batching) باعث افزایش توان عملیاتی (throughput) و کاهش تأخیر دم‌توزیع (tail latency) می‌شود؟

از آنجا که عملیات رمزگشایی (decode) محدود به پهنای باند است، پردازش درخواست‌های اضافی از نظر محاسباتی تقریباً رایگان محسوب می‌شود. یک بار خواندن وزن‌ها (weights) می‌تواند برای هر توالی در دسته، یک توکن تولید کند؛ بنابراین توان عملیاتی کل تقریباً به‌صورت خطی با افزایش اندازه دسته رشد می‌کند، تا زمانی که یا فضای استخر KV تمام شود یا اندازه دسته به‌قدری بزرگ شود که دوباره به محدودیت محاسباتی برسد. دسته‌بندی پیوسته (continuous batching) در هر گام دسته را بازسازی می‌کند؛ بنابراین درخواست تکمیل‌شده خارج می‌شود و درخواست در صف قرار گرفته، بدون انتظار برای سایر همسایگان، جایگزین آن می‌شود.

هزینه این کار در صدک‌ها (percentiles) نمایان می‌شود. اکنون توکن بعدی هر کاربر منتظر کندترین بخش از یک گام مشترک می‌ماند؛ در نتیجه p50 (میانه) قابل‌قبول باقی می‌ماند، اما p99 (کندترین 1 درخواست از هر 100 درخواست) افزایش می‌یابد. کاربران p99 را حس می‌کنند، زیرا این همان وقفه‌ای است که در میان یک جمله رخ می‌دهد.

عملیات پیش‌پر کردن (prefill) این وضعیت را حادتر می‌کند. یک پرامپت بزرگ که در میانه جریان داده می‌رسد، دستگاه را برای یک گام طولانی اشغال می‌کند و همه کسانی که در حال دریافت استریم هستند، با وقفه مواجه می‌شوند. پیش‌پر کردن تکه‌تکه (chunked prefill) با خرد کردن پرامپت‌های طولانی به قطعات کوچک‌تر و ترکیب هر قطعه با دسته‌های رمزگشایی، بخش بزرگی از این مشکل را حل می‌کند. از اوت 2026، موتور vLLM V1 این کار را به‌صورت پیش‌فرض انجام می‌دهد و تعادل آن را از طریق --max-num-batched-tokens در دسترس قرار می‌دهد. مستندات تنظیمات vLLM این بده‌بستان را به‌صراحت بیان می‌کند: مقادیر کوچک‌تر، در حدود 2048، تأخیر بین توکن‌ها (ITL) بهتری ارائه می‌دهند زیرا تعداد دفعات کمتری پیش‌پر کردن باعث وقفه در رمزگشایی می‌شود، و مقادیر بزرگ‌تر TTFT بهتری می‌دهند زیرا توکن‌های پیش‌پر کردن بیشتری در یک دسته جای می‌گیرند. آن پرچم واحد، تقابل پیش‌پر کردن در برابر رمزگشایی است که به‌صورت عددی قابل‌تنظیم ارائه شده است. اینکه p99 در چه نقطه‌ای دیگر قابل‌قبول نیست، یک مسئله ظرفیت است و تعداد کاربران هم‌زمانی که یک LLM خودمیزبان می‌تواند سرویس‌دهی کند با استفاده از همین معیارها به آن می‌پردازد.

چرا کارت گرافیک قوی‌تر گاهی هیچ تغییری ایجاد نمی‌کند؟

زیرا «قوی‌تر» معمولاً به معنای توان پردازشی (compute) بیشتر است، در حالی که عملیات decode به توان پردازشی وابسته نیست.

دو ردیف از جدول بالا را مقایسه کنید. کارت A100 80GB دارای پهنای باند اعلام‌شده 2039 گیگابایت بر ثانیه در برابر کارت L40S با 864 گیگابایت بر ثانیه است و سقف decode دقیقاً از همین نسبت پیروی می‌کند: 127 توکن در ثانیه در مقابل 54. کارت RTX 4090 از اکثر جهات یک کارت بسیار سریع محسوب می‌شود و پهنای باند 1008 گیگابایت بر ثانیه‌ای آن، سقف عملکردش را روی 63 قرار می‌دهد. فارغ از هر تفاوت دیگری بین دو کارت، سرعت decode تک‌جریانی (single stream) دقیقاً با خط پهنای باند در برگه مشخصات فنی همخوانی دارد.

بنابراین دو راه برای افزایش سرعت decode وجود دارد: خواندن بایت‌های کمتر به ازای هر توکن (کوانتیزه کردن وزن‌ها یا اجرای یک مدل کوچک‌تر)، یا خرید پهنای باند بیشتر. مرحله prefill دقیقاً برعکس است. این مرحله به توان پردازشی نیاز دارد، بنابراین یک کارت سریع‌تر واقعاً زمان TTFT را در پرامپت‌های طولانی کاهش می‌دهد. اگر مشکل این است که تولید اولین توکن 4 ثانیه طول می‌کشد، سخت‌افزار بهتر ممکن است آن را حل کند. اما اگر مشکل این است که متن به‌کندی تایپ می‌شود، سخت‌افزار جدید احتمالاً تأثیری نخواهد داشت.

آیا باید prefill و decode را روی workerهای جداگانه اجرا کرد؟

پشته‌های سرویس‌دهی بزرگ دقیقاً همین کار را انجام می‌دهند و این تکنیک، تفکیک (disaggregation) بین prefill و decode نامیده می‌شود. یک گروه از workerها فقط prefill را اجرا می‌کنند، گروه دوم فقط decode را انجام می‌دهند و KV cache ساخته‌شده توسط گروه اول، از طریق یک interconnect سریع به گروه دوم منتقل می‌شود. این روش به این دلیل کارآمد است که این دو مرحله به سخت‌افزار و زمان‌بندی متفاوتی نیاز دارند. مرحله prefill به توان پردازشی و دسته‌های بزرگ توکن نیاز دارد، در حالی که مرحله decode به پهنای باند و تعداد زیادی دنباله (sequence) همزمان نیازمند است. تفکیک این دو مرحله اجازه می‌دهد هر گروه به‌طور مستقل مقیاس‌پذیر شود و از متوقف شدن تمام جریان‌های فعال توسط یک prompt بسیار بزرگ جلوگیری می‌کند.

روی یک VPS (سرور مجازی) با یک GPU، انجام این کار تقریباً هرگز منطقی نیست. شما در واقع یک دستگاه را علیه خودش تقسیم می‌کنید و یک اشاره‌گر (pointer) را به انتقال شبکه‌ایِ گیگابایت‌ها داده در cache تبدیل می‌کنید. این تکنیک زمانی سودمند است که به اندازه کافی شتاب‌دهنده داشته باشید تا بتوانید ماشین‌های کامل را به هر مرحله اختصاص دهید و ترافیک پایداری داشته باشید که هر دو گروه را مشغول نگه دارد. در مقیاس‌های کوچک‌تر، chunked prefill با استفاده از یک flag، تقریباً همان سطح از جداسازی را فراهم می‌کند.

در صورت نامناسب بودن اعداد چه چیزی را تغییر دهیم

هنگامی که TTFT بیش از حد بالا است:

  • طول پرامپت (prompt) را کوتاه کنید. هزینه Prefill با توکن‌های پرامپت محاسبه می‌شود و سیستم پرامپت در هر درخواست پرداخت می‌شود.
  • قابلیت prefix caching را فعال کنید تا یک پیشوند تکراری به‌جای هر بار محاسبه، فقط یک‌بار پردازش شود.
  • مقدار --max-num-batched-tokens را افزایش دهید تا حجم کاری بیشتری در هر مرحله از prefill قرار گیرد.
  • پیش از مقصر دانستن مدل، صف (queue) را بررسی کنید. مقدار vllm:num_requests_waiting بالاتر از صفر به این معنی است که درخواست هنوز شروع نشده است که نشان‌دهنده مشکل در ظرفیت است.

هنگامی که تعداد توکن در ثانیه (tokens per second) بیش از حد پایین است:

  • وزن‌ها (weights) را کوانتیزه (quantize) کنید. بایت کمتر برای هر وزن به معنای خواندن بایت کمتر برای هر توکن است.
  • پهنای باند حافظه کارت خود را با نمودار بالا مقایسه کنید و ببینید چقدر به سقف توانایی آن نزدیک هستید.
  • مقدار --max-num-batched-tokens را کاهش دهید تا عملیات prefill وقفه کمتری در decode ایجاد کند.
  • طول کانتکست (context length) را بررسی کنید. مکالمه‌ای که به هزاران توکن رسیده است، در هر مرحله یک KV cache بسیار بزرگ‌تر را می‌خواند.

Runtime نیز در اینجا اهمیت دارد، زیرا Ollama و vLLM عملیات prefill و decode را به روش‌های متفاوتی زمان‌بندی می‌کنند و تنظیمی که به یکی کمک می‌کند ممکن است در دیگری بی‌اثر باشد. ابتدا در هر دو فاز اندازه‌گیری کنید و سپس فقط یک مورد را تغییر دهید.

FAQ

چرا اولین توکن من چند ثانیه طول می‌کشد اما بقیه توکن‌ها سریع استریم می‌شوند؟

این زمان انتظار مربوط به مرحله prefill و استریم مربوط به مرحله decode است. در مرحله prefill، کل prompt در یک مرحله محاسباتی پردازش می‌شود تا اولین خروجی تولید شود، بنابراین هزینه آن با طول prompt افزایش می‌یابد. در مرحله decode، در هر گام یک توکن تولید می‌شود که نرخ آن توسط پهنای باند حافظه تعیین می‌گردد و تقریباً مستقل از طول prompt اولیه است. وجود یک system prompt طولانی در هر درخواست، دلیل معمول این تأخیر است. قابلیت prefix caching این بخش تکراری از هزینه را حذف می‌کند.

آیا prompt طولانی‌تر باعث کاهش سرعت تولید توکن در ثانیه می‌شود؟

کمی، اما به دلیلی متفاوت از TTFT. در هر گام decode، کلیدها و مقادیر (KV cache) تمام توکن‌های قبلی خوانده می‌شوند، بنابراین KV cache بزرگ‌تر به معنای خواندن بایت‌های بیشتر در هر توکن است. برای یک مدل رایج 8 میلیارد پارامتری، حافظه کش حدود 128 KiB به ازای هر توکن است، بنابراین یک context با 8,000 توکن تقریباً 1 GB حجم دارد که در هر گام پردازش می‌شود. با این حال، تأثیر اصلی prompt طولانی همچنان بر TTFT است، نه بر سرعت استریم.

کدام مشخصه GPU سرعت decode را تعیین می‌کند؟

پهنای باند حافظه. اگر پهنای باند اعلام‌شده را بر حجم وزن‌های (weights) موجود در حافظه تقسیم کنید، سقف محاسباتی برای یک استریم به دست می‌آید. کارتی که قدرت محاسباتی بیشتری دارد اما پهنای باند آن یکسان است، استریم سریع‌تری نخواهد داشت. به همین دلیل است که کوانتایز کردن به 8 بیت، سرعت decode را تقریباً دو برابر می‌کند: این کار بدون تغییر در محاسبات، تعداد بایت‌های خوانده‌شده به ازای هر توکن را نصف می‌کند.

چرا با افزودن کاربران، نرخ throughput افزایش می‌یابد اما هر کاربر سرعت کمتری را تجربه می‌کند؟

یک بار خواندن وزن‌ها، یک توکن برای تمام دنباله‌ها (sequences) در batch تولید می‌کند، بنابراین مجموع توکن‌های تولیدشده در ثانیه با افزایش batch size بالا می‌رود. از آنجا که هر توکنِ فردی اکنون باید منتظر یک گام مشترک بماند، تأخیر (latency) برای هر کاربر هم‌زمان افزایش می‌یابد. به جای عدد کلی throughput، شاخص p99 inter-token latency را مانیتور کنید و vllm:num_requests_waiting را بررسی کنید تا ببینید آیا درخواست‌ها در صف انتظار هستند یا در حال اجرا.