Đặt vấn đề
Không có request nào thực sự chỉ là một request.
Ta nhìn dashboard, thấy:
11.000 request/s
rồi bộ não lập tức vui vẻ kết luận:
À, hôm nay hệ thống tải gấp đôi hôm qua.
Nhưng phía dưới con số bé tí đó là một đoàn quân token đang bò qua GPU.
Có request chỉ mang theo 200 token, giống một đứa trẻ chạy vào lớp hỏi:
Cô ơi, 1 + 1 bằng mấy?
Nhưng cũng có request kéo theo 30.000, 50.000 hay 100.000 token lịch sử hội thoại, source code, tool result, tài liệu RAG và system prompt, giống một ông khách bước vào quán phở, đặt lên bàn nguyên một cái vali rồi nói:
Anh đọc hết cái này trước, sau đó trả lời tôi đúng ba dòng.
Cả hai đều được dashboard đếm là:
11 request
Đó chính là chỗ mọi thứ bắt đầu trở nên ngáo ngáo.
GPU không thực sự “ăn” request theo nghĩa tải tính toán. GPU phải xử lý token, tensor, KV cache, memory bandwidth và những vòng decode nối tiếp nhau.
Paper “A Year in LLM Serving: Workload Evolution, Caching and Load-Balancing” của William Nixon, Jon Durbin, Florian Standhartinger, Haryadi S. Gunawi và Juncheng Yang nhìn vào đúng vấn đề này bằng một lượng dữ liệu production khá hiếm: một trace kéo dài một năm từ nền tảng Chutes, gồm hơn 6,12 tỷ request, hàng nghìn model và hàng trăm nghìn user. (arXiv)
Điều thú vị nhất của paper không phải là con số 6 tỷ.
Mà là sau 6 tỷ request đó, một số trực giác tưởng như hiển nhiên của chúng ta về LLM serving bắt đầu lung lay.
Một năm production LLM lớn đến mức nào?
Trace được thu thập trong khoảng từ 11/04/2025 đến 12/04/2026.
Nó gồm:
- 6.122.413.756 request.
- 314.970 user.
- 9.174 model.
- Trong đó 3.922 model public và 5.252 model private.
- 875.921 serving instance xuất hiện trong trace.
- Khoảng 35,8 nghìn tỷ input token.
- Khoảng 2,52 nghìn tỷ output token.
Tính trung bình thô trên toàn trace, mỗi request chứa khoảng 5.847 input token và 412 output token. Nhưng chính paper cũng cho thấy những con số trung bình kiểu này có thể che giấu một thế giới cực kỳ không đồng đều phía dưới. (arXiv)
Đây là điểm rất quan trọng.
Nếu ta chỉ benchmark một model bằng vài nghìn prompt ShareGPT rồi bắn request theo Poisson distribution, ta đang nhìn một cái hồ nhân tạo rất sạch sẽ.
Production thật giống biển.
Có sóng.
Có bão.
Có thủy triều.
Có hôm êm ru, rồi đột nhiên một đám agent thức dậy lúc 2 giờ sáng và bắt đầu gọi API như thể ngày mai Trái Đất sẽ hết token.
Trước tiên: GPU làm gì khi nhận một request?
Một request LLM có thể đơn giản hóa thành hai giai đoạn:
1Prompt
2 |
3 v
4Prefill
5 |
6 v
7KV Cache
8 |
9 v
10Decode token 1
11 |
12Decode token 2
13 |
14Decode token 3
15 |
16 ...
Prefill
Prefill là lúc model đọc đề.
Giả sử prompt có 20.000 token.
Model phải xử lý 20.000 token đó để tạo trạng thái attention và KV cache trước khi bắt đầu nói.
Input càng dài thì prefill càng nặng.
Một metric rất hay được quan tâm ở đây là Time To First Token (TTFT):
1User gửi request
2 |
3 |------- TTFT -------|
4 v
5 token đầu tiên
Decode
Sau khi đọc đề xong, model bắt đầu sinh token.
Nhưng nó không viết cả câu một phát.
Nó làm kiểu:
1"Hôm"
2"Hôm nay"
3"Hôm nay trời"
4"Hôm nay trời đẹp"
5...
Từng token một.
Do đó output càng dài thì request càng phải nằm trong vòng decode lâu.
Paper quan sát thấy trên trace này, latency end-to-end thường bị chi phối bởi decode, trong khi TTFT liên quan mạnh tới input length. Những workload như DeepSeek R1 thiên về output dài có đặc tính khác rõ rệt so với MiniMax-M2.5, vốn có xu hướng input dài và output tương đối ngắn trong các workload coding/agentic. (arXiv)
Vì vậy hai request này:
1Request A
2Input: 500 tokens
3Output: 5.000 tokens
4
5Request B
6Input: 30.000 tokens
7Output: 200 tokens
đều là một request.
Nhưng chúng hành hạ hệ thống theo hai kiểu hoàn toàn khác nhau.
A bắt decode chạy marathon.
B bắt prefill nuốt một cuốn sách rồi mới được nói.
Phát hiện thứ nhất: Request Per Second có thể nói dối
Một trong những biểu đồ đáng chú ý nhất của paper đặt request volume và token volume cạnh nhau.
Hai đường có liên quan.
Nhưng chúng không phải một.
Có những giai đoạn số request tương tự nhau trong khi tổng token phải xử lý khác đáng kể. Số model active, loại API được sử dụng và hình dạng input/output cũng thay đổi trong suốt năm.
Nói cách khác:
1Throughput != Request/s
ít nhất nếu ta đang nói về lượng công việc thực sự mà GPU phải xử lý.
Một dashboard production chỉ có:
1RPS
2P95 latency
3GPU utilization
là chưa đủ.
Ít nhất nên quan sát thêm:
1input_tokens/s
2output_tokens/s
3prefill tokens/s
4decode tokens/s
5TTFT
6cache hit ratio
Bởi vì:
110 request × 1.000 token
không giống:
110 request × 50.000 token
Cùng mười ông khách.
Một nhóm gọi mười ly trà đá.
Nhóm còn lại gọi nguyên con bò.
Nhà hàng không thể autoscale bằng cách chỉ đếm số đầu người.
Workload LLM không đứng yên
Một assumption rất dễ mắc phải khi capacity planning là:
Lấy traffic tháng trước làm chuẩn cho năm sau.
Paper cho thấy LLM workload là một hệ thống non-stationary.
Không chỉ traffic thay đổi.
Bản chất request cũng thay đổi.
Trong trace một năm, các model thống trị traffic liên tục thay phiên nhau. Đầu kỳ, các model DeepSeek chiếm tỷ trọng lớn; về sau những model khác như Qwen3-32B hay DeepSeek-V3.2 xuất hiện nhiều hơn, trong khi phần traffic thuộc nhóm “Others” ngày càng lớn. (arXiv)
Đây là một chuyện khá tự nhiên.
Người dùng không cưới model.
Model mới ra tốt hơn, rẻ hơn, nhanh hơn thì application đổi model.
Router đổi model.
Agent framework đổi model.
Ba tháng sau workload đã biến thành một con vật khác.
Output đang ngắn dần
Một phát hiện thú vị khác là median input toàn cục tương đối ổn định ở mức hàng nghìn token, nhưng median output giảm rõ rệt trong năm: từ vài trăm token xuống dưới 100 token ở gần cuối trace. (arXiv)
Nhìn riêng từng cohort user thì lại thấy một xu hướng khác:
user mới có xu hướng đưa vào prompt dài hơn.
(arXiv)
Có thể hình dung sự tiến hóa của application LLM như thế này:
12023:
2"Viết giúp tôi email xin nghỉ phép"
3
42026:
5System prompt
6+ user profile
7+ memory
8+ RAG
9+ tool definitions
10+ tool outputs
11+ conversation history
12+ JSON schema
13+ policy
14+ rồi cuối cùng:
15"Trả lời 2 dòng"
Con người ngày càng nhét nhiều thứ vào đầu model.
Nhưng lại muốn model nói ngắn hơn.
Một sinh vật kỳ lạ.
Đọc ba cuốn sách.
Trả lời “OK”.
Về mặt serving, điều này đẩy áp lực ngày càng nhiều về phía prefill và làm prefix caching trở nên đáng giá hơn.
Long-tail model: đừng dựng một căn nhà cho mỗi người ngủ một đêm
9.174 model không có nghĩa 9.174 model đều bận tối mắt.
Traffic có long tail rất dài.
Nhiều model nhận request thưa thớt và có inter-arrival time lớn.
Nhưng paper phát hiện thêm một thứ quan trọng: traffic không chỉ thưa, nó thường bursty.
Nhóm tác giả đo hệ số biến thiên của inter-arrival time:
$$ CV = \frac{\sigma_{IAT}}{\mu_{IAT}} $$
Nếu:
$$ CV > 1 $$
thì request không đến đều đều.
Phần lớn model nằm trong vùng bursty này.
Đáng chú ý hơn, nhiều workload còn có positive autocorrelation: giai đoạn bận có xu hướng tiếp tục bận một lúc, và giai đoạn im lặng cũng có xu hướng tiếp tục im lặng. (arXiv)
Tức là model không hoạt động kiểu:
1request
2nghỉ
3request
4nghỉ
5request
6nghỉ
mà thường giống:
1....................
2
3R R R R R R R R R R R R
4
5....................
6
7R R R R R R
Điều này mở ra một hướng khá rõ cho multi-model serving.
Thay vì:
1Model A -> GPU riêng
2Model B -> GPU riêng
3Model C -> GPU riêng
4Model D -> GPU riêng
có thể nghĩ tới:
1 +--> Model A
2 |
3Shared GPU --+--> Model B
4 |
5 +--> Model C
với placement, multiplexing hoặc model loading dựa trên pattern traffic.
Paper chỉ ra đây là một cơ hội thiết kế chứ không khẳng định một thuật toán colocation cụ thể là tối ưu. Nhưng ý tưởng rất đáng chú ý: long-tail model không nhất thiết phải được coi như một service always-on độc lập. (arXiv)
Và rồi KV Cache xuất hiện
Giả sử chúng ta chat:
1User:
2Con mèo nhà tôi tên Bông.
3
4Assistant:
5OK.
6
7User:
8Nó 3 tuổi.
9
10Assistant:
11OK.
12
13User:
14Tên con mèo là gì?
Ở lượt cuối, prompt thực tế có thể chứa lại toàn bộ lịch sử:
1Con mèo nhà tôi tên Bông.
2OK.
3Nó 3 tuổi.
4OK.
5Tên con mèo là gì?
Model đã từng tính phần:
1Con mèo nhà tôi tên Bông.
2OK.
3Nó 3 tuổi.
4OK.
Nếu lần nào cũng vứt kết quả đi rồi tính lại từ đầu thì hơi giống mỗi sáng thức dậy ta đốt luôn quyển vở hôm qua, mua vở mới, rồi chép lại từ trang 1.
Prefix caching giữ lại KV state của phần prefix đã từng xử lý.
Request sau chỉ cần tính đoạn mới.
Ví dụ:
1Request trước:
2
3[A B C D E F]
4
5Request mới:
6
7[A B C D E F G H]
nếu cache còn:
1[A B C D E F]
thì GPU chỉ phải prefill:
1[G H]
Đây chính là lý do các hệ thống như SGLang xây RadixAttention để giữ và tìm các prefix KV có thể tái sử dụng; thiết kế gốc của SGLang sử dụng LRU kết hợp cache-aware scheduling. (arXiv)
Nghe thì có vẻ đơn giản.
Nhưng production lại thò ra một con quái vật khác.
Cache hit của LLM có hình dạng khá kỳ quặc
Một trong những kết quả mình thấy thú vị nhất của paper là phân phối prefix-cache hit ở request level có dạng bimodal.
Nói tiếng người:
request thường rơi vào một trong hai trạng thái:
1gần như cache được hết
hoặc:
1gần như không cache được gì
Các request nằm lưng chừng ít hơn đáng kể. (arXiv)
Điều này khá hợp lý nếu nghĩ về multi-turn conversation.
Request hoặc là:
1turn tiếp theo của session cũ
nên gần như toàn bộ context trước có thể reuse.
Hoặc:
1session mới
và chẳng có gì để reuse.
Nó giống việc bước vào thư viện.
Hoặc quyển sách bạn cần đang nguyên vẹn trên bàn.
Hoặc nó không có.
Ít khi có trường hợp:
Chúng tôi còn 43,7% quyển sách của anh.
Cache LLM là một sinh vật cực kỳ yêu ký ức vừa mới xảy ra
Đây có lẽ là kết quả thực dụng nhất trong paper.
Nhóm tác giả phân tích khoảng thời gian giữa các request liên tiếp của cùng một cặp:
1(user, model)
và thấy locality tập trung cực mạnh ở khoảng thời gian ngắn.
Khoảng một nửa repeat arrival xảy ra trong khoảng 0,1 giây, khoảng 80% trong vòng 10 giây, và gần như toàn bộ nằm trong vài phút; Figure 16 báo cáo khoảng 99% reuse xuất hiện trong vòng 15 phút kể từ request trước. (arXiv)
Đây là một dữ kiện cực kỳ quan trọng.
Cache LLM không giống kho lưu trữ ảnh nơi một object nổi tiếng có thể được gọi lại sau vài ngày.
Nó thường giống trí nhớ của một cuộc hội thoại:
109:00:00 turn 1
209:00:02 turn 2
309:00:05 turn 3
409:00:08 tool call
509:00:09 turn 4
Context rất nóng trong vài giây.
Rồi nguội cực nhanh.
Và điều này dẫn tới một kết quả khá hài.
Thuật toán cache xịn chưa chắc thắng LRU
Nhóm tác giả dùng trace để mô phỏng nhiều eviction policy, gồm những thuật toán như:
- FIFO.
- LRU.
- ARC.
- LRB.
- GDSF.
- Sieve.
- size-aware Belady làm oracle tham chiếu.
Kết quả trên hai workload đại diện MiniMax-M2.5 và DeepSeek V3.2 cho thấy nhiều thuật toán cache phức tạp không vượt qua được những policy đơn giản như FIFO và LRU, thậm chí một số trường hợp còn kém hơn. (arXiv)
Tại sao?
Bởi workload có recency bias cực mạnh.
Thứ vừa được dùng rất có khả năng sắp được dùng lại.
Thứ đã lạnh một lúc thì khả năng quay lại giảm nhanh.
Và đó chính xác là thế giới mà LRU rất thích.
1Recently Used
2 |
3 v
4giữ lại
5
6Lâu không đụng
7 |
8 v
9đuổi đi
Xong thuật toán.
Quá dễ.
Tất nhiên không nên kết luận:
LRU là cache algorithm cuối cùng của nhân loại.
Size-aware Belady vẫn cho thấy một khoảng cách đáng kể trong một số workload, nghĩa là còn chỗ cho những cache policy được thiết kế riêng cho LLM. (arXiv)
Nhưng paper gửi đi một lời cảnh báo rất đẹp:
Một thuật toán được chứng minh tốt trên web cache hay storage workload không mặc nhiên tốt cho KV cache của LLM.
Workload quyết định thuật toán.
Không phải độ ngầu của tên thuật toán.
Rồi load balancing phá bữa tiệc
Đến đây ta có thể nghĩ:
Dễ mà. User nào đã nằm trên GPU nào thì cứ gửi tiếp request về GPU đó.
Đúng.
Cache hit sẽ đẹp.
Ví dụ ta có ba instance:
1GPU A
2GPU B
3GPU C
User Tùng đang chat qua GPU A.
GPU A có:
1KV(prefix của Tùng)
Turn sau tiếp tục gửi về A:
1Tùng -> GPU A
Cache hit.
Rất đẹp.
Nhưng nếu Tùng đột nhiên chạy 1.000 agent song song?
GPU A:
1🔥🔥🔥🔥🔥🔥🔥🔥🔥
GPU B:
1...
GPU C:
1...
Chúng ta vừa thắng cache.
Và thua load balancing.
Cache locality và load balancing là hai ông kéo co
Paper so sánh bốn chiến lược routing trong simulation.
Round-robin
1request 1 -> GPU A
2request 2 -> GPU B
3request 3 -> GPU C
4request 4 -> GPU A
Rất công bằng.
Nhưng session bị rải lung tung.
Load-first
Gửi request tới instance đang có token load thấp nhất.
Tốt cho cân bằng tải.
Nhưng cũng có thể phá locality.
Sticky
Một user được ghim vào một instance.
1Tùng -> GPU B
2Tùng -> GPU B
3Tùng -> GPU B
Cache locality cực đẹp.
Nhưng một heavy user có thể biến GPU B thành lò nướng.
Cache-first
Tìm instance đang giữ prefix có thể reuse nhiều nhất.
Nếu không có cache phù hợp thì mới fallback sang instance ít tải.
Paper cho thấy sự khác biệt chính nằm giữa cache-aware routing và cache-blind routing. Khi cache trên mỗi instance lớn lên, round-robin hay load-first vẫn không tận dụng tốt lượng cache mới vì các turn tiếp theo tiếp tục bị rải sang replica khác. Cache-first thì biến phần cache tăng thêm thành hit ratio thực sự. (arXiv)
Đây là một kết quả rất quan trọng:
Mua thêm KV cache chưa chắc tăng cache hit nếu router cứ ném request sai chỗ.
Ta có thể xây cả một thư viện khổng lồ.
Nhưng mỗi lần cần sách lại chạy sang thư viện khác.
RAM vẫn đầy.
CPU vẫn vui.
Người đọc vẫn không có sách.
Một cái giá khác: KV cache bị nhân bản
Giả sử context của một session có kích thước X.
Nếu mọi turn đều về GPU A:
1GPU A: X
2GPU B: 0
3GPU C: 0
Tổng cache:
$$ X $$
Nhưng nếu session bị rải:
1GPU A: X
2GPU B: X
3GPU C: X
ta có thể tiêu tốn:
$$ 3X $$
cho cùng một context logic.
Paper gọi hiện tượng này là replication.
Trong production, khi load cao, router có xu hướng trải request của cùng một user-model pair ra nhiều instance hơn để giữ tải cân bằng. Cache hit có thể vẫn tồn tại, nhưng cái giá là cùng một prefix phải xuất hiện ở nhiều KV cache cục bộ. (arXiv)
Đây chính là sự giằng co:
1Cache locality
2 ^
3 |
4 |
5 |
6 +---------> Load balance
Kéo về cache quá mạnh:
1hot GPU
Kéo về balance quá mạnh:
1KV duplication
2+
3cache miss
Scheduler tốt phải đứng ở giữa.
Không phải như một ông bảo vệ chỉ biết:
Chỗ nào trống thì mời anh vào.
Mà phải biết:
Anh là ai, sách của anh đang nằm ở đâu, chỗ đó đang đông đến mức nào, và nếu chuyển anh sang bàn khác thì phải photocopy bao nhiêu quyển sách.
Load imbalance chủ yếu làm prefill đau trước
Paper còn quan sát một điểm khá thú vị trong production.
Khi một instance chịu token throughput quá cao, P90 TTFT tăng mạnh, đặc biệt với request có input dài.
Trong khi decode time chủ yếu tiếp tục phụ thuộc vào output length và ít nhạy hơn với mức throughput của instance trong phép đo này. (arXiv)
Nói đơn giản:
1GPU quá đông
2 |
3 +--> người mới phải chờ lâu để được "đọc đề"
Đây là lý do input-heavy workload rất dễ bị tổn thương bởi load imbalance.
Một agent gửi:
140.000 input tokens
2200 output tokens
có thể không giữ decode lâu.
Nhưng nếu nó bị đẩy vào một GPU đang nghẹt prefill thì TTFT sẽ tăng mạnh.
Nếu xây LLM serving production, mình rút ra điều gì?
Paper không đưa ra một scheduler thần thánh giải quyết tất cả.
Nhưng từ dữ liệu của nó, có thể rút ra một kiến trúc tư duy khá rõ.
1. Đừng autoscale chỉ bằng RPS
Thay vì:
1if RPS > 100:
2 scale_up()
ít nhất nên nghĩ tới:
1input_token_rate
2output_token_rate
3active_sequences
4prefill_queue
5decode_queue
6TTFT
7KV cache pressure
Request là đơn vị business.
Token mới gần hơn với đơn vị physical workload.
2. Router nên biết cache đang ở đâu
Một router hoàn toàn cache-blind:
1least_requests
2round_robin
3random
đang bỏ qua một lượng thông tin rất giá trị.
Ít nhất scheduler có thể cân nhắc:
1prefix_match
2current_token_load
3queue_depth
4cache_pressure
Một heuristic minh họa — đây không phải công thức trong paper — có thể tưởng tượng như:
$$ Score(i) = \alpha C_i - \beta L_i - \gamma Q_i - \delta R_i $$
Trong đó:
- $C_i$: số token prefix có thể reuse trên instance $i$.
- $L_i$: load hiện tại.
- $Q_i$: queue pressure.
- $R_i$: chi phí replication dự kiến.
Chọn:
$$ i^* = \arg\max_i Score(i) $$
Trong thực tế vấn đề khó hơn rất nhiều, nhưng tư duy quan trọng là:
cache và load không nên được tối ưu bởi hai bộ não hoàn toàn không nói chuyện với nhau.
Preble trước đó cũng đi theo hướng joint scheduling giữa KV reuse và distributed load balancing, cho thấy đây đã trở thành một bài toán riêng của distributed LLM serving chứ không còn là một chi tiết nhỏ trong inference engine. (arXiv)
3. Đừng nghĩ cache capacity và cache hit là cùng một thứ
Có:
1100 GB KV cache
không đồng nghĩa:
1cache hit cao
Nếu request liên quan không quay lại đúng nơi chứa cache, RAM đó chỉ đang ngồi rất nghiêm túc và không làm được gì.
Ta cần xem đồng thời:
1cache capacity
2cache hit ratio
3routing locality
4replication ratio
4. Long-tail model cần scheduler khác hot model
Một model có hàng triệu request mỗi giờ và một fine-tuned model nhận vài request mỗi phút không nhất thiết phải được deploy bằng cùng một chiến lược.
Model long tail có thể phù hợp hơn với:
- colocation;
- multiplexing;
- dynamic placement;
- sleep/wake;
- model pool dùng chung;
- dự đoán burst dựa trên historical pattern.
Đây là nơi workload trace dài hạn có giá trị hơn benchmark một buổi chiều.
Một dashboard LLM serving mình sẽ muốn có
Sau khi đọc paper này, nếu chỉ được làm một dashboard production, mình sẽ không bắt đầu bằng CPU và GPU.
Mình sẽ bắt đầu từ workload.
1Traffic
2├── requests/s
3├── input tokens/s
4├── output tokens/s
5├── input length distribution
6└── output length distribution
7
8Latency
9├── TTFT P50/P90/P99
10└── end-to-end latency
11
12Cache
13├── cached token ratio
14├── prefix hit distribution
15├── cache working set
16└── replication ratio
17
18Routing
19├── requests/instance
20├── tokens/instance
21├── max/mean token load
22└── user/session spread across replicas
23
24Temporal
25├── inter-arrival time
26├── burstiness
27└── model popularity evolution
Bởi nếu dashboard chỉ nói:
1GPU utilization = 80%
thì ta biết GPU đang bận.
Nhưng không biết tại sao nó bận.
Và đó mới là câu hỏi quan trọng.
Một hạn chế quan trọng của nghiên cứu
Paper này có quy mô dữ liệu rất lớn, nhưng không có nghĩa mọi kết luận có thể bê nguyên xi vào tất cả hệ thống.
Có vài điểm cần lưu ý.
Thứ nhất, trace đến từ một nền tảng cụ thể. Một hệ thống enterprise chatbot, coding agent, consumer chat hay batch inference có thể có workload khác.
Thứ hai, dữ liệu cached_tokens chỉ có trong hai tháng cuối của trace, nên phần phân tích production prefix caching không bao phủ trọn một năm. (arXiv)
Thứ ba, trace không chứa prompt hay explicit session ID. Để chạy cache simulation, nhóm tác giả phải reconstruct multi-turn session từ trình tự request, user, model, endpoint và quan hệ token length. Vì vậy đây là một phép suy luận session hợp lý phục vụ simulation chứ không phải session ground truth tuyệt đối.
Thứ tư, phần routing simulation tập trung vào một số model đại diện như DeepSeek V3.2 và MiniMax-M2.5. Kết quả cho thấy cơ chế và trade-off, nhưng không nên hiểu rằng các đường cong đó sẽ giống hệt trên mọi model.
Và đây lại quay về triết lý ban đầu của paper:
workload matters.
Điều mình thấy đáng giá nhất trong paper
Chúng ta thường nói về LLM serving bằng tên công nghệ:
1PagedAttention
2FlashAttention
3Continuous Batching
4Prefix Cache
5Speculative Decoding
6Tensor Parallel
7Disaggregated Serving
Nhưng tất cả chúng chỉ là những bánh răng.
Bánh răng nào nên quay nhanh, bánh răng nào nên lớn, bánh răng nào thậm chí không cần tồn tại đều phụ thuộc vào một thứ ít hào nhoáng hơn:
1người dùng thực sự đang gửi cái gì
6,12 tỷ request trong paper này kể một câu chuyện khá rõ.
Traffic không đứng yên.
Model không đứng yên.
User không đứng yên.
Input và output cũng không đứng yên.
Cache vừa tạo xong có thể cực kỳ quý giá trong mười giây tiếp theo rồi nhanh chóng trở thành một cục tensor chiếm RAM.
Một router cố làm cho GPU công bằng có thể vô tình phá locality.
Một router cố giữ locality có thể biến một GPU thành núi lửa.
Và một thuật toán cache mang theo cả một vali paper hàn lâm đôi khi vẫn bị ông LRU già nua ngồi góc phòng đánh bại, đơn giản vì workload này yêu những thứ vừa mới xảy ra.
Không có scheduler tốt trong chân không.
Không có cache policy tốt trong chân không.
Không có con số RPS nào tự mình kể đủ câu chuyện.
Phía sau mỗi request là một chuỗi vật lý rất thật: token được đọc, matrix được nhân, HBM bị chiếm, KV tensor được giữ lại, request được đẩy từ GPU này sang GPU khác, rồi hàng triệu byte ký ức có thể bị nhân bản chỉ vì một quyết định routing tưởng như vô hại.
Và có lẽ bài học lớn nhất của paper chỉ gói trong một câu:
Muốn tối ưu LLM serving, trước tiên phải hiểu workload của mình thực sự đang sống như thế nào.
Không phải workload trong benchmark.
Không phải workload mà ta tưởng tượng.
Mà là con vật đang chạy ngoài production.
Cảm ơn các bạn đã quan tâm và theo dõi bài viết, hẹn gặp lại ở các bài viết tiếp theo.
Tham khảo
-
A Year in LLM Serving: Workload Evolution, Caching and Load-Balancing https://arxiv.org/abs/2608.13573
-
Preble: Efficient Distributed Prompt Scheduling for LLM Serving https://arxiv.org/abs/2407.00023
-
SGLang: Efficient Execution of Structured Language Model Programs https://arxiv.org/abs/2312.07104
-
libCacheSim https://github.com/1a1a11a/libCacheSim
Bình luận