LLM-ல் Prefill மற்றும் Decode ஏன் மெதுவாகிறது?
Prefill என்பது compute bound, இது time to first token-ஐ தீர்மானிக்கிறது. Decode என்பது memory bandwidth bound, இது tokens per second-ஐ தீர்மானிக்கிறது. இவற்றைத் தனித்தனியாக அளவிடுங்கள்.
Prefill மற்றும் decode ஒப்பீடு
Self-hosted LLM-ல் ஏற்படும் பெரும்பாலான latency சிக்கல்களைப் புரிந்துகொள்ள Prefill மற்றும் decode ஆகியவற்றுக்கு இடையேயான வேறுபாடு மிக முக்கியமானது. Prefill என்பது கொடுக்கப்பட்ட prompt முழுவதையும் ஒரே நேரத்தில் வாசிக்கும் செயல்முறை; இது compute திறனைச் சார்ந்தது. Decode என்பது பதிலை ஒவ்வொரு token-ஆக உருவாக்கும் செயல்முறை; இது memory bandwidth-ஐச் சார்ந்தது. Time to first token என்பது Prefill-ஐக் குறிக்கும் அளவீடு, Tokens per second என்பது decode-ஐக் குறிக்கும் அளவீடு. இவை இரண்டும் ஒரே GPU-வில், ஒரே weights-ஐப் பயன்படுத்தி, ஒரே process-க்குள் இயங்குவதால், இவற்றை ஒரே workload-ஆகக் கருதுவது இயல்பானது. ஆனால், இவை ஒரே சாதனத்தைப் பகிர்ந்து கொள்ளும் இரண்டு வெவ்வேறு நிரல்களைப் போலச் செயல்படுகின்றன. இவற்றைத் தனித்தனியாகப் பிரித்துப் பார்த்தால், குழப்பமான முடிவுகள் அனைத்தும் தெளிவாகிவிடும்.
Prefill ஏன் compute bound ஆக உள்ளது?
Prefill, முழு prompt-ஐயும் ஒவ்வொரு layer வழியாகவும் ஒருமுறை செலுத்துகிறது. 2,000 token கொண்ட ஒரு prompt, ஒவ்வொரு matrix multiply-க்கும் 2,000 வரிகள் கொண்ட வேலையை வழங்குகிறது; எனவே, GPU தான் ஏற்றும் ஒவ்வொரு byte weight-க்கும் அதிகப்படியான கணிதச் செயல்பாடுகளைச் செய்கிறது. இந்த விகிதம், அதாவது நகர்த்தப்படும் ஒவ்வொரு byte-க்கும் செய்யப்படும் கணிதச் செயல்பாடு, arithmetic intensity என்று அழைக்கப்படுகிறது; prefill-க்கு இந்த அளவு அதிகமாக உள்ளது. சாதனம் அதன் compute limit-க்கு அருகில் இயங்குகிறது, மேலும் memory bus-ல் சுமை குறைவாகவே உள்ளது.
Prefill இரண்டு விஷயங்களை உருவாக்குகிறது: ஒவ்வொரு prompt token-க்கும் தேவையான KV cache (key மற்றும் value tensors) மற்றும் முதல் output token. அந்த pass முடியும் வரை எதையும் பயனர் பெற முடியாது; இதனால்தான் prefill நேரமும், time to first token (TTFT)-ம் கிட்டத்தட்ட ஒரே அளவீடாக உள்ளன.
Prompt-ன் நீளம் அதிகரிக்க அதிகரிக்க prefill செலவும் அதிகரிக்கிறது. இதில் linear பகுதி என்பது ஒவ்வொரு layer-லும் நடக்கும் matrix வேலை ஆகும். Quadratic பகுதி என்பது attention ஆகும்; இதில் ஒவ்வொரு token-ம் தனக்கு முந்தைய அனைத்து token-களையும் கவனிக்கிறது, இது நீண்ட context-ல் முக்கியத்துவம் பெறுகிறது. எனவே, prompt-ன் அளவை இரட்டிப்பாக்கும்போது TTFT குறைந்தது இரண்டு மடங்காகிறது.
இதை நீங்கள் ஒரு நிமிடத்தில் சரிபார்க்கலாம். உங்கள் server-க்கு 200 token கொண்ட ஒரு prompt-ஐயும், பின்னர் 2,000 token கொண்ட ஒரு prompt-ஐயும் அனுப்பி, இரண்டிலும் ஒரே எண்ணிக்கையிலான output token-களைக் கேட்கவும். TTFT மிக வேகமாக உயரும். ஆனால், முதல் token-க்கு பிறகு streaming வேகம் பெரிய அளவில் மாறாது.
Decode ஏன் memory bandwidth-ஐச் சார்ந்து இயங்குகிறது?
Decode செயல்பாட்டில் ஒவ்வொரு படியிலும் ஒரு token உருவாக்கப்படுகிறது. அந்த ஒரு token-ஐ உருவாக்க, GPU-வானது model-ல் உள்ள அனைத்து weight-களையும் memory-யிலிருந்து வாசிக்க வேண்டும், ஒவ்வொரு weight-ஐயும் ஒரு சில கணக்கீடுகளுக்குப் பயன்படுத்த வேண்டும், பிறகு அதை நீக்க வேண்டும். இதன் arithmetic intensity 1-க்கு அருகில் இருப்பதால், compute units பெரும்பாலும் காத்திருப்பதிலேயே நேரத்தைச் செலவிடுகின்றன.
ஒவ்வொரு token-ஐ உருவாக்கவும் முழு model-ஐயும் memory-யிலிருந்து வாசிக்க வேண்டியிருப்பதால், decode வேகம் குறைவாக உள்ளது. எனவே, memory bus-ன் வேகமே ஒட்டுமொத்த வேகத்தைத் தீர்மானிக்கிறது, compute units வேலையின்றி இருக்கின்றன.
இதன் காரணமாக, single stream decode வேகத்திற்கான உச்ச வரம்பை (ceiling) காகிதத்தில் கணக்கிட முடியும். Memory bandwidth-ஐ, அந்த weight-கள் ஆக்கிரமிக்கும் bytes அளவால் வகுக்கவும்.
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
}
]Bandwidth நெடுவரிசையில் ஒவ்வொரு vendor-ம் வெளியிட்ட அதிகாரப்பூர்வ விவரக்குறிப்புகள் உள்ளன. Ceiling நெடுவரிசையில், அந்த மதிப்பை 16 GB-ஆல் வகுத்த விடை உள்ளது; இது 16-bit precision-ல் சேமிக்கப்பட்ட 8 billion parameter model-ன் அளவாகும். இது ஒரு கணக்கீடு மட்டுமே, benchmark முடிவு அல்ல. நீங்கள் அளவிடும் வேகம் இதைவிடக் குறைவாகவே இருக்கும்; அது எவ்வளவு குறைவாக உள்ளது என்பதை அறிவது பயனுள்ளது, ஏனெனில் உங்கள் serving stack-ஐச் சரிசெய்ய வேண்டுமா அல்லது hardware-ஐ மாற்ற வேண்டுமா என்பதை அது உணர்த்தும்.
6 வரிசைகளை வரிசையாகப் பார்த்தால், இந்த அமைப்பு தெளிவாகப் புரியும். Dual channel DDR5 கொண்ட ஒரு CPU சுமார் 90 GB/s வேகத்தில் தரவை நகர்த்துகிறது, இது அந்த model-க்கு decode வேகத்தை 6 tokens per second என்ற அளவில் கட்டுப்படுத்துகிறது. ஒரு L40S இந்த வேகத்தை 54 அளவில் அடைகிறது. 3350 GB/s bandwidth கொண்ட ஒரு H100 SXM, 209 வேகத்திற்கு அருகில் இயங்குகிறது.
Decode வேகத்தை அதிகரிப்பதில் quantization ஏன் மிக முக்கியமான காரணியாக உள்ளது என்பதற்கும் இதுவே காரணம். அதே model-ஐ 16-bit-க்கு பதிலாக 8-bit-ல் சேமிக்கும்போது, ஒரு token-க்கு வாசிக்கப்படும் bytes அளவு பாதியாகக் குறைகிறது, இதனால் உச்ச வரம்பு தோராயமாக இரண்டு மடங்காகிறது. நீங்கள் கூடுதல் கணக்கீடுகளைச் செய்யவில்லை, ஆனால் குறைவான memory தரவையே நகர்த்துகிறீர்கள்.
எனது சொந்த server-ல் ஒவ்வொரு கட்டத்தையும் எவ்வாறு அளவிடுவது?
Ollama அதன் response body-ல் இந்த பிரிவினையை வழங்குகிறது. Streaming இல்லாத completion-ஐக் கோரி, counters-ஐப் படிக்கவும்.
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 செய்த model tag-ஐப் பயன்படுத்தவும், அதை ollama list உங்களுக்குக் காட்டும். prompt_eval_count மற்றும் prompt_eval_duration ஆகியவை prefill தொடர்பானவை: prompt token எண்ணிக்கை மற்றும் அதற்காகச் செலவிடப்பட்ட நேரம். eval_count மற்றும் eval_duration ஆகியவை decode தொடர்பானவை. கால அளவுகள் nanoseconds-ல் இருப்பதால், decode வேகம் eval_count / eval_duration * 1e9 மற்றும் prefill வேகம் prompt_eval_count / prompt_eval_duration * 1e9 ஆகும். ஒரே கோரிக்கையில், decode வேகத்தை விட prefill வேகம் மிக அதிகமாக இருக்கும் என்று எதிர்பார்க்கலாம். அந்த இடைவெளிதான் இங்கு விளக்கப்படும் மற்ற அனைத்திற்கும் காரணமாகும்.
vLLM போன்ற OpenAI இணக்கமான server-க்கு, curl மூலம் முதல் byte-க்கான நேரத்தை உங்களால் கணக்கிட முடியும்.
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 என்பது முதல் body byte வந்தடைந்த தருணம், எனவே "stream": true உடன் சேர்த்துப் பார்க்கும்போது அது TTFT மற்றும் connection setup நேரத்தைக் குறிக்கும். Setup செலவைக் கழிக்க time_pretransfer-ஐப் பயன்படுத்தவும். இதை இரண்டு முறை இயக்கவும், இரண்டாவது முடிவை மட்டும் வைத்துக்கொள்ளவும்; ஏனெனில் முதல் அழைப்பில் cold model load இருக்கலாம்.
vLLM இந்த பிரிவினையை Prometheus metrics ஆக /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 ஆகிய histograms கிடைக்கும். Queue depth-க்கு vllm:num_requests_running மற்றும் vllm:num_requests_waiting-ஐயும், cache pressure-க்கு vllm:kv_cache_usage_perc-ஐயும் சேர்க்கவும். இந்த ஐந்து பெயர்களுமே முழு dashboard-க்குத் தேவையானவை.
சுமை (load) இருக்கும்போது, vllm bench serve --model <name> --num-prompts 200 --request-rate 4 இயங்கும் server-ஐக் கட்டுப்படுத்தி, time to first token மற்றும் ஒரு output token-க்கான latency ஆகியவற்றை percentiles அடிப்படையில் தெரிவிக்கும்; இந்த இரண்டு கட்டங்களும் எவ்வாறு ஒன்றோடொன்று போட்டியிடுகின்றன என்பதை அறிய இதுவே ஒரே வழி. எதையும் மாற்றியமைக்கும் முன், ஒரு தெளிவான baseline-ஐ எடுத்துக்கொள்ளவும்: local LLM-ல் tokens per second-ஐ அளவிடுதல் என்ற பகுதியில் உள்ள முறை, reboot செய்த பிறகும் அழியாத baseline-ஐ உங்களுக்கு வழங்கும்.
நீண்ட system prompt ஏன் முதல் token-ஐ தாமதப்படுத்துகிறது, ஆனால் streaming வேகத்தைப் பாதிப்பதில்லை?
System prompt என்பது prefill வேலை மட்டுமே, வேறொன்றும் இல்லை. முதல் token தோன்றுவதற்கு முன்பு, prompt-ன் மற்ற பகுதியுடன் சேர்த்து இது ஒரே முறையில் செயலாக்கப்படுகிறது. அந்தச் செயலாக்கத்திற்குப் பிறகு, அது KV cache பதிவுகளாக மட்டுமே இருக்கும்; decode செய்யும் போது மற்றவற்றுடன் சேர்த்து இதையும் கணினி வாசிக்கும். எனவே, 3,000 token கொண்ட system prompt ஒவ்வொரு கோரிக்கையிலும் (request) TTFT-ஐ அதிகரிக்கிறது, ஆனால் ஒரு வினாடிக்கான token எண்ணிக்கையை (tokens per second) கிட்டத்தட்ட மாற்றாமல் வைத்திருக்கிறது.
கிட்டத்தட்ட என்றுதான் சொல்ல வேண்டும், சரியாக அல்ல. அந்த கூடுதல் KV பதிவுகள் ஒவ்வொரு decode நிலையிலும் மீண்டும் வாசிக்கப்படுகின்றன, எனவே மிக நீண்ட prompt decode வேகத்தை சற்று குறைக்கும். இதைப் பற்றி அடுத்த பகுதியில் காணலாம்.
ஒரே prefix-ஐ மீண்டும் மீண்டும் கணக்கிடுவதைத் தவிர்ப்பதே இதற்கான தீர்வு. Prefix caching வசதி கொண்ட server, பகிரப்பட்ட prefix-ன் KV cache-ஐத் தக்கவைத்து மீண்டும் பயன்படுத்துகிறது. இதனால், அதே system prompt-ஐக் கொண்ட இரண்டாவது கோரிக்கை prefill-ன் அந்தப் பகுதியை முழுமையாகத் தவிர்க்கிறது. vLLM இதை automatic prefix caching என்று அழைக்கிறது; உங்கள் பதிப்பில் vllm serve --help-ஐச் சரிபார்க்கவும், ஏனெனில் வெளியீடுகளுக்கு ஏற்ப இதன் default அமைப்பு மாறியிருக்கலாம். GPU-க்குள் இருக்கும் அந்த KV cache, API வழங்குநர் உங்களிடம் கட்டணம் வசூலிக்கும் prompt cache-லிருந்து மாறுபட்டது. எனவே, இரண்டையும் மாற்றியமைக்கும் முன் KV cache மற்றும் prompt cache ஆகியவற்றுக்கு இடையேயான வேறுபாடு குறித்த கட்டுரையை வாசிப்பது அவசியம்.
Context நிரம்பும்போது decode ஏன் வேகம் குறைகிறது?
இதற்கு இரண்டு காரணங்கள் உள்ளன, இரண்டுமே KV cache தொடர்பானவை.
முதலாவது bandwidth. ஒவ்வொரு decode நிலையிலும், attention முந்தைய அனைத்து token-களின் keys மற்றும் values-ஐயும் வாசிக்கிறது. Weights என்பது ஒரு token-க்கு நிலையான செலவு. ஆனால் KV cache வளர்ந்து கொண்டே இருக்கும். model-ன் config.json-ஐக் கொண்டு அதன் அளவைக் கணக்கிடலாம்: ஒரு token-க்கான bytes என்பது 2 பெருக்கல் num_hidden_layers, பெருக்கல் num_key_value_heads, பெருக்கல் head dimension (hidden_size வகுத்தல் num_attention_heads), பெருக்கல் ஒரு element-க்கான bytes. இதில் முன்னால் உள்ள 2 என்பது ஒரு key மற்றும் ஒரு value-ஐக் குறிக்கிறது.
பொதுவான 8 billion parameter அமைப்பு, 32 layers, GQA (grouped query attention)-ன் கீழ் 8 key மற்றும் value heads, head dimension 128, 16 bit precision ஆகியவற்றைக் கொண்டால், அது 2 x 32 x 8 x 128 x 2 = 131,072 bytes, அதாவது ஒரு token-க்கு சுமார் 128 KiB ஆகும். எனவே, 8,000 token கொண்ட உரையாடல் ஒரு request-க்கு சுமார் 1 GB KV cache-ஐ எடுத்துக்கொள்ளும்.
இரண்டாவது capacity. அந்த 1 GB நினைவகம் (memory) weights-ஐயோ அல்லது மற்றொரு பயனரின் context-ஐயோ வைத்திருக்க முடியாது. server தனது KV pool-ஐ தொடக்கத்திலேயே, vLLM-ல் --gpu-memory-utilization வழியாக அமைத்துவிடும். pool நிரம்பிவிட்டால் புதிய request-கள் காத்திருக்க வேண்டியிருக்கும். vllm:num_requests_waiting உயர்ந்து கொண்டே இருந்து, vllm:kv_cache_usage_perc 1-க்கு அருகில் இருந்தால், அது இந்த நிலையைக் குறிக்கும் அறிகுறியாகும். சில stacks இயங்கிக்கொண்டிருக்கும் request-ஐ நிறுத்திவிட்டு (preempt), அதன் cache-ஐப் பிறகு மீண்டும் கணக்கிடும்; இது queue-வில் வைப்பதற்குப் பதிலாகச் செய்யப்படுகிறது. பயனர் இதை stream-ன் இடையில் ஏற்படும் ஒரு தடையாக (stall) உணர்வார்.
நீண்ட context உங்களுக்கு இரண்டு விதமான பாதிப்புகளை ஏற்படுத்துகிறது: தொடக்கத்தில் அதிக prefill வேலை, மற்றும் பதிலின் மீதமுள்ள பகுதிக்கு ஒவ்வொரு token-க்கும் அதிக நினைவக வாசிப்பு.
Batching ஏன் throughput-ஐ அதிகரித்து tail latency-ஐ பாதிக்கிறது?
Decode செயல்முறை bandwidth-ஐச் சார்ந்து இருப்பதால், compute பக்கத்தில் கூடுதல் கோரிக்கைகளை (requests) கையாள்வது எளிது. Weights-ஐ ஒருமுறை வாசிப்பதன் மூலம் batch-ல் உள்ள ஒவ்வொரு sequence-க்கும் ஒரு token-ஐ உருவாக்க முடியும். எனவே, KV pool தீரும் வரை அல்லது batch அளவு மீண்டும் compute-ஐச் சார்ந்த நிலையை அடையும் வரை, மொத்த throughput batch அளவைப் பொறுத்து நேர்க்கோட்டில் (linearly) உயரும். Continuous batching ஒவ்வொரு நிலையிலும் batch-ஐ மீண்டும் உருவாக்குகிறது. இதனால், ஒரு கோரிக்கை முடிந்ததும் அது வெளியேறுகிறது, வரிசையில் உள்ள புதிய கோரிக்கை அதன் அண்டை கோரிக்கைகளுக்காகக் காத்திருக்காமல் இணைகிறது.
இதன் பாதிப்பு percentiles-ல் தெரியும். ஒவ்வொரு பயனரின் அடுத்த token-ம், பகிரப்பட்ட ஒரு step-ன் மிக மெதுவான பகுதிக்காகக் காத்திருக்க வேண்டியுள்ளது. இதனால் p50 (median) ஏற்றுக்கொள்ளக்கூடிய அளவில் இருக்கும், ஆனால் p99 (100-ல் மிக மெதுவான 1 கோரிக்கை) அதிகரிக்கிறது. p99 என்பது மக்கள் கவனிக்கும் ஒரு விஷயம், ஏனெனில் இது ஒரு வாக்கியத்தின் இடையில் ஏற்படும் தாமதமாகும்.
Prefill இந்த பாதிப்பை இன்னும் தீவிரமாக்குகிறது. ஒரு பெரிய prompt பாதியிலேயே வரும்போது, அது நீண்ட நேரத்திற்கு device-ஐ ஆக்கிரமிக்கிறது, இதனால் தற்போது streaming-ல் உள்ள அனைவரும் ஒரு இடைவெளியை உணர்வார்கள். Chunked prefill, ஒரு நீண்ட prompt-ஐ சிறு துண்டுகளாகப் பிரித்து, ஒவ்வொரு துண்டையும் decode batches-உடன் கலப்பதன் மூலம் இந்த சிக்கலை நீக்குகிறது. ஆகஸ்ட் 2026 நிலவரப்படி, vLLM V1 engine இதை இயல்பாகவே செய்கிறது மற்றும் --max-num-batched-tokens மூலம் இந்த சமநிலையை மாற்ற அனுமதிக்கிறது. vLLM tuning ஆவணங்கள் இந்த சமநிலையைத் தெளிவாகக் குறிப்பிடுகின்றன: 2048 போன்ற சிறிய மதிப்புகள் சிறந்த inter token latency (ITL)-ஐத் தருகின்றன, ஏனெனில் குறைவான prefills மட்டுமே decode-ஐத் தடுக்கின்றன. பெரிய மதிப்புகள் சிறந்த TTFT-ஐத் தருகின்றன, ஏனெனில் அதிக prefill tokens ஒரே batch-ல் அடங்கும். அந்த ஒரு flag-தான் prefill மற்றும் decode-க்கு இடையிலான சமநிலை, அதை நீங்கள் மாற்றக்கூடிய ஒரு எண்ணாக வழங்கப்பட்டுள்ளது. p99 எப்போது ஏற்றுக்கொள்ள முடியாததாக மாறுகிறது என்பது capacity தொடர்பான கேள்வி, மேலும் ஒரு self-hosted LLM எத்தனை பயனர்களை ஒரே நேரத்தில் கையாள முடியும் என்பது இதே அளவீடுகளைக் கொண்டு விளக்கப்பட்டுள்ளது.
பெரிய GPU சில நேரங்களில் எந்த மாற்றத்தையும் ஏற்படுத்தாதது ஏன்?
பெரிய GPU என்பது பொதுவாக அதிக compute திறனைக் குறிக்கும், ஆனால் decode செயல்பாட்டிற்கு compute தேவைப்படுவதில்லை.
மேலே உள்ள அட்டவணையில் இரண்டு வரிசைகளை ஒப்பிட்டுப் பாருங்கள். A100 80GB கார்டு 2039 GB/s என்ற வெளியிடப்பட்ட bandwidth-ஐக் கொண்டுள்ளது, அதே சமயம் L40S கார்டு 864 GB/s-ஐக் கொண்டுள்ளது. இதற்கேற்ப decode உச்ச வரம்பும் சரியாக அமைகிறது: 127 tokens per second மற்றும் 54. RTX 4090 பெரும்பாலான அளவீடுகளில் மிக வேகமான கார்டாகும், அதன் 1008 GB/s bandwidth அதன் உச்ச வரம்பை 63 அளவில் வைக்கிறது. இரண்டு கார்டுகளுக்கு இடையே மற்ற அம்சங்கள் எதுவாக இருந்தாலும், single stream decode என்பது specification sheet-ல் உள்ள bandwidth அளவைப் பொறுத்தே அமையும்.
எனவே, decode வேகத்தை அதிகரிக்க இரண்டு வழிகள் உள்ளன: ஒரு token-க்கு குறைவான bytes-களை வாசிப்பது (weights-ஐ quantize செய்வது அல்லது சிறிய model-ஐ இயக்குவது), அல்லது அதிக bandwidth கொண்ட கார்டை வாங்குவது. Prefill இதற்கு நேர்மாறானது. அதற்கு compute தேவை, எனவே வேகமான கார்டு நீண்ட prompts-க்கான TTFT-ஐக் கணிசமாகக் குறைக்கும். முதல் token வருவதற்கு நான்கு வினாடிகள் ஆகிறது என்பது புகாராக இருந்தால், சிறந்த hardware அதைச் சரிசெய்யக்கூடும். ஆனால், உரை மெதுவாகத் தட்டச்சு செய்யப்படுகிறது (text types slowly) என்பது புகாராக இருந்தால், hardware மாற்றம் பெரும்பாலும் உதவாது.
Prefill மற்றும் decode பணிகளைத் தனித்தனி workers-ல் இயக்க வேண்டுமா?
பெரிய அளவிலான serving stacks இந்த முறையைத்தான் பின்பற்றுகின்றன; இந்த நுட்பம் prefill மற்றும் decode disaggregation என்று அழைக்கப்படுகிறது. ஒரு worker pool prefill பணிகளை மட்டுமே செய்யும், மற்றொரு pool decode பணிகளை மட்டுமே செய்யும். முதல் pool-ஆல் உருவாக்கப்பட்ட KV cache, அதிவேக interconnect மூலம் இரண்டாவது pool-க்கு மாற்றப்படும். இந்த இரண்டு நிலைகளுக்கும் வெவ்வேறு வன்பொருள் (hardware) மற்றும் scheduling தேவைப்படுவதால் இந்த முறை பயனுள்ளதாக இருக்கிறது. Prefill-க்கு அதிக compute திறனும், பெரிய token batches-ம் தேவை. Decode-க்கு அதிக bandwidth-ம், ஒரே நேரத்தில் பல sequences-ஐ கையாளும் திறனும் தேவை. இவற்றைத் தனித்தனியாகப் பிரிப்பதன் மூலம், ஒவ்வொரு pool-ஐயும் தேவைக்கேற்ப scale செய்ய முடியும். மேலும், ஒரு பெரிய prompt காரணமாக மற்ற active streams பாதிக்கப்படுவதையும் இது தடுக்கிறது.
ஒரே ஒரு GPU கொண்ட ஒரு VPS (virtual private server)-ல் இதைச் செய்வது பெரும்பாலும் பலன் தராது. இது ஒரே சாதனத்தை இரண்டாகப் பிரிப்பதற்குச் சமம்; மேலும், ஒரு pointer-ஐப் பயன்படுத்துவதற்குப் பதிலாக, gigabytes அளவிலான cache-ஐ network வழியாக மாற்ற வேண்டிய சூழல் ஏற்படும். ஒவ்வொரு phase-க்கும் தனித்தனி இயந்திரங்களை ஒதுக்கும் அளவுக்கு accelerators இருக்கும்போதும், இரண்டு pool-களையும் தொடர்ந்து இயக்கும் அளவுக்கு traffic இருக்கும்போதும் மட்டுமே இந்த நுட்பம் பயனுள்ளதாக இருக்கும். அதற்கு குறைவான சூழலில், chunked prefill வசதியைப் பயன்படுத்துவதன் மூலம் ஒரே flag-ஐக் கொண்டு அதே போன்ற isolation-ஐப் பெற முடியும்.
எண் சரியாக இல்லாதபோது எதை மாற்ற வேண்டும்
TTFT அதிகமாக இருந்தால்:
- Prompt-ஐச் சுருக்கவும். Prefill cost என்பது prompt tokens-ஐக் கணக்கிடுகிறது, மேலும் system prompt ஒவ்வொரு கோரிக்கையிலும் கட்டணம் வசூலிக்கப்படும்.
- Prefix caching-ஐ இயக்கவும், இதன் மூலம் மீண்டும் மீண்டும் வரும் prefix ஒவ்வொரு முறையும் கணக்கிடப்படாமல் ஒருமுறை மட்டுமே கணக்கிடப்படும்.
--max-num-batched-tokens-ஐ உயர்த்தவும், இதனால் ஒவ்வொரு நிலையிலும் அதிக prefill வேலைகள் நடைபெறும்.- மாடலைக் குறை கூறுவதற்கு முன் queue-வைச் சரிபார்க்கவும்.
vllm:num_requests_waitingபூஜ்ஜியத்திற்கு மேல் இருந்தால், கோரிக்கை இன்னும் தொடங்கவில்லை என்று அர்த்தம், இது ஒரு capacity சிக்கலாகும்.
Tokens per second குறைவாக இருந்தால்:
- Weights-ஐக் குறைக்க (Quantize) செய்யவும். ஒரு weight-க்குக் குறைவான bytes இருந்தால், ஒரு token-க்கு வாசிக்கப்படும் bytes-ம் குறையும்.
- உங்கள் card-ன் memory bandwidth-ஐ மேலே உள்ள அட்டவணையுடன் ஒப்பிட்டு, அதன் உச்ச வரம்பிற்கு எவ்வளவு அருகில் உள்ளீர்கள் என்று சரிபார்க்கவும்.
--max-num-batched-tokens-ஐக் குறைக்கவும், இதனால் prefills decode-ஐ அடிக்கடி குறுக்கிடாது.- Context length-ஐச் சரிபார்க்கவும். ஆயிரக்கணக்கான tokens கொண்ட உரையாடல், ஒவ்வொரு நிலையிலும் மிகப் பெரிய KV cache-ஐ வாசிக்கிறது.
இங்கு runtime-ம் முக்கியமானது, ஏனெனில் Ollama மற்றும் vLLM ஆகியவை prefill மற்றும் decode-ஐ வெவ்வேறு விதமாகத் திட்டமிடுகின்றன, ஒரு அமைப்பில் உதவும் மாற்றம் மற்றொன்றில் எந்த மாற்றத்தையும் ஏற்படுத்தாமல் போகலாம். முதலில் இரண்டு நிலைகளிலும் அளவீடு செய்யுங்கள், அதன் பிறகு ஒரு மாற்றத்தை மட்டும் செய்யுங்கள்.
FAQ
முதல் token வருவதற்கு ஏன் சில நொடிகள் ஆகிறது, ஆனால் மற்றவை வேகமாக வருகின்றன?
இந்த காத்திருப்பு நேரம் prefill எனப்படும். streaming என்பது decode ஆகும். output உருவாவதற்கு முன்பே, முழு prompt-ஐயும் ஒரு compute bound pass-ல் prefill செயலாக்குகிறது; எனவே prompt-ன் நீளத்திற்கு ஏற்ப இதன் செலவு அதிகரிக்கிறது. அதன் பிறகு, memory bandwidth-ஆல் தீர்மானிக்கப்படும் வேகத்தில், ஒவ்வொரு படியிலும் ஒரு token-ஐ decode வெளியிடுகிறது. இது prompt-ன் நீளத்தைச் சார்ந்தது அல்ல. ஒவ்வொரு கோரிக்கையிலும் (request) நீண்ட system prompt-ஐப் பயன்படுத்துவது இதற்கு முக்கிய காரணமாகும். Prefix caching இந்த மீண்டும் மீண்டும் வரும் செலவைக் குறைக்கிறது.
நீண்ட prompt-ஆல் ஒரு நொடிக்கான token-களின் வேகம் குறையுமா?
சிறிது குறையும், ஆனால் இது TTFT-க்கு இருக்கும் காரணத்திலிருந்து மாறுபட்டது. ஒவ்வொரு decode படியும் இதற்கு முந்தைய அனைத்து token-களின் keys மற்றும் values-ஐ வாசிக்கிறது. எனவே, பெரிய KV cache என்பது ஒவ்வொரு token-க்கும் அதிக bytes வாசிக்கப்படுவதைக் குறிக்கிறது. பொதுவாக 8 billion parameter அமைப்பில், ஒரு token-க்கு சுமார் 128 KiB cache தேவைப்படும். எனவே, 8,000 token கொண்ட context-ல் ஒவ்வொரு படியிலும் சுமார் 1 GB தரவு கையாளப்படுகிறது. நீண்ட prompt-ன் தாக்கம் streaming வேகத்தை விட TTFT-ல் தான் அதிகமாக இருக்கும்.
எந்த GPU விவரக்குறிப்பு decode வேகத்தை முன்கூட்டியே கணிக்கும்?
Memory bandwidth. வெளியிடப்பட்ட bandwidth-ஐ memory-ல் உள்ள weights-ன் அளவால் வகுத்தால், ஒரு stream-க்கான அதிகபட்ச வேகம் (arithmetic ceiling) கிடைக்கும். அதிக compute திறன் கொண்ட ஆனால் அதே bandwidth கொண்ட ஒரு card, வேகமான streaming-ஐத் தராது. இதனால்தான் 8 bits-க்கு quantize செய்வது decode வேகத்தை இருமடங்காக்குகிறது: இது compute-ஐ பாதிக்காமல், ஒரு token-க்கு வாசிக்கப்படும் bytes-ன் அளவை பாதியாகக் குறைக்கிறது.
பயனர்களைச் சேர்க்கும்போது throughput ஏன் அதிகரிக்கிறது, ஆனால் ஒவ்வொரு பயனருக்கும் வேகம் ஏன் குறைகிறது?
Weights-ஐ ஒருமுறை வாசிப்பது, batch-ல் உள்ள அனைத்து sequence-களுக்கும் ஒரு token-ஐ வழங்குகிறது. எனவே, batch size அதிகரிக்க அதிகரிக்க, ஒரு நொடிக்கான மொத்த token-களின் எண்ணிக்கை உயர்கிறது. இப்போது ஒவ்வொரு தனிப்பட்ட token-ம் ஒரு பகிரப்பட்ட பதிக்காகக் காத்திருப்பதால், பயனருக்கான latency ஒரே நேரத்தில் அதிகரிக்கிறது. மொத்த throughput எண்ணிக்கையை மட்டும் பார்க்காமல், p99 inter token latency-ஐ கவனிக்கவும். கோரிக்கைகள் இயங்காமல் queue-ல் இருக்கிறதா என்பதைச் சரிபார்க்க vllm:num_requests_waiting-ஐப் பார்க்கவும்.