5.5 Speculative decoding với vLLM thực chiến
Lấy vLLM v0.32.0 làm chuẩn, lần lượt khởi động Decode thường, N-gram, Draft độc lập và EAGLE-3; thu chỉ số chấp nhận từng request, nội dung đầu ra và độ trễ/throughput trên hai nhóm code/hội thoại; kiểm điều kiện gắn Suffix và MTP
Bốn mục trước đã tách “đoán rồi nghiệm thu” thành quy tắc sampling, nguồn ứng viên, mạng phụ và sổ hiệu năng. Giờ nối lại vào dịch vụ suy luận: cùng một Target, cùng một GPU, cùng một bộ request — bật speculative thì thực sự đổi gì? Chỉ có tỉ lệ chấp nhận mà không có baseline Decode thường, hoặc chỉ có Token/s mà không xem nội dung đầu ra, đều trả lời không được câu đó.
Mục này đưa một quy trình tái đo được: trước chạy baseline, rồi lần lượt N-gram, Draft độc lập và EAGLE-3; tách thống kê sửa code và hội thoại mở; ghi cùng lúc chấp nhận, đầu ra và hiệu năng. Lệnh theo tài liệu và mã nguồn vLLM v0.32.0. Thí nghiệm GPU cần chạy trên môi trường của bạn — bài không điền điểm số chưa đo.
⏰ Độ mới của thông tin: dựa trên vLLM v0.32.0 (phát hành 2026-10-18). Chỉ số chấp nhận từng request là giao diện thử nghiệm; trường có thể đổi theo phiên bản — hãy khóa phiên bản.
📑 Mục lục
- 1. Khóa môi trường và biến đối chứng
- 2. Chuẩn bị hai nhóm đầu vào code và hội thoại
- 3. Lần lượt khởi động baseline, N-gram và Draft
- 4. EAGLE-3, Suffix và MTP: kiểm artifact rồi mới cấu hình
- 5. Thu tỉ lệ chấp nhận: thấy mẫu số thật từng request
- 6. So hiệu năng: cùng khối lượng việc và đồng thời
- 7. Chất lượng và cách đọc kết quả
- 8. Gỡ lỗi và vòng tinh chỉnh tiếp
- Tóm tắt
- Checklist tự kiểm
- Tài liệu tham khảo
1. Khóa môi trường và biến đối chứng
1.1 Cài đặt và ghi phiên bản
Dùng môi trường Linux GPU mà vLLM hỗ trợ. Trước hết xác nhận tương thích driver, CUDA và Wheel theo tài liệu cài GPU. GPU memory phải chứa được trọng số Target, KV Cache, workspace và CUDA Graph; nhóm Draft cần thêm chỗ.
Cài trong môi trường tách biệt và lưu thông tin phiên bản:
python -m venv .venv
source .venv/bin/activate
python -m pip install 'vllm[bench]==0.32.0' # lệnh con bench cần dependency thêm
python -c "import torch, vllm; print('torch', torch.__version__, 'cuda', torch.version.cuda, 'vllm', vllm.__version__)"
nvidia-smi
mkdir -p results
python -m pip freeze > results/environment.txt
vllm serve --help > results/serve-help.txt
vllm bench serve --help > results/bench-help.txt
Đổi phiên bản khác thì trước hết đối chiếu tài liệu phiên bản đó về tên phương pháp và trường. Nút cấu hình speculative decoding đổi nhiều gần đây — ví dụ tên MTP riêng theo mô hình đã thống nhất thành mtp, và tham số cũ --speculative-model đã deprecated, dòng lệnh đổi sang --speculative-config (viết tắt -sc).
1.2 Chỉ đổi phương pháp đề xuất
Target thống nhất dùng Qwen/Qwen3-8B. Chọn vậy vì có sẵn ba đối tác: Qwen/Qwen3-0.6B cùng họ làm Draft độc lập (ví dụ chính thức trong tài liệu vLLM), RedHatAI/Qwen3-8B-speculator.eagle3 là trọng số EAGLE-3 huấn luyện cho nó, còn N-gram không cần trọng số thêm. Bốn nhóm thí nghiệm dùng chung một baseline.
| 📊 Mục cố định | Ví dụ này | Lý do |
|---|---|---|
| Target / dtype | Qwen3-8B, BF16 (độ chính xác gốc của checkpoint) | Không trộn biến lượng hóa |
| GPU / TP | Cùng một card, TP=1 | Không tính đổi số card thành lợi ích speculative |
| KV dtype | auto | Không chồng FP8 KV |
| Ngữ cảnh và lịch | Cùng trần độ dài và ngân sách Token | Điều kiện phục vụ so được |
| Prefix Cache | Tắt | Tránh request lặp hit KV làm nhiễu đối chứng |
| Sampling và template | Chỉ định tường minh, các nhóm giống nhau | Tránh mặc định lặng lẽ đổi khối lượng việc |
Qwen3 mặc định vào chế độ Thinking; ví dụ này thống nhất enable_thinking=False. Đó là để khống chế biến, và cũng khớp điều kiện huấn luyện trọng số EAGLE-3 này (model card ghi lúc huấn luyện tắt Thinking). Nếu nghiệp vụ cần bật Thinking, độ dài đầu ra và chấp nhận đều đổi — phải đo lại.
Thí nghiệm chính thức nên ghi revision từng mô hình; tốt nhất tải snapshot cố định về máy để tái đo.
2. Chuẩn bị hai nhóm đầu vào code và hội thoại
2.1 Sinh JSONL kiểm được
Lưu code dưới thành prepare_workloads.py. Chạy xong được hai nhóm, mỗi nhóm 64 đầu vào. Nhóm code bắt mô hình chỉ sửa một hàm, giữ nguyên phần còn lại; nhóm hội thoại yêu cầu giải thích chủ đề khác nhau, gần như không có đoạn gốc để chép.
import json
from pathlib import Path
Path("data").mkdir(exist_ok=True)
source = "\n\n".join(
f"def scale_{i}(value):\n return value * {i + 1}" for i in range(12)
)
topics = [
"giải thích vì sao dịch vụ suy luận phải vừa quan tâm throughput vừa quan tâm độ trễ",
"nêu các lý do có thể khiến tỉ lệ hit cache cao mà người dùng vẫn phải chờ",
"thảo luận đội ngũ chọn phương án triển khai dễ bảo trì thế nào",
"giải thích vì sao chỉ số trung bình có thể che vài thất bại nghiêm trọng",
]
for task in ["code", "chat"]:
rows = []
for i in range(64):
if task == "code":
prompt = (
f"Chỉ đổi hệ số của scale_{i % 12} thành {100 + i}; giữ nguyên mọi hàm khác. "
"Xuất cả file Python, không giải thích, cũng không hàng rào code.\n" + source
)
else:
prompt = f"Case {i}: Hãy viết hai đoạn văn đầy đủ để {topics[i % len(topics)]}."
rows.append({"prompt": prompt, "output_tokens": 128})
with open(f"data/{task}.jsonl", "w", encoding="utf-8") as f:
f.writelines(json.dumps(r, ensure_ascii=False) + "\n" for r in rows)
print("Đã sinh data/code.jsonl và data/chat.jsonl, mỗi file 64 dòng (demo quy trình)")
python prepare_workloads.py
sha256sum data/*.jsonl > results/input-sha256.txt
2.2 Hai nhóm dữ liệu này nói được gì
Đây là demo cơ chế. Nhóm code cố ý phóng đại đoạn có thể chép; không đại diện mọi sinh code. Nhóm hội thoại cũng không đại diện mọi ngôn ngữ, lĩnh vực và nhiệt độ. Đánh giá chính thức nên đổi sang mẫu nghiệp vụ thật đủ lớn, và mọi phiên bản dịch vụ phải đọc cùng một file.
Hai nhóm độ dài đầu vào khác nhau, nên so sánh luôn trong cùng một task: kết quả speculative của code so với baseline code, chat so với chat.
3. Lần lượt khởi động baseline, N-gram và Draft
3.1 Ở terminal A định nghĩa tham số chung
Ví dụ Bash dưới. Tham số chung bỏ vào một mảng; mỗi nhóm chỉ nối thêm cấu hình speculative:
TARGET_MODEL=Qwen/Qwen3-8B
COMMON=(
--served-model-name spec-demo
--host 127.0.0.1
--port 8000
--dtype bfloat16
--tensor-parallel-size 1
--kv-cache-dtype auto
--max-model-len 4096
--max-num-seqs 16
--max-num-batched-tokens 4096
--gpu-memory-utilization 0.85
--no-enable-prefix-caching
--generation-config vllm
--seed 42
--per-request-spec-decode-metrics summary
)
# Nhóm 1: Decode thường — không truyền --speculative-config
vllm serve "$TARGET_MODEL" "${COMMON[@]}"
Vài tham số làm gì: --generation-config vllm khiến dịch vụ bỏ qua tham số sampling mặc định kèm repo mô hình, mọi sampling do client chỉ định tường minh; --per-request-spec-decode-metrics summary gắn thống kê chấp nhận của request này vào mỗi response (mục 5).
Sau khi load mô hình, biên dịch và bắt CUDA Graph xong, kiểm ở terminal B:
curl http://127.0.0.1:8000/health
curl http://127.0.0.1:8000/v1/models
Thu xong baseline mục 5 và 6 thì ở terminal A nhấn Ctrl+C dừng dịch vụ, dùng nvidia-smi xác nhận GPU memory đã giải phóng. Các nhóm chạy tuần tự — đừng để nhiều dịch vụ tranh cùng một GPU.
3.2 Nhóm 2: N-gram
vllm serve "$TARGET_MODEL" "${COMMON[@]}" \
--speculative-config '{"method":"ngram","num_speculative_tokens":4,"prompt_lookup_min":2,"prompt_lookup_max":4}'
Cửa sổ tìm 2–4 Token; mỗi vòng đề xuất tối đa 4. Không đặt cửa sổ thì vLLM mặc định hai đầu đều 5. Bước không khớp thì không speculative, nên chú ý độ phủ ứng viên (mục 5.2 mục 7).
3.3 Nhóm 3: Draft độc lập
Dừng dịch vụ trước, xác nhận memory đã giải phóng, rồi khởi động:
vllm serve "$TARGET_MODEL" "${COMMON[@]}" \
--speculative-config '{"method":"draft_model","model":"Qwen/Qwen3-0.6B","num_speculative_tokens":4}'
Ví dụ chính thức dùng 5 ứng viên; ở đây lấy 4 để căn với nhóm N-gram. Log khởi động in cấu hình Draft và dung lượng KV Cache — ghi cùng dung lượng nhóm baseline để thấy Draft chiếm bao nhiêu chỗ.
--speculative-config chỉ lo cấu hình speculative. temperature, top_p và tham số sampling khác thuộc request phía client — đừng nhét vào JSON này.
📌 Điểm then chốt: mọi nhóm dùng cùng tên dịch vụ spec-demo, script client không cần sửa. Dịch vụ khởi động được chỉ nghĩa là cấu hình qua kiểm tra; đầu ra và hiệu năng vẫn phải kiểm từng mục.
4. EAGLE-3, Suffix và MTP: kiểm artifact rồi mới cấu hình
4.1 Nhóm 4: EAGLE-3
Trọng số EAGLE-3 phải khớp Target. Model card của RedHatAI/Qwen3-8B-speculator.eagle3 ghi verifier là Qwen/Qwen3-8B, nên gắn thẳng vào Target ví dụ này:
vllm serve "$TARGET_MODEL" "${COMMON[@]}" \
--speculative-config '{"method":"eagle3","model":"RedHatAI/Qwen3-8B-speculator.eagle3","num_speculative_tokens":3}'
Số ứng viên 3 lấy từ ví dụ vLLM trên model card; bảng độ dài chấp nhận trên card cũng cho thấy sau 3–4 cái lợi ích giảm rõ. Card còn ghi lúc huấn luyện tắt Thinking và nên dùng /v1/chat/completions — script thu ở mục 5 đúng hai điểm đó.
Đổi Target khác thì phải đổi trọng số EAGLE-3 tương ứng và chạy lại baseline Decode thường cho Target mới. method="eagle" tương ứng trọng số EAGLE thế hệ một; hai thế hệ không đổi tên method là dùng lẫn được.
4.2 Suffix Decoding
Suffix phụ thuộc Arctic Inference. Cài vẫn khóa phiên bản vLLM để dependency resolution không lặng lẽ đổi môi trường đối chứng:
python -m pip install 'vllm==0.32.0' arctic-inference
python -m pip check
python -m pip freeze > results/environment-suffix.txt
vllm serve "$TARGET_MODEL" "${COMMON[@]}" \
--speculative-config '{"method":"suffix","num_speculative_tokens":16,"suffix_decoding_max_cached_requests":0}'
suffix_decoding_max_cached_requests: 0 tắt cây toàn cục xuyên request, chỉ giữ lịch sử request hiện tại — demo dễ giải thích hơn. 16 là trần số ứng viên mỗi vòng; độ dài thực tế đổi động. Nếu xung đột phiên bản dependency, chọn tổ hợp tương thích theo hướng dẫn Arctic Inference và chạy lại baseline.
4.3 MTP: chỉ cho mô hình sẵn MTP
Qwen3-8B không có module MTP, nên MTP không nằm trong nhóm đối chứng ví dụ này. Với mô hình sẵn MTP, cấu hình đơn giản nhất:
vllm serve XiaomiMiMo/MiMo-7B-Base \
--speculative-config '{"method":"mtp","num_speculative_tokens":1}'
Khuyến nghị chính thức bắt đầu tinh chỉnh từ 1 ứng viên. Thí nghiệm kiểu này phải lấy Decode thường của chính mô hình đó làm baseline.
⚠️ Lưu ý: rejection_sample_method: "synthetic" cho ứng viên qua theo tỉ lệ chấp nhận đặt sẵn, dùng để ép tải giới hạn hệ thống khi chưa có Draft tốt. Nó không bảo toàn phân phối — chỉ cho kiểm hiệu năng, không được xuất hiện trong cấu hình production.
5. Thu tỉ lệ chấp nhận: thấy mẫu số thật từng request
5.1 Thống kê chấp nhận trong response
Bật --per-request-spec-decode-metrics summary thì request n=1 mang thống kê request này trong metrics.speculative_decoding. Các trường chính:
| 📊 Trường | Ý nghĩa |
|---|---|
num_spec_steps | Số vòng nghiệm thu — ở mục 5.2 |
num_draft_tokens | Tổng ứng viên đề xuất , đã trừ ứng viên bị ràng buộc structured output hủy |
num_accepted_draft_tokens | Tổng ứng viên chấp nhận , không gồm Token thưởng |
acceptance_histogram | Độ dài num_speculative_tokens + 1; phần tử là số vòng chấp nhận đúng ứng viên |
mean_acceptance_length |
Đặt detailed còn kèm per_step_accepted và per_step_drafted từng vòng, hợp quan sát phương án độ dài động như Suffix. Speculative bật nhưng request không đề xuất ứng viên nào thì trường vẫn có, histogram toàn 0; dịch vụ baseline không speculative nên trường là null.
5.2 Script thu
Lưu code dưới thành collect_acceptance.py. Nó dùng Chat API không streaming chạy bốn đề, lưu đầu ra và thống kê từng đề, rồi tổng hợp theo task:
import argparse
import json
import time
from pathlib import Path
from urllib.request import Request, urlopen
parser = argparse.ArgumentParser()
parser.add_argument("label", help="ví dụ baseline / ngram / draft / eagle3 / suffix")
args = parser.parse_args()
code = "def add(a, b):\n return a + b\n\ndef multiply(a, b):\n return a * b\n"
cases = [
{"id": "edit", "task": "code",
"prompt": "Đổi tên add thành subtract và thành phép trừ; giữ nguyên multiply. Chỉ xuất code đầy đủ, không hàng rào.\n" + code},
{"id": "copy", "task": "code", "prompt": "Xuất nguyên văn đoạn code sau, không giải thích hay hàng rào:\n" + code},
{"id": "explain", "task": "chat", "prompt": "Dùng hai đoạn văn giải thích khác biệt giữa throughput và độ trễ từng request trong dịch vụ suy luận."},
{"id": "tradeoff", "task": "chat", "prompt": "Đội ngũ phải đánh đổi giữa chi phí thấp và độ trễ ổn định. Hãy đưa một cách thảo luận có lý có lẽ."},
]
rows = []
for case in cases:
payload = {
"model": "spec-demo",
"messages": [{"role": "user", "content": case["prompt"]}],
"temperature": 0, "top_p": 1, "seed": 42, "n": 1,
"max_tokens": 256, "stream": False,
"chat_template_kwargs": {"enable_thinking": False},
}
req = Request("http://127.0.0.1:8000/v1/chat/completions",
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"})
start = time.perf_counter()
with urlopen(req, timeout=180) as resp:
result = json.load(resp)
choice = result["choices"][0]
spec = (result.get("metrics") or {}).get("speculative_decoding")
if args.label != "baseline" and spec is None:
raise RuntimeError("thiếu chỉ số chấp nhận: kiểm phiên bản, tham số khởi động và n=1")
rows.append({**case, "output": choice["message"]["content"],
"finish_reason": choice["finish_reason"], "usage": result.get("usage"),
"request_seconds": time.perf_counter() - start, "speculative": spec})
Path("results").mkdir(exist_ok=True)
Path(f"results/{args.label}-acceptance.json").write_text(
json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8")
for task in ["code", "chat"]:
stats = [r["speculative"] for r in rows if r["task"] == task and r["speculative"]]
C = sum(s["num_accepted_draft_tokens"] for s in stats)
D = sum(s["num_draft_tokens"] for s in stats)
R = sum(s["num_spec_steps"] for s in stats)
hist = [sum(col) for col in zip(*(s["acceptance_histogram"] for s in stats))]
print(task, {"C": C, "D": D, "R": R,
"acceptance_rate": C / D if D else None,
"mean_acceptance_length": 1 + C / R if R else None,
"histogram": hist})
5.3 Mỗi nhóm dịch vụ chạy một lần
# Chạy trong lúc dịch vụ tương ứng đang chạy
python collect_acceptance.py baseline
python collect_acceptance.py ngram
python collect_acceptance.py draft
python collect_acceptance.py eagle3
Bốn đề chỉ để kiểm quy trình thu. So sánh chính thức cần mẫu lớn hơn, độ dài so được, và warmup trước. Thời gian tường không streaming mà script ghi gồm Prefill và xếp hàng — không phải TTFT hay TPOT.
Khi tổng hợp, cộng , , trước rồi mới lấy tỉ số — đừng trung bình phần trăm từng request. Khi in None: không có mẫu số, khác với “tỉ lệ chấp nhận 0%”. Phần tử 0 của histogram (vòng không chấp nhận gì) thường giải thích rõ nhất vì sao nhóm chat lợi ích hạn chế.
5.4 Đối chiếu chéo với chỉ số tổng hợp phía server
Trong /metrics, vllm:spec_decode_num_drafts_total, vllm:spec_decode_num_draft_tokens_total, vllm:spec_decode_num_accepted_tokens_total lần lượt tương ứng tổng các trường từng request , , . vllm:spec_decode_num_accepted_tokens_per_pos_total mang nhãn position; đếm ở vị trí là số vòng chấp nhận ít nhất ứng viên — chia cho num_drafts_total ra tỉ lệ chấp nhận tích lũy theo vị trí, thấy ngay tốc độ suy giảm theo vị trí.
Đây là đếm tích lũy từ lúc process khởi động; dùng thì lấy hiệu hai snapshot trước/sau thí nghiệm. Tổng hợp phía server không tách code/chat; request warmup và n>1 cũng được tính. Request streaming để chỉ số từng request trong usage chunk cuối — cần stream_options.include_usage: true hoặc khởi động dịch vụ với --enable-force-include-usage.
6. So hiệu năng: cùng khối lượng việc và đồng thời
6.1 Ở terminal B chạy Benchmark
Dịch vụ hiện tại là nhóm nào thì đặt LABEL tương ứng, rồi chạy vòng dưới, lưu một kết quả cho mỗi tổ hợp “task × đồng thời”:
LABEL=baseline # sau khi đổi dịch vụ: ngram / draft / eagle3
for TASK in code chat; do
for CONCURRENCY in 1 8; do
vllm bench serve \
--backend openai \
--base-url http://127.0.0.1:8000 \
--endpoint /v1/completions \
--model spec-demo \
--tokenizer Qwen/Qwen3-8B \
--dataset-name custom \
--dataset-path "data/${TASK}.jsonl" \
--chat-template-kwargs '{"enable_thinking":false}' \
--custom-output-len 128 \
--num-prompts 64 \
--num-warmups 4 \
--max-concurrency "$CONCURRENCY" \
--request-rate inf \
--temperature 0 \
--top-p 1 \
--ignore-eos \
--seed 42 \
--percentile-metrics ttft,tpot,itl \
--metric-percentiles 50,95,99 \
--save-result \
--result-dir results \
--result-filename "${LABEL}-${TASK}-c${CONCURRENCY}.json"
done
done
Dataset custom ở phía client dùng Tokenizer của Target gắn Chat Template (ở đây tắt Thinking tường minh), rồi gửi văn bản đã format sang Completions API. Script thu mục 5 đi Chat API, server gắn template. Hai đường mục đích khác nhau, nhưng cấu hình template phải khớp.
6.2 Độ dài cố định chỉ dùng cho hiệu năng
--ignore-eos khiến mỗi request sinh đủ 128 Token, tránh các nhóm khối lượng việc khác nhau vì kết thúc tự nhiên khác. Cái giá là đầu ra có thể vượt phạm vi đáp án bình thường — nên lô văn bản này không dùng để đánh giá chất lượng.
64 request là điểm bắt đầu chạy thông được; P95/P99 trên mẫu nhỏ thế này không ổn định. Thí nghiệm chính thức cần thêm mẫu và số lần lặp, giữ JSON gốc, và đo thêm dưới tốc độ đến hữu hạn (--request-rate đặt số cụ thể).
6.3 Ghi theo task, đồng thời và cặp với baseline
| 📊 Mục ghi | baseline | Từng nhóm speculative |
|---|---|---|
| GPU, revision mô hình, phiên bản phần mềm và tham số sampling | Cố định | Giống; thêm ghi cấu hình speculative |
| Số Token vào/ra thực, số request thành công | Benchmark JSON | Benchmark JSON |
| Output Throughput, P95 TTFT/TPOT/ITL | Đo | Đo |
| Dung lượng KV Cache, trọng số thêm, số lần preempt | Log khởi động và chạy | Log khởi động và chạy |
| , , histogram chấp nhận | Không áp dụng | Thống kê theo task |
| Đầu ra sinh bình thường và lý do kết thúc | Lưu từng đề | Lưu từng đề |
Speculative chủ yếu cải TPOT và throughput đầu ra; TTFT chủ yếu phụ thuộc xếp hàng và Prefill. Một chunk streaming có thể chứa nhiều Token, nên hình dạng ITL khác nhóm baseline — mục 5.4 mục 7 giải thích lý do.
7. Chất lượng và cách đọc kết quả
7.1 Trước hết loại lỗi cấu hình rõ
So *-acceptance.json các nhóm: đề code có giữ hàm chưa sửa và sửa đúng không, đề hội thoại có kết thúc bình thường không. Khi finish_reason là "length", trước kiểm ngân sách đầu ra có thiếu không, rồi mới xét nguyên nhân khác.
Ví dụ này dùng greedy decoding, đầu ra các nhóm lẽ ra khớp. Có lệch thì khoanh: trước kiểm template và tham số sampling có giống không, rồi xét hình dạng Batch và lệch số do Kernel (mục 5.1 mục 8). Sampling ngẫu nhiên thì cùng Seed cũng không bảo đảm từng chữ giống — nên so phân phối chỉ số tác vụ.
7.2 Rồi làm đánh giá tác vụ độc lập
Đánh giá chất lượng chính thức phải tách khỏi dữ liệu huấn luyện Draft hoặc EAGLE, phủ tính đúng thực thi code, toán, đầu ra có cấu trúc, văn bản dài, hội thoại nhiều vòng, và khôi phục hành vi EOS/Stop bình thường. Báo cáo ghi rõ cách nghiệm thu (quy tắc chuẩn hay khác), và Target có lượng hóa không.
Chỉ lượng hóa Draft thì có thể giữ baseline Target gốc; lượng hóa Target hoặc đổi Target thì phải dựng lại baseline, tránh trộn vài thay đổi.
7.3 Đọc bốn kết quả phổ biến thế nào
- Nhóm code chấp nhận cao mà tăng tốc yếu: xem độ dài nghiệm thu, thời gian Draft, và baseline vốn đã nhanh chưa.
- N-gram chấp nhận cao nhưng nhiều bước không có ứng viên: xem histogram và ; báo cùng độ phủ ứng viên.
- Đồng thời 1 nhanh hơn, đồng thời 8 throughput giảm: tải cao speculative đang tranh sức tính với request khác, hoặc dung lượng KV nhỏ đi — cân nhắc Dynamic SD (mục 5.4 mục 3).
- TPOT trung bình tốt hơn, khoảng dừng streaming dài hơn: kiểm ITL P95/P99; trải nghiệm tương tác không chỉ do Token/s trung bình quyết.
Chốt cấu hình và mẫu sẽ so trước khi nhìn kết quả. Cấu hình không lợi ích và thất bại cũng giữ — chúng đánh dấu biên áp dụng của phương án.
8. Gỡ lỗi và vòng tinh chỉnh tiếp
8.1 Hiện tượng phổ biến và thứ tự kiểm
| 🔍 Hiện tượng | Kiểm trước | Rồi kiểm |
|---|---|---|
| Dịch vụ khởi động thất bại | Bản lưu --help có tham số đó không; tên phương pháp đúng chưa | Trọng số phụ có khớp Target; GPU memory có đủ |
Response thiếu speculative_decoding | Có gắn --per-request-spec-decode-metrics không; request có n=1 không | Request streaming có bật include_usage không |
| Tỉ lệ chấp nhận gần 0 | Từ vựng, Chat Template, công tắc Thinking có khớp điều kiện huấn luyện trọng số | Target có lượng hóa không; lượng hóa xong chấp nhận sụt thì trước xác nhận phiên bản — v0.32.0 đã sửa chấp nhận 0% trên checkpoint NVFP4 dòng DeepSeek-V4 |
| Chấp nhận cao mà không tăng tốc | Thời gian Draft, độ dài nghiệm thu, TPOT baseline | Có nhầm dùng synthetic hay chế độ ép tải khác không |
| Đồng thời tăng thì throughput giảm | Dung lượng KV, số lần preempt | Số ứng viên có quá lớn; cân nhắc phân dải theo đồng thời |
| Đầu ra greedy các nhóm không khớp | Template, tham số sampling, --generation-config | Hình dạng Batch và lệch số Kernel |
Một lần chỉ đổi một biến. Nếu cùng lúc đổi số ứng viên, mô hình và đồng thời thì kết quả tốt hay xấu đều khó giải thích.
8.2 Vòng sau chỉnh gì
- Số ứng viên: ở đồng thời cố định quét 1, 2, 3, 4…, vẽ đường cong TPOT và độ dài chấp nhận trung bình, tìm chỗ lợi ích bắt đầu tụt (mục 5.4 mục 3).
- Phân dải theo đồng thời: nếu đồng thời thấp và cao muốn độ dài tối ưu khác nhau, đặt
num_speculative_tokens_per_batch_sizetheo dải; dải tải cao có thể đặt 0. - Cửa sổ N-gram: nhìn cùng tỉ lệ chấp nhận và độ phủ ứng viên; cửa sổ rộng hơn phủ tăng, chấp nhận giảm là bình thường — nhìn TPOT dưới cả hai.
- Đổi phương pháp: nghiệp vụ chép nhiều so N-gram với Suffix trước; nội dung mới nhiều so Draft độc lập với EAGLE-3; mô hình sẵn MTP thì ưu tiên thử MTP.
✅ Tín hiệu có thể lên production: ở tải mục tiêu TPOT và throughput đều hơn baseline, đánh giá chất lượng qua, mất dung lượng chấp nhận được.
❌ Tín hiệu nên bỏ: chỉ có lợi ích ở đồng thời thấp hơn nhiều so với tải mục tiêu, hoặc chấp nhận cao mà thời gian end-to-end không cải.
📝 Tóm tắt
- Mọi nhóm speculative dùng chung một baseline Decode thường; chỉ đổi
--speculative-config; các dịch vụ chạy tuần tự. - Code và hội thoại thống kê tách; so sánh chỉ trong cùng một task.
- N-gram và Suffix không cần trọng số thêm; Draft độc lập muốn cùng họ, cùng từ vựng; trọng số EAGLE-3 phải khớp Target; MTP chỉ cho mô hình sẵn module.
--per-request-spec-decode-metricsgắn , , và histogram chấp nhận vào mỗi response; tổng hợp cộng trước rồi mới lấy tỉ số.- Kiểm hiệu năng dùng
--ignore-eoscố định khối lượng việc; phán chất lượng dùng đầu ra kết thúc bình thường — hai lô dữ liệu tách. - Kết quả đọc cùng dung lượng KV, preempt, P95/P99; cấu hình không lợi ích cũng ghi.
🎯 Checklist tự kiểm
- Liệt kê biến phải cố định trong thí nghiệm đối chứng và lý do từng cái
- Viết được
--speculative-configcho N-gram, Draft độc lập, EAGLE-3, Suffix và MTP - Giải thích vì sao trọng số EAGLE-3 không đổi Target khác dùng thẳng được
- Bật chỉ số chấp nhận từng request và đọc , , , histogram từ response
- Tổng hợp tỉ lệ chấp nhận và độ dài chấp nhận trung bình theo task, phân biệt với chấp nhận 0%
- Đối chiếu chéo thống kê từng request bằng hiệu snapshot
/metricstrước/sau - Dùng
vllm bench serveso baseline với nhóm speculative dưới cùng khối lượng việc và đồng thời - Giải thích vì sao
--ignore-eoschỉ hợp kiểm hiệu năng - Từ hiện tượng như “chấp nhận cao mà không tăng tốc”, “đồng thời cao throughput giảm” khoanh nguyên nhân
📚 Tài liệu tham khảo
- vLLM v0.32.0 Release Notes
- vLLM — cài GPU
- vLLM — Speculative Decoding
- vLLM — N-Gram Speculation
- vLLM — Draft Models
- vLLM — EAGLE Draft Models
- vLLM — Suffix Decoding
- vLLM — Multi-Token Prediction (MTP)
- vLLM — Per-Request Acceptance Metrics
- vLLM — Dynamic Speculative Decoding
- vLLM — CLI
vllm bench serve - vLLM v0.32.0 — hiện thực dataset Benchmark (CustomDataset)
- Hugging Face — RedHatAI/Qwen3-8B-speculator.eagle3
- Hugging Face — Qwen/Qwen3-8B
- Hugging Face — Qwen/Qwen3-0.6B
- Snowflake — Arctic Inference