[AI & MLOps Blog #54] Tối Ưu Hóa Suy Luận Mô Hình Ngôn Ngữ Lớn (LLM Inference Optimization): PagedAttention (vLLM), Continuous Batching, Speculative Decoding & Lượng Tử Hóa KV Cache
![[AI & MLOps Blog #54] Tối Ưu Hóa Suy Luận Mô Hình Ngôn Ngữ Lớn (LLM Inference Optimization): PagedAttention (vLLM), Continuous Batching, Speculative Decoding & Lượng Tử Hóa KV Cache](/uploads/ai-images/cover-llm-inference-optimization.png)
1. Bối cảnh thực tế (Context & Problem Statement)
Trong vòng đời của các sản phẩm Trí tuệ Nhân tạo tạo sinh (Generative AI):
- Quá trình Huấn luyện (Training / Fine-tuning) chỉ diễn ra định kỳ vài tuần hoặc vài tháng một lần.
- Quá trình Suy luận (Inference / Serving) diễn ra 24/7 với hàng triệu lượt yêu cầu mỗi ngày. Chi phí vận hành phần cứng GPU phục vụ suy luận chiếm tới tổng ngân sách AI của doanh nghiệp.
Nếu phục vụ một mô hình ngôn ngữ lớn (LLaMA 3, Mistral) bằng các Web Framework truyền thống (FastAPI, Flask, TorchServe thông thường):
- Lãng phí bộ nhớ GPU trầm trọng: Do độ dài câu trả lời của người dùng không thể biết trước, cơ chế cấp phát bộ nhớ tĩnh buộc phải giữ chỗ trước các khối VRAM liền kề khổng lồ, dẫn đến việc lãng phí tới VRAM do phân mảnh bộ nhớ (Memory Fragmentation).
- Thông lượng (Throughput) cực thấp: Các yêu cầu đến sau phải chờ toàn bộ batch cũ sinh xong từ cuối cùng mới được phục vụ.
Để đưa ứng dụng LLM vào phục vụ hàng chục nghìn người dùng đồng thời với chi phí thấp nhất và độ trễ dưới , kỹ sư AI hiện đại bắt buộc phải làm chủ các kỹ thuật tối ưu hóa suy luận ở cấp độ kiến trúc hệ thống và phần cứng GPU.
2. Các Khái Niệm & Cơ Chế Cốt Lõi
2.1. Hai Pha Vật Lý Của Quá Trình Suy Luận: Prefill vs. Decoding
Quá trình sinh văn bản của một mô hình tự hồi quy (Autoregressive LLM) bị chi phối bởi hai quy luật vật lý hoàn toàn khác biệt:
PHA 1: PREFILL (Xử lý toàn bộ Prompt) PHA 2: DECODING (Sinh từng từ tuần tự)
[User Prompt: 1,000 tokens] [Token 1] ---> [Token 2] ---> [Token 3] ...
- Tính toán song song trên toàn bộ 1,000 tokens. - Sinh TỪNG TOKEN MỘT qua vòng lặp.
- Nạp đầy năng lực tính toán của GPU cores. - Bắt buộc nạp toàn bộ 70B trọng số từ HBM
- COMPUTE-BOUND (Nghẽn năng lực tính toán). vào SRAM chỉ để sinh DUY NHẤT 1 token!
- MEMORY-BANDWIDTH BOUND (Nghẽn băng thông nhớ).
1. Cường độ tính toán (Arithmetic Intensity):
- Pha Prefill: Mô hình xử lý đồng thời tất cả các tokens của prompt. Tận dụng ma trận nhân ma trận (GEMM), số phép tính FLOPs rất lớn so với số bytes nạp từ bộ nhớ Tận dụng tối đa sức mạnh của Tensor Cores (Compute-Bound).
- Pha Decoding: Tại mỗi bước sinh từ, mô hình chỉ xử lý đúng token mới. Đây là phép nhân ma trận với vector (GEMV).
- Hậu quả: Để sinh ra 1 token cho mô hình 70B (kích thước ở FP16), GPU bắt buộc phải kéo toàn bộ trọng số từ bộ nhớ chính (HBM) vào chip xử lý (SRAM) chỉ để làm vài phép tính cho 1 token!
- Các nhân tính toán Tensor Cores phải ngồi chơi xơi nước thời gian để chờ dữ liệu được truyền qua bus bộ nhớ (Memory-Bandwidth Bound).
2.2. Đột Phá PagedAttention & Kiến Trúc vLLM (Kwon et al., UC Berkeley 2023)
Trong quá trình Decoding, mô hình lưu trữ toàn bộ các vector Key và Value của các từ trước đó vào KV Cache để tránh tính toán lại.
- Vấn nạn của KV Cache truyền thống:
- Bộ nhớ GPU cấp phát tĩnh các mảng bộ nhớ liền kề (Contiguous Memory).
- Do không biết câu trả lời dài bao nhiêu, hệ thống phải cấp phát sẵn cho độ dài tối đa (ví dụ tokens) Gây ra hiện tượng Phân mảnh nội (Internal Fragmentation) và Phân mảnh ngoại (External Fragmentation), khiến GPU hết VRAM khi mới chỉ phục vụ được vài người dùng.
- Cơ chế PagedAttention (Lấy cảm hứng từ Bộ nhớ ảo của Hệ điều hành):
- Chia nhỏ KV Cache thành các Trang bộ nhớ cố định (Physical Blocks / Pages), mỗi trang chứa đúng hoặc tokens.
- Các trang này được lưu trữ rải rác ở bất kỳ vùng nhớ trống nào trong VRAM.
- Sử dụng một Bảng ánh xạ trang (Block Table) để chuyển đổi địa chỉ logic liên tục của câu văn thành các địa chỉ khối vật lý phân tán.
- Hiệu quả vượt bậc: Giảm tỷ lệ lãng phí VRAM từ xuống dưới , cho phép GPU nhồi thêm gấp số lượng request xử lý đồng thời (Throughput tăng vọt)!
ĐỊA CHỈ LOGIC: [ Token 0 - 15 ] ---> [ Token 16 - 31 ] ---> [ Token 32 - 47 ]
| | |
(Block Table) (Block Table) (Block Table)
v v v
VRAM VẬT LÝ: [ Trang 7 (RAM) ] [ Trang 2 (RAM) ] [ Trang 19 (RAM) ]
(Lưu trữ rải rác, không cần liền kề -> Triệt tiêu 100% phân mảnh)
2.3. Lập Lịch Cấp Độ Lặp: Continuous Batching (Orca / vLLM)
- Static Batching truyền thống: Gom 4 câu hỏi vào 1 batch. Câu hỏi ngắn nhất sinh xong sau 10 tokens, câu hỏi dài nhất sinh 500 tokens.
3 câu hỏi ngắn phải tiếp tục ngồi chờ vô nghĩa trong VRAM cho đến khi câu hỏi dài nhất sinh xong! - Continuous Batching (Iteration-level Scheduling):
- Bộ lập lịch không đợi cả batch hoàn thành.
- Tại mỗi chu kỳ sinh từ đơn lẻ (Per-iteration): Ngay khi một request sinh xong token kết thúc (
<eos>), hệ thống lập tức giải phóng vị trí của nó và chèn một request mới từ hàng đợi vào ngay lập tức mà không làm gián đoạn các request đang chạy dở!
STATIC BATCHING (Lãng phí GPU):
Req 1: [Tokens] [DONE] ..................... Chờ Req 3 xong mới trả về!
Req 2: [Tokens.......] [DONE] .............. Chờ Req 3 xong mới trả về!
Req 3: [Tokens............................] [DONE] -> Toàn bộ Batch hoàn thành.
CONTINUOUS BATCHING (Tối ưu tuyệt đối):
Req 1: [Tokens] [DONE] -> Trả về ngay! Chèn Req 4 vào thế chỗ!
Req 4: [Tokens..............]
Req 2: [Tokens.......] [DONE] -> Trả về ngay! Chèn Req 5 vào thế chỗ!
Req 3: [Tokens............................]
2.4. Speculative Decoding: Kỹ Thuật "Đoán Trước Tương Lai" Không Tổn Hao
Làm thế nào để vượt qua giới hạn Memory-bound của pha Decoding mà không làm suy giảm chất lượng mô hình?
- Ý tưởng hai mô hình phối hợp (Draft Model + Target Model):
- Mô hình phụ trợ nhỏ (Draft Model - ví dụ LLaMA-3-8B): Rất nhỏ và chạy siêu nhanh. Trong vòng , nó chạy suy luận sinh trước một chuỗi gồm tokens dự đoán tương lai (ví dụ tokens).
- Mô hình chính lớn (Target Model - ví dụ LLaMA-3-70B): Nhận toàn bộ chuỗi tokens dự đoán đó và chạy đúng 1 bước Forward Pass duy nhất ở chế độ song song (Pha Prefill - Compute-bound) để kiểm chứng xác suất của cả 5 tokens cùng một lúc!
- Thuật toán chấp thuận (Rejection Sampling):
- Mô hình chính chấp thuận các token có xác suất hợp lý và dừng lại tại token đầu tiên bị sai.
- Tính chất toán học kỳ diệu: Bằng cách hiệu chỉnh phân phối xác suất bằng phép lấy mẫu chấp nhận/từ chối, phân phối đầu ra cuối cùng được chứng minh bằng toán học là TRÙNG KHỚP HOÀN TOÀN với việc chạy trực tiếp mô hình lớn 70B!
- Tăng tốc độ sinh từ thực tế lên gấp mà không làm mất đi một chút trí thông minh nào của mô hình!
Bước 1: [Draft Model 8B] sinh nhanh 4 tokens: ["Hà", "Nội", "là", "thủ"] (Mất 10ms)
|
Bước 2: [Target Model 70B] kiểm tra SONG SONG trong 1 lần chạy: (Mất 20ms)
- "Hà" -> Đúng (Accept)
- "Nội" -> Đúng (Accept)
- "là" -> Đúng (Accept)
- "thủ" -> Đúng (Accept)
- Sinh thêm từ tiếp theo: "đô"
===> Kết quả: Chỉ mất 30ms sinh được 5 tokens (Tốc độ tăng gấp 3x so với chạy tuần tự 70B)!
3. Các Bảng Markdown So Sánh Chi Tiết
📊 Bảng 1: So Sánh Toàn Diện: Prefill Phase vs. Decoding Phase
| Tiêu chí kỹ thuật | Pha Prefill (Context Phase) | Pha Decoding (Generation Phase) |
|---|---|---|
| Bản chất phép toán | Xử lý song song toàn bộ Input Prompt | Xử lý tuần tự từng từ một (Autoregressive) |
| Loại phép tính GPU | GEMM (Matrix - Matrix Multiplication) | GEMV (Matrix - Vector Multiplication) |
| Giới hạn vật lý | Compute-Bound (Nghẽn năng lực tính toán FLOPs) | Memory-Bandwidth Bound (Nghẽn băng thông đọc HBM) |
| Cường độ tính toán | Rất cao (Hàng trăm FLOPs / Byte nạp vào) | Cực thấp ( FLOPs / Byte nạp vào) |
| Chỉ số đo lường hiệu năng | Time-To-First-Token (TTFT) | Time-Per-Output-Token (TPOT) / Tokens-Per-Second |
| Kỹ thuật tối ưu hóa chính | FlashAttention, Chunked Prefill | PagedAttention, Speculative Decoding, KV Cache Quant |
📊 Bảng 2: So Sánh Các Cơ Chế Batching Trong LLM Serving
| Tiêu chí | Static Batching (Truyền thống) | Dynamic Batching (Triton cũ) | Continuous Batching (vLLM / TensorRT-LLM) |
|---|---|---|---|
| Cấp độ lập lịch | Cấp độ toàn bộ Batch (Batch-level) | Cấp độ toàn bộ Batch (Có gom hàng đợi) | Cấp độ từng lượt sinh từ (Iteration-level) |
| Xử lý câu có độ dài lệch nhau | Phải đệm <pad> gây lãng phí tính toán | Vẫn phải chờ câu dài nhất sinh xong | Tự động đào thải câu xong và chèn câu mới ngay |
| Độ trễ phản hồi (Latency) | Rất tệ khi có nhiều người dùng | Trung bình | Tối ưu nhất cho mọi request |
| Thông lượng GPU (Throughput) | Thấp () | Khá () | Cực đại ( so với Static) |
📊 Bảng 3: So Sánh Các LLM Inference Serving Engines Hàng Đầu Hiện Nay
| Tiêu chí | vLLM (UC Berkeley) | TensorRT-LLM (NVIDIA) | TGI (Text Generation Inference - HF) | Ollama / llama.cpp |
|---|---|---|---|---|
| Công nghệ tối ưu cốt lõi | PagedAttention + Continuous Batching | Kernel Fusion C++ cực sâu cho chip NVIDIA | FlashAttention + PagedAttention | Lượng tử hóa CPU/GGUF chạy cục bộ |
| Tốc độ thuần túy trên GPU | Cực nhanh, cấu hình linh hoạt | Nhanh nhất thế giới trên GPU NVIDIA | Rất nhanh | Chậm hơn (Tối ưu cho Edge/Local) |
| Hỗ trợ phần cứng | NVIDIA GPUs, AMD ROCm, Intel Gaudi | Chỉ độc quyền trên GPU NVIDIA | NVIDIA GPUs, Intel, Habana | CPU, Apple Silicon (Metal), GPU phổ thông |
| Độ phức tạp triển khai | Rất dễ (Cài bằng pip, chuẩn OpenAI API) | Phức tạp (Cần build engine nhị phân riêng) | Trung bình (Chạy qua Docker) | Siêu đơn giản (1 click cài đặt) |
| Use-case tối ưu | Doanh nghiệp vừa và lớn, Triển khai Cloud API | Hệ thống tải siêu khủng hàng triệu users cần ép xung | Doanh nghiệp trong hệ sinh thái HuggingFace | Chạy thử nghiệm nội bộ, Desktop, Thiết bị nhúng |
4. Best Practices & Kỹ Thuật Tối Ưu Hóa Bộ Nhớ VRAM (Pro-Tips)
💡 Công Thức Tính Dung Lượng KV Cache Trên GPU
Trước khi deploy mô hình, Data Engineer bắt buộc phải tính toán dung lượng VRAM mà KV Cache sẽ chiếm dụng: (Số đầu tiên là cho 2 vector Key và Value; Số thứ hai là số byte ở định dạng FP16).
- Ví dụ thực tế với LLaMA-3-8B (32 layers, GQA với 8 Key/Value heads, head dim 128) với Context 8,192 tokens:
- Nếu có 10 user đồng thời, KV Cache ngốn sạch hơn VRAM!
- Giải pháp cứu cánh: Lượng tử hóa KV Cache sang FP8 hoặc INT4 Giảm dung lượng KV Cache từ xuống còn , cho phép phục vụ số lượng người dùng đồng thời gấp !
💡 Kỹ Thuật "Chunked Prefill" Để Bảo Vệ Độ Trễ Của Pha Decoding
- Xung đột tài nguyên: Khi một người dùng gửi một đoạn văn bản dài tokens (Pha Prefill), GPU sẽ dồn toàn bộ sức mạnh để xử lý khối GEMM này trong . Hệ quả: Toàn bộ các luồng người dùng khác đang nhận từng token trả về (Pha Decoding) bị khựng lại nửa giây, tạo cảm giác đơ giật cho ứng dụng chat.
- Giải pháp Chunked Prefill (vLLM / Sarathi): Chia prompt tokens thành các khối nhỏ tokens. Xen kẽ giữa mỗi khối tokens của prompt là một bước sinh từ của các luồng Decoding Duy trì tốc độ gõ chữ mượt mà liên tục cho tất cả người dùng!
5. Góc Ôn Luyện Phỏng Vấn (Interview Corner)
❓ Câu hỏi 1: Tại sao giai đoạn Decoding (sinh từng token) trong LLM Inference lại bị nghẽn bởi Băng thông bộ nhớ (Memory-Bandwidth Bound) chứ không phải Năng lực tính toán (Compute-Bound)? Khái niệm Arithmetic Intensity giải thích điều này như thế nào?
- Gợi ý trả lời:
- Bản chất của thuật toán tự hồi quy (Autoregressive Generation):
- Để sinh ra một token mới tại bước , mô hình bắt buộc phải nhân vector của token đó với toàn bộ các ma trận trọng số của mạng nơ-ron ().
- Với mô hình 70 tỷ tham số lưu ở dạng FP16 (), tổng dung lượng trọng số là .
- Để sinh đúng 1 token duy nhất, GPU phải đọc toàn bộ dữ liệu này từ bộ nhớ HBM vào chip xử lý.
- Phân tích theo Arithmetic Intensity:
- Cường độ tính toán: .
- Để nhân một vector kích thước với một ma trận , số phép tính xấp xỉ FLOPs, trong khi số bytes cần đọc là bytes (ở FP16) Cường độ tính toán chỉ đạt .
- Một chiếc GPU NVIDIA H100 có năng lực tính toán cực đại lên tới , nhưng băng thông bộ nhớ chỉ đạt .
- Để tận dụng hết , mô hình cần một cường độ tính toán ít nhất là .
- Với mức của pha Decoding, chip GPU chỉ chạy được chưa đầy công suất tính toán, thời gian còn lại hoàn toàn bị nghẽn do phải chờ đợi bộ nhớ truyền dữ liệu qua bus Memory-Bandwidth Bound tuyệt đối.
- Bản chất của thuật toán tự hồi quy (Autoregressive Generation):
❓ Câu hỏi 2: PagedAttention giải quyết bài toán lãng phí và phân mảnh bộ nhớ VRAM của KV Cache như thế nào? Cơ chế Bảng trang (Block Table) hoạt động ra sao để hỗ trợ tính năng chia sẻ bộ nhớ khi người dùng thực hiện Parallel Sampling hoặc Beam Search?
- Gợi ý trả lời:
- Khắc phục phân mảnh bộ nhớ:
- Các hệ thống cũ cấp phát các mảng nhớ liên tục dựa trên độ dài ngữ cảnh tối đa. Do độ dài văn bản kết thúc ngẫu nhiên, phần đuôi không dùng đến bị bỏ phí (Internal Fragmentation) và các khoảng trống giữa các request không đủ lớn để nhồi request mới vào (External Fragmentation), làm lãng phí VRAM.
- PagedAttention chia nhỏ KV Cache thành các khối trang vật lý cố định (thường là 16 tokens). Khi câu văn dài thêm tokens, nó mới yêu cầu cấp phát thêm 1 trang mới từ danh sách trang trống (Free Block Pool). Các trang này có thể nằm ở bất kỳ đâu trong VRAM, triệt tiêu hoàn toàn sự lãng phí bộ nhớ.
- Cơ chế Bảng trang (Block Table) và Chia sẻ bộ nhớ (Memory Sharing):
- Bảng trang quản lý ánh xạ: .
- Trong Parallel Sampling (Một câu hỏi prompt sinh ra nhiều câu trả lời khác nhau):
- Cả 4 câu trả lời đều có chung một đoạn Prompt đầu vào.
- PagedAttention cho phép tất cả các tiến trình cùng trỏ chung vào các Khối trang vật lý của đoạn Prompt đó (Reference Count ).
- Khi các câu trả lời bắt đầu sinh ra các từ khác nhau, hệ thống mới kích hoạt cơ chế Copy-on-Write (Sao chép khi ghi): Chỉ cấp phát trang mới riêng biệt cho phần văn bản bị rẽ nhánh.
- Tiết kiệm tới dung lượng VRAM cho các tác vụ suy luận phân nhánh phức tạp.
- Khắc phục phân mảnh bộ nhớ:
❓ Câu hỏi 3: Giải thích nguyên lý toán học của kỹ thuật Speculative Decoding. Làm thế nào kỹ thuật này có thể tăng tốc độ sinh từ lên gấp 2–3 lần mà vẫn đảm bảo tính phân phối xác suất đầu ra giống hệt 100% so với mô hình lớn ban đầu (Lossless Acceleration)?
- Gợi ý trả lời:
- Ý tưởng hai bước: Sử dụng một mô hình nhỏ (Draft Model ) sinh nhanh một chuỗi tokens dự đoán: . Sau đó dùng mô hình lớn (Target Model ) kiểm chứng toàn bộ tokens này trong đúng một lần chạy duy nhất ở pha Prefill (Compute-Bound).
- Cơ chế Lấy mẫu chấp nhận/từ chối (Speculative Sampling):
- Tại mỗi bước , ta có xác suất dự đoán của mô hình nhỏ và mô hình lớn .
- Ta chấp nhận token từ mô hình nhỏ với xác suất:
- Nếu token được chấp nhận: Token đó chính thức được giữ lại.
- Nếu token bị từ chối: Ta loại bỏ toàn bộ các token dự đoán phía sau nó, đồng thời lấy mẫu lại một token thay thế trực tiếp từ phân phối hiệu chỉnh:
- Chứng minh tính không tổn hao (Lossless Guarantee):
- Xác suất thực tế một token được sinh ra theo thuật toán:
- Về mặt toán học, xác suất sinh ra token đúng bằng (phân phối xác suất gốc của mô hình lớn). Do đó, mô hình sinh ra kết quả hoàn toàn giống với việc chạy trực tiếp mô hình lớn 70B, không hề bị suy giảm chất lượng logic hay văn phong.
6. Tóm Tắt Ghi Nhớ Nhanh (Key Takeaways)
- Pha Prefill bị nghẽn bởi Năng lực tính toán (Compute-Bound / GEMM); Pha Decoding bị nghẽn bởi Băng thông bộ nhớ (Memory-Bandwidth Bound / GEMV).
- PagedAttention áp dụng cơ chế Bộ nhớ ảo (Virtual Memory Paging) lên KV Cache, giảm tỷ lệ lãng phí VRAM xuống dưới và tăng gấp thông lượng phục vụ.
- Continuous Batching lập lịch ở cấp độ từng lượt sinh từ (Iteration-level), triệt tiêu hoàn toàn thời gian chờ đợi lãng phí của Static Batching.
- Speculative Decoding dùng mô hình nhỏ đoán trước tokens để mô hình lớn kiểm chứng song song, tăng tốc với chất lượng được bảo toàn không tổn hao.
- Áp dụng Lượng tử hóa KV Cache (FP8) và Chunked Prefill để tối ưu hóa đồng thời dung lượng VRAM và độ mượt mà của hệ thống serving.