Prefill vs decode: మొదటి token ఎందుకు ఆలస్యం?
Prefill compute-bound కావడంతో మొదటి tokenకు సమయం పడుతుంది. Decode memory bandwidth-bound కావడంతో tokens per second నిర్ణయించబడుతుంది. మీ boxలో రెండింటినీ వేరుగా కొలవండి.
Prefill మరియు decode తేడా, ఒక పేరాలో
Self-hosted LLM (large language model)కు సంబంధించిన latency ప్రశ్నల్లో ఎక్కువ భాగాన్ని వివరించే ప్రధాన తేడా prefill మరియు decode మధ్య ఉంటుంది. Prefill మొత్తం promptను ఒకే passలో చదువుతుంది; దీనికి compute పరిమితిగా ఉంటుంది. Decode సమాధానాన్ని ఒక్కో token చొప్పున రాస్తుంది; దీనికి memory bandwidth పరిమితిగా ఉంటుంది. మొదటి token అందే సమయం prefillకు సంబంధించిన కొలత. సెకనుకు ఉత్పత్తి అయ్యే tokens decodeకు సంబంధించిన కొలత.
రెండు దశలూ అదే GPU (graphics processing unit)పై, అదే weightsతో, అదే processలో నడుస్తాయి. అందువల్ల వీటిని ఒకే workloadగా పరిగణించడం సహజం. అయితే ఇవి ఒకే deviceను పంచుకునే రెండు వేర్వేరు programsలా ప్రవర్తిస్తాయి. వీటిని వేరు చేసి పరిశీలిస్తే, గందరగోళంగా కనిపించే అనేక ఫలితాలు ఇక గందరగోళంగా ఉండవు.
prefill compute-bound ఎందుకు ఉంటుంది?
Prefill ప్రతి layer ద్వారా మొత్తం prompt ను ఒక్కసారి పంపుతుంది. 2,000 tokenల prompt ప్రతి matrix multiply కు 2,000 వరుసల పని ఇస్తుంది. అందువల్ల GPU లోడ్ చేసే ప్రతి weight byte కు పెద్ద మొత్తంలో arithmetic పని జరుగుతుంది. తరలించిన ప్రతి byte కు జరిగే arithmetic నిష్పత్తిని arithmetic intensity అంటారు. Prefill లో ఈ నిష్పత్తి ఎక్కువగా ఉంటుంది. పరికరం తన compute పరిమితికి దగ్గరగా నడుస్తుంది. Memory bus లో మాత్రం కొంత సామర్థ్యం మిగిలి ఉంటుంది.
Prefill రెండు ఫలితాలను ఉత్పత్తి చేస్తుంది: ప్రతి prompt token కు సంబంధించిన KV cache (key మరియు value tensors), అలాగే మొదటి output token. ఆ pass పూర్తయ్యే వరకు reader కు ఏదీ చేరదు. అందువల్ల prefill సమయం మరియు time to first token (TTFT) దాదాపు ఒకే కొలతగా ఉంటాయి.
Prompt పొడవు పెరిగే కొద్దీ prefill ఖర్చు కూడా పెరుగుతుంది. Linear భాగం ప్రతి layer లో జరిగే matrix పని. Quadratic భాగం attention. ఇందులో ప్రతి token తనకు ముందు ఉన్న ప్రతి token పై దృష్టి పెడుతుంది. ఎక్కువ context వద్ద ఈ భాగం ప్రభావం చూపడం ప్రారంభిస్తుంది. కాబట్టి prompt ను రెట్టింపు చేస్తే TTFT కనీసం రెట్టింపు అవుతుంది.
దీన్ని ఒక నిమిషంలో గమనించవచ్చు. మీ server కు 200 tokenల prompt పంపండి. తరువాత ప్రతిసారి ఒకే సంఖ్యలో output tokenలు కోరుతూ 2,000 tokenల prompt పంపండి. TTFT గణనీయంగా పెరుగుతుంది. మొదటి token తర్వాత streaming speed మాత్రం దాదాపు మారదు.
డీకోడ్ మెమరీ బ్యాండ్విడ్త్పై ఎందుకు ఆధారపడుతుంది?
డీకోడ్ ప్రతి దశలో ఒక token ను ఉత్పత్తి చేస్తుంది. ఆ ఒక్క token ను ఉత్పత్తి చేయడానికి GPU మోడల్లోని ప్రతి weight ను memory నుంచి చదివి, ప్రతి weight ను కొన్ని operations కోసం ఉపయోగించి, తరువాత discard చేయాలి. Arithmetic intensity దాదాపు 1గా ఉంటుంది. అందువల్ల compute units ఎక్కువ సమయం వేచి ఉంటాయి.
ప్రతి token కోసం మొత్తం మోడల్ను memory నుంచి చదవాల్సి ఉంటుంది. అందుకే decode నెమ్మదిగా ఉంటుంది. Memory bus వేగాన్ని నిర్ణయిస్తుంది, compute units idle గా ఉంటాయి.
దీంతో single-stream decode వేగానికి గల ceiling ను కాగితంపై చేసే లెక్కలతో అంచనా వేయవచ్చు. Memory bandwidth ను weights ఆక్రమించే 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 column లో ప్రతి vendor ప్రచురించిన specification figure ఉంటుంది. ceiling column అనేది ఆ figure ను 16 GB తో భాగించిన ఫలితం. 16 bit precision లో నిల్వ చేసిన 8 billion parameter model పరిమాణం 16 GB. ఇది arithmetic లెక్క మాత్రమే; benchmark ఫలితం కాదు. మీరు కొలిచే rate దీనికంటే తక్కువగా ఉంటుంది. అది ఎంత తక్కువగా ఉందో తెలుసుకోవడం ఉపయోగకరం, ఎందుకంటే మీ serving stack ను సరిచేయాలా లేదా hardware ను సరిచేయాలా అన్నది తెలుస్తుంది.
6 rows ను క్రమంగా చదివితే pattern స్పష్టంగా కనిపిస్తుంది. dual-channel DDR5 పై CPU సుమారు 90 GB/s వేగంతో data ను తరలిస్తుంది. ఆ మోడల్కు decode ceiling సుమారు 6 tokens per second గా ఉంటుంది. L40S విలువ సుమారు 54. ప్రచురించిన bandwidth 3350 GB/s కలిగిన H100 SXM విలువ సుమారు 209.
అందుకే decode వేగాన్ని పెంచడానికి quantization అత్యంత ప్రభావవంతమైన ఒకే మార్గంగా ఉంటుంది. అదే మోడల్ను 16 bits బదులు 8 bits వద్ద నిల్వ చేస్తే ప్రతి token కోసం చదవాల్సిన bytes సగానికి తగ్గుతాయి. అందువల్ల ceiling సుమారుగా రెట్టింపు అవుతుంది. మీరు compute ఏదీ పెంచలేదు. తక్కువ 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 count మరియు దానిపై ఖర్చైన సమయం. eval_count మరియు eval_duration decodeకు సంబంధించినవి. Durations nanosecondsలో ఉంటాయి. అందువల్ల decode speed eval_count / eval_duration * 1e9, prefill speed prompt_eval_count / prompt_eval_duration * 1e9. అదే requestలో prefill rate, decode rate కంటే చాలా ఎక్కువగా ఉంటుంది. ఇక్కడ వివరించిన మిగతా అంశాలన్నీ ఆ తేడాను వివరిస్తాయి.
vLLM వంటి OpenAI compatible 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 ను తీసివేయండి. దీన్ని రెండుసార్లు అమలు చేసి, రెండవ ఫలితాన్ని ఉంచండి. మొదటి callలో cold model load ఉండవచ్చు.
vLLM ఈ విభజనను /metrics వద్ద 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 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ను కొలిచి, first tokenకు పట్టే సమయం మరియు ప్రతి output token latencyని percentilesతో చూపిస్తుంది. ఈ రెండు దశలు పరస్పరం ఎలా ప్రభావితం చేసుకుంటున్నాయో చూడటానికి ఇదే ఏకైక మార్గం. ఏదైనా tune చేయడానికి ముందు శుభ్రమైన baseline తీసుకోండి: local LLMలో tokens per secondను కొలవడంలోని పద్ధతి reboot తర్వాత కూడా ఉపయోగించగల baselineను ఇస్తుంది.
దీర్ఘమైన system prompt మొదటి token ను ఆలస్యం చేస్తుంది, కానీ streaming వేగాన్ని ఎందుకు ప్రభావితం చేయదు?
ఎందుకంటే system prompt prefill పనిలో భాగం మాత్రమే. మిగిలిన prompt తో పాటు, మొదటి token కనిపించకముందే ఒకే pass లో దీనిని ఒకసారి process చేస్తారు. ఆ pass పూర్తయిన తర్వాత ఇది KV cache entries గా మాత్రమే ఉంటుంది. Decode సమయంలో మిగిలిన సమాచారంతో పాటు వాటిని కూడా చదువుతుంది. అందువల్ల 3,000 token system prompt ప్రతి request లో TTFT ను పెంచుతుంది, కానీ tokens per second దాదాపుగా మారదు.
అయితే ఇది పూర్తిగా మార్పులేనిది కాదు. ప్రతి decode step లో ఆ అదనపు KV entries ను మళ్లీ చదవాలి. అందువల్ల చాలా దీర్ఘమైన prompt decode ను కొద్దిగా నెమ్మదింపజేస్తుంది. తదుపరి విభాగంలో దీనిని వివరిస్తాం.
ఒకే prefix ను మళ్లీ మళ్లీ recompute చేయకుండా ఉండటమే పరిష్కారం. prefix caching ఉన్న server shared prefix యొక్క KV cache ను నిల్వ చేసి మళ్లీ ఉపయోగిస్తుంది. అందువల్ల అదే system prompt ఉన్న రెండవ request లో ఆ భాగానికి సంబంధించిన prefill పూర్తిగా skip అవుతుంది. vLLM దీనిని automatic prefix caching అని పిలుస్తుంది; మీ version లో vllm serve --help ను పరిశీలించండి, ఎందుకంటే releases మధ్య default మారింది. GPU లో ఉండే ఈ KV cache, API provider మీకు billing చేసే prompt cache కు భిన్నమైనది. మీరు వీటిలో దేనినైనా tune చేయడానికి ముందు KV cache మరియు prompt cache మధ్య తేడా చదవడం ఉపయోగకరం.
సందర్భం నిండుతున్న కొద్దీ decode ఎందుకు నెమ్మదిస్తుంది?
దీనికి రెండు కారణాలు ఉన్నాయి. రెండూ KV cache కు సంబంధించినవే.
మొదటిది bandwidth. ప్రతి decode దశలో attention, ఇంతకుముందు వచ్చిన ప్రతి token యొక్క keys మరియు values ను చదువుతుంది. Weights ప్రతి token కు స్థిరమైన ఖర్చు. KV cache పరిమాణం పెరుగుతూ ఉంటుంది. దాని పరిమాణాన్ని మోడల్ యొక్క 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 layout లో, 32 layers, GQA (grouped query attention) కింద 8 key మరియు value heads, 128 head dimension, 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 లేదా మరొక user యొక్క context నిల్వ చేయలేరు. Server startup సమయంలోనే తన KV pool పరిమాణాన్ని నిర్ణయిస్తుంది; vLLM లో ఇది --gpu-memory-utilization ద్వారా నియంత్రించబడుతుంది. Pool నిండినప్పుడు కొత్త requests వేచి ఉంటాయి. vllm:num_requests_waiting పెరుగుతూ, vllm:kv_cache_usage_perc దాదాపు 1 వద్ద ఉండటం ఈ స్థితికి ఖచ్చితమైన సూచన. కొన్ని stacks queue చేయడానికి బదులుగా నడుస్తున్న request ను తాత్కాలికంగా ఆపి, దాని cache ను తరువాత మళ్లీ లెక్కిస్తాయి. దీని వల్ల stream మధ్యలో stall వచ్చినట్లు user కు అనిపిస్తుంది.
Long context కు రెండుసార్లు ఖర్చు ఉంటుంది: ప్రారంభంలో ఎక్కువ prefill పని, అలాగే సమాధానం మిగిలిన సమయంలో ప్రతి token కు ఎక్కువ memory read.
బ్యాచింగ్ throughput ను ఎందుకు పెంచుతుంది, tail latency ను ఎందుకు దెబ్బతీస్తుంది?
Decode ప్రక్రియ bandwidth-bound కావడంతో, అదనపు requests compute పరంగా దాదాపు ఉచితం. Weights ను ఒక్కసారి read చేయడం ద్వారా batch లోని ప్రతి sequence కు ఒక token ఉత్పత్తి చేయవచ్చు. అందువల్ల batch size పెరిగే కొద్దీ మొత్తం throughput దాదాపు linear గా పెరుగుతుంది. అయితే KV pool ఖాళీ అయ్యే వరకు లేదా batch మళ్లీ compute-bound అయ్యేంత పెద్దదయ్యే వరకు మాత్రమే ఇది జరుగుతుంది. Continuous batching ప్రతి step లో batch ను మళ్లీ నిర్మిస్తుంది. అందువల్ల ఒక request పూర్తయితే అది బయటకు వెళ్తుంది, queue లోని తదుపరి request దాని సహచరుల కోసం వేచి ఉండకుండా batch లో చేరుతుంది.
దీని ప్రభావం percentiles లో కనిపిస్తుంది. ప్రతి user యొక్క తదుపరి token ఇప్పుడు shared step లోని నెమ్మదైన భాగం పూర్తయ్యే వరకు వేచి ఉండాలి. అందువల్ల median అయిన p50 సాధారణంగా ఆమోదయోగ్యంగానే ఉంటుంది. కానీ p99, అంటే 100 requests లో నెమ్మదిగా పూర్తయ్యే 1 request కు సంబంధించిన latency, పెరుగుతుంది. ప్రజలు ఎక్కువగా గమనించేది p99 ను, ఎందుకంటే అది వాక్యం మధ్యలో అనిపించే విరామం.
Prefill దీనిని మరింత తీవ్రం చేస్తుంది. Stream మధ్యలో పెద్ద prompt వస్తే, device ఒక long step కోసం దానిని process చేయడంలో నిమగ్నమవుతుంది. ఆ సమయంలో ప్రస్తుతం streaming చేస్తున్న ప్రతి user కు gap కనిపిస్తుంది. Chunked prefill పెద్ద prompt ను చిన్న భాగాలుగా విభజిస్తుంది. ప్రతి భాగాన్ని decode batches లో కలపడం ద్వారా ఈ ప్రభావాన్ని ఎక్కువగా తొలగిస్తుంది. August 2026 నాటికి vLLM V1 engine దీన్ని default గా చేస్తుంది. దీని సమతుల్యతను --max-num-batched-tokens ద్వారా నియంత్రించవచ్చు. vLLM tuning documentation ఈ tradeoff ను స్పష్టంగా వివరిస్తుంది: సుమారు 2048 వంటి చిన్న values మెరుగైన inter token latency (ITL) ఇస్తాయి, ఎందుకంటే తక్కువ prefills decodes కు అంతరాయం కలిగిస్తాయి. పెద్ద values మెరుగైన TTFT ఇస్తాయి, ఎందుకంటే ఒక batch లో ఎక్కువ prefill tokens సరిపోతాయి. ఈ ఒక్క flag prefill మరియు decode మధ్య సమతుల్యతను ఒక సంఖ్యగా నియంత్రించడానికి అందిస్తుంది. p99 ఎక్కడి నుంచి ఆమోదయోగ్యం కాకుండా మారుతుందో అనేది capacity సమస్య. అదే metrics ఆధారంగా ఒక self-hosted LLM ఎన్ని concurrent users కు సేవలందించగలదో అర్థం చేసుకోవచ్చు.
పెద్ద GPU కొన్నిసార్లు ఎలాంటి మార్పు ఎందుకు చేయదు?
ఎందుకంటే పెద్దది అంటే సాధారణంగా ఎక్కువ compute అని అర్థం. కానీ decode కు ఎక్కువ compute అవసరం ఉండదు.
పై chart లోని రెండు వరుసలను పోల్చండి. A100 80GB కి ప్రచురించిన bandwidth 2039 GB/s ఉంది. L40S కి అది 864 GB/s. Decode ceiling కూడా అదే విధంగా ఉంటుంది: సెకనుకు 127 tokens, దీనికి విరుద్ధంగా 54. చాలా ప్రమాణాల ప్రకారం RTX 4090 అత్యంత వేగవంతమైన card. దాని 1008 GB/s వల్ల దాని ceiling 63 వద్ద ఉంటుంది. రెండు cards మధ్య ఇతర తేడాలు ఏవైనా ఉన్నా, single stream decode specification sheet లోని bandwidth line ను అనుసరిస్తుంది.
అందువల్ల decode ను వేగవంతం చేయడానికి రెండు మార్గాలు ఉన్నాయి: ప్రతి token కు చదవాల్సిన bytes ను తగ్గించడం (weights ను quantize చేయడం లేదా చిన్న model ను అమలు చేయడం), లేదా ఎక్కువ bandwidth కొనుగోలు చేయడం. Prefill దీనికి విరుద్ధమైన సందర్భం. దానికి compute అవసరం. అందువల్ల వేగవంతమైన card ఎక్కువ పొడవైన prompts పై TTFT ను వాస్తవంగా తగ్గిస్తుంది. మొదటి token రావడానికి నాలుగు seconds పడుతున్నాయనేది సమస్య అయితే, మెరుగైన hardware దాన్ని పరిష్కరించవచ్చు. Text నెమ్మదిగా type అవుతున్నదనేది సమస్య అయితే, అది సాధారణంగా పరిష్కరించదు.
prefill మరియు decode ను వేర్వేరు workers పై నడపాలా?
పెద్ద serving stacks ఇదే విధానాన్ని ఉపయోగిస్తాయి. దీనిని prefill and decode disaggregation అంటారు. ఒక worker pool prefill ను మాత్రమే నడుపుతుంది. రెండో pool decode ను మాత్రమే నడుపుతుంది. మొదటి pool నిర్మించిన KV cache ను fast interconnect ద్వారా రెండో pool కు బదిలీ చేస్తారు. ఈ విధానం పనిచేయడానికి కారణం, ఈ రెండు దశలకు వేర్వేరు hardware మరియు వేర్వేరు scheduling అవసరాలు ఉండటమే. prefill కు compute మరియు పెద్ద token batches అవసరం. decode కు bandwidth మరియు ఒకేసారి నడిచే అనేక sequences అవసరం. వాటిని విడదీయడం వల్ల ప్రతి pool ను స్వతంత్రంగా scale చేయవచ్చు. అలాగే ఒక పెద్ద prompt కారణంగా అన్ని active streams నిలిచిపోకుండా ఉంటుంది.
ఒకే GPU ఉన్న single VPS (virtual private server) పై దీన్ని చేయడం దాదాపు ఎప్పుడూ ప్రయోజనకరం కాదు. మీరు ఒకే device ను దాని నుంచే విభజించినట్లవుతుంది. అదనంగా, pointer ను gigabytes పరిమాణంలోని cache network transfer గా మార్చాల్సి వస్తుంది. ప్రతి దశకు మొత్తం machines కేటాయించడానికి సరిపడా accelerators ఉన్నప్పుడు, అలాగే రెండు pools ను నిరంతరం busy గా ఉంచగల స్థిరమైన traffic ఉన్నప్పుడు ఈ విధానం ప్రయోజనకరంగా ఉంటుంది. అంతకంటే తక్కువ స్థాయిలో, ఒక flag తో chunked prefill దాదాపు ఇదే isolation ను అందిస్తుంది.
సంఖ్య సరిగ్గా లేకపోతే ఏమి మార్చాలి
TTFT చాలా ఎక్కువగా ఉన్నప్పుడు:
- prompt ను చిన్నదిగా చేయండి. Prefill ఖర్చు prompt tokens ను అనుసరిస్తుంది. ప్రతి అభ్యర్థనలో system prompt కోసం కూడా వనరులు వినియోగించబడతాయి.
- prefix caching ను ప్రారంభించండి. దీంతో పునరావృతమయ్యే prefix ను ప్రతి సారి కాకుండా ఒక్కసారి మాత్రమే compute చేస్తుంది.
--max-num-batched-tokensను పెంచండి. దీంతో ప్రతి step లో ఎక్కువ prefill పని జరుగుతుంది.- model ను నిందించే ముందు queue ను పరిశీలించండి.
vllm:num_requests_waitingసున్నా కంటే ఎక్కువగా ఉంటే అభ్యర్థన ఇంకా ప్రారంభం కాలేదని అర్థం. ఇది capacity సమస్య.
tokens per second చాలా తక్కువగా ఉన్నప్పుడు:
- weights ను quantize చేయండి. ప్రతి weight కు తక్కువ bytes ఉంటే, ప్రతి token కోసం చదవాల్సిన bytes కూడా తక్కువగా ఉంటాయి.
- మీ card కోసం ప్రచురించిన memory bandwidth ను పై chart తో పోల్చండి. మీరు ceiling కు ఎంత దగ్గరగా ఉన్నారో చూడండి.
--max-num-batched-tokensను తగ్గించండి. దీంతో prefill, decode ను తక్కువ తరచుగా అంతరాయం కలిగిస్తుంది.- context length ను పరిశీలించండి. వేల tokens వరకు పెరిగిన conversation ప్రతి step లో చాలా పెద్ద KV cache ను చదువుతుంది.
ఇక్కడ runtime కూడా ముఖ్యం. ఎందుకంటే Ollama మరియు vLLM prefill, decodeలను వేర్వేరు విధాలుగా schedule చేస్తాయి. ఒక runtime కు సహాయపడే setting మరొక runtime పై ఎలాంటి ప్రభావం చూపకపోవచ్చు. ముందుగా రెండు phases లోనూ కొలవండి. తరువాత ఒక్కసారి ఒక్క మార్పు చేయండి.
FAQ
నా మొదటి token రావడానికి సెకన్లు పడుతున్నాయి, కానీ మిగతావి వేగంగా stream అవుతున్నాయి. ఎందుకు?
ఆ నిరీక్షణ prefill, ఆ streaming decode. ఏదైనా output ఏర్పడకముందు prefill మొత్తం prompt ను compute-bound pass లో ప్రాసెస్ చేస్తుంది. అందువల్ల దాని ఖర్చు prompt పొడవు పెరిగే కొద్దీ పెరుగుతుంది. తరువాత decode ప్రతి step కు ఒక token ను విడుదల చేస్తుంది. దీని వేగాన్ని memory bandwidth నిర్ణయిస్తుంది. ఇది prompt ఎంత పొడవుగా ఉందో దాదాపు స్వతంత్రంగా ఉంటుంది. ప్రతి request లో పొడవైన system prompt ఉండటం సాధారణ కారణం. Prefix caching ద్వారా మళ్లీ మళ్లీ ప్రాసెస్ చేయాల్సిన భాగం తొలగిపోతుంది.
పొడవైన prompt tokens per second వేగాన్ని తగ్గిస్తుందా?
కొంతవరకు తగ్గిస్తుంది. అయితే దీనికి TTFT కంటే వేరే కారణం ఉంది. ప్రతి decode step లో ముందు వచ్చిన అన్ని tokens కు చెందిన keys మరియు values చదవాలి. అందువల్ల పెద్ద KV cache ఉన్నప్పుడు ప్రతి token కు చదవాల్సిన bytes పెరుగుతాయి. సాధారణ 8 billion parameter layout లో cache ప్రతి token కు సుమారు 128 KiB ఉంటుంది. కాబట్టి 8,000 token context లో ప్రతి step వద్ద సుమారు 1 GB డేటాను చదవాలి. అయినప్పటికీ పొడవైన prompt ప్రభావం ప్రధానంగా TTFT పై ఉంటుంది, streaming speed పై కాదు.
Decode speed ను ఏ GPU specification ముందుగా అంచనా వేయగలదు?
Memory bandwidth. ప్రచురించిన bandwidth ను memory లోని weights పరిమాణంతో భాగిస్తే, ఒక stream కు సాధ్యమైన arithmetic ceiling లభిస్తుంది. ఎక్కువ compute ఉన్నా అదే bandwidth కలిగిన card వేగంగా stream చేయదు. 8 bits కు quantize చేసినప్పుడు decode speed సుమారు రెట్టింపు కావడానికి ఇదే కారణం. Compute ను మార్చకుండా ప్రతి token కు చదవాల్సిన bytes సగానికి తగ్గుతాయి.
Users ను జోడించినప్పుడు throughput పెరుగుతోంది, కానీ ప్రతి user కు నెమ్మదిగా అనిపిస్తోంది. ఎందుకు?
Weights ను ఒకసారి చదవడం batch లోని ప్రతి sequence కు ఒక token ను అందిస్తుంది. అందువల్ల batch size పెరిగేకొద్దీ మొత్తం tokens per second పెరుగుతుంది. అయితే ప్రతి user యొక్క token ఇప్పుడు shared step కోసం వేచి ఉండాలి. కాబట్టి అదే సమయంలో ప్రతి user latency పెరుగుతుంది. Aggregate throughput సంఖ్యను మాత్రమే కాకుండా p99 inter token latency ను కూడా monitor చేయాలి. Requests queue అవుతున్నాయా, లేక అమలవుతున్నాయా తెలుసుకోవడానికి vllm:num_requests_waiting ను పరిశీలించండి.