Hướng dẫn triển khai LLM inference với vLLM trên Linux: Tăng thông lượng và tiết kiệm VRAM

Artificial Intelligence tutorial - IT technology blog
Artificial Intelligence tutorial - IT technology blog

Bối cảnh & Tại sao cần tối ưu LLM inference?

Các mô hình ngôn ngữ lớn (LLM) như Llama, Mixtral hay GPT đã trở nên phổ biến. Chúng được ứng dụng rộng rãi từ trợ lý ảo, sáng tạo nội dung đến phân tích dữ liệu, mở ra nhiều tiềm năng. Tuy nhiên, việc triển khai LLM vào môi trường sản phẩm (production) không hề dễ, đặc biệt về hiệu năng.

Khi triển khai LLM inference, hai thách thức lớn nhất là độ trễ (latency) và thông lượng (throughput). Để người dùng có trải nghiệm tốt, mô hình cần phản hồi nhanh chóng. Hệ thống cũng phải xử lý nhiều yêu cầu cùng lúc, giúp tối ưu chi phí hạ tầng. Thêm vào đó, LLM thường tiêu tốn rất nhiều VRAM. Điều này gây khó khăn khi chạy nhiều mô hình hoặc xử lý batch lớn trên các GPU phổ thông.

Trước đây, mình cũng từng chật vật tối ưu LLM inference trong môi trường production, đặc biệt với các mô hình lớn và lượng yêu cầu cao. Làm sao để vừa giảm độ trễ, vừa tăng khả năng xử lý đồng thời, lại vừa tận dụng hiệu quả tài nguyên GPU là một bài toán khó. Các giải pháp truyền thống như batching tĩnh hay tối ưu kernel thường chỉ giải quyết được một phần nhỏ vấn đề.

Sau nhiều tìm tòi và thử nghiệm, vLLM cuối cùng đã chứng minh hiệu quả vượt trội. vLLM là một thư viện rất hiệu quả, được tạo ra để tăng tốc độ LLM inference nhờ thuật toán PagedAttention. Khác với cơ chế attention truyền thống cấp phát bộ nhớ liên tục, PagedAttention quản lý K/V cache theo từng page.

Cách này giống như hệ điều hành quản lý bộ nhớ ảo. Nhờ đó, vLLM giảm đáng kể việc cấp phát bộ nhớ kém hiệu quả, giúp đạt thông lượng cao hơn và tiết kiệm VRAM ấn tượng. Khi mình áp dụng vào môi trường production, kết quả rất ổn định. Nó giúp giảm chi phí hạ tầng và cải thiện đáng kể trải nghiệm người dùng.

Vậy, vLLM có những ưu điểm nào so với các giải pháp khác?

  • Thông lượng cao hơn: PagedAttention giúp vLLM xử lý nhiều yêu cầu đồng thời, ngay cả khi độ dài prompt và output khác nhau.
  • Độ trễ thấp hơn: Kỹ thuật continuous batching cho phép xử lý yêu cầu ngay lập tức, không cần chờ batch đầy đủ.
  • Tiết kiệm VRAM: Quản lý K/V cache hiệu quả giúp tận dụng tối đa bộ nhớ GPU.
  • Dễ sử dụng: Hỗ trợ nhiều mô hình phổ biến từ Hugging Face và có API thân thiện.

Trong bài viết này, mình sẽ chia sẻ kinh nghiệm triển khai vLLM trên Linux. Hy vọng bạn cũng có thể tối ưu hiệu năng LLM inference cho dự án của mình.

Cài đặt vLLM trên môi trường Linux

Để bắt đầu với vLLM, bạn cần một hệ thống Linux có NVIDIA GPU và driver CUDA đã cài đặt. Bài viết này giả định bạn đã có CUDA toolkit. Nếu chưa, hãy cài đặt theo hướng dẫn từ trang chủ NVIDIA.

1. Chuẩn bị môi trường Python

Nên sử dụng môi trường ảo Python để tránh xung đột thư viện.


# Tạo môi trường ảo
python3 -m venv vllm_env

# Kích hoạt môi trường
source vllm_env/bin/activate

2. Cài đặt vLLM

Bạn có thể cài đặt vLLM dễ dàng qua pip, với hai tùy chọn chính: từ PyPI (bản ổn định) hoặc từ source (bản mới nhất, nhưng có thể chưa ổn định hoàn toàn).

Cài đặt từ PyPI (Recommended)


pip install vllm

Nếu bạn muốn sử dụng các tính năng mới nhất hoặc có nhu cầu tùy chỉnh, có thể cài đặt từ source. Tuy nhiên, với đa số trường hợp, bản từ PyPI là đủ.

Cài đặt từ source (Tùy chọn)


git clone https://github.com/vllm-project/vllm.git
cd vllm
pip install -e .

Sau khi cài đặt xong, bạn có thể kiểm tra xem vLLM đã sẵn sàng chưa bằng cách chạy một lệnh đơn giản:


import vllm
print(vllm.__version__)

Nếu không có lỗi và phiên bản vLLM được hiển thị, bạn đã cài đặt thành công!

Cấu hình chi tiết và chạy vLLM server

Sau khi cài đặt, bạn cần khởi động vLLM như một server API. Điều này cho phép các ứng dụng khác dễ dàng gọi inference. vLLM cung cấp một giao diện tiện lợi để thực hiện việc này.

1. Khởi động API server cơ bản

Bạn có thể khởi động một server vLLM chỉ với vài tham số cơ bản. Ví dụ, để chạy một mô hình Llama 2 7B trên GPU của mình:


python -m vllm.entrypoints.api_server \
    --model meta-llama/Llama-2-7b-hf \
    --port 8000 --host 0.0.0.0

Giải thích các tham số:

  • --model meta-llama/Llama-2-7b-hf: Chỉ định tên mô hình từ Hugging Face Hub. vLLM sẽ tự động tải mô hình này nếu chưa có.
  • --port 8000--host 0.0.0.0: Thiết lập địa chỉ và cổng mà API server sẽ lắng nghe.

Mình thường theo dõi kỹ mục này để biết mô hình chiếm bao nhiêu VRAM, liệu có phù hợp với GPU đang dùng không.

2. Các tham số cấu hình quan trọng

Để tối ưu hơn, vLLM cung cấp nhiều tham số bạn nên nắm rõ:

--tensor-parallel-size (hoặc -tp)

Tham số này rất hữu ích khi bạn có nhiều GPU. Bạn có thể phân chia mô hình giữa chúng để tăng tốc độ hoặc chạy các mô hình lớn không vừa trên một GPU. Ví dụ, nếu bạn có 2 GPU:


python -m vllm.entrypoints.api_server \
    --model meta-llama/Llama-2-13b-hf \
    --tensor-parallel-size 2 \
    --port 8000 --host 0.0.0.0

Lúc này, mô hình Llama 2 13B sẽ được chia đều trên 2 GPU, mỗi GPU xử lý một phần các phép tính. Điều này giúp tăng tốc độ inference đáng kể, đặc biệt với các mô hình siêu lớn.

--gpu-memory-utilization

Tham số này rất quan trọng để kiểm soát lượng VRAM vLLM được phép dùng. Mặc định là 0.9 (90%). Nếu cần dành VRAM cho tác vụ khác hoặc chạy nhiều ứng dụng trên cùng GPU, bạn có thể giảm giá trị này. Ví dụ, chỉ sử dụng 80% VRAM:


python -m vllm.entrypoints.api_server \
    --model mistralai/Mistral-7B-Instruct-v0.2 \
    --gpu-memory-utilization 0.8 \
    --port 8000 --host 0.0.0.0

Mình thường điều chỉnh tham số này dựa trên kết quả theo dõi VRAM thực tế khi thử nghiệm. Đừng đặt quá thấp, vì điều đó có thể ảnh hưởng đến khả năng batching của vLLM và giảm thông lượng.

--dtype

Đây là kiểu dữ liệu của mô hình (ví dụ: float16, bfloat16, float32). Sử dụng float16 hoặc bfloat16 giúp tiết kiệm VRAM và tăng tốc độ tính toán, thường không làm ảnh hưởng nhiều đến độ chính xác.


python -m vllm.entrypoints.api_server \
    --model mistralai/Mistral-7B-Instruct-v0.2 \
    --dtype bfloat16 \
    --port 8000 --host 0.0.0.0

--max-model-len

Tham số này quy định độ dài tối đa của sequence (prompt + output) mà mô hình có thể xử lý. Nếu bạn biết trước các yêu cầu không vượt quá một độ dài nhất định, việc đặt tham số này sẽ giúp vLLM tối ưu bộ nhớ tốt hơn. Tuy nhiên, đừng đặt quá nhỏ, nếu không nó sẽ cắt bớt các response dài.

Cấu hình quantization (Lượng tử hóa)

Để tiết kiệm VRAM tối đa, bạn có thể sử dụng các mô hình đã được lượng tử hóa (quantized models) như AWQ hoặc GPTQ. vLLM hỗ trợ trực tiếp các mô hình này.


# Ví dụ với mô hình AWQ
python -m vllm.entrypoints.api_server \
    --model TheBloke/Mistral-7B-Instruct-v0.2-AWQ \
    --quantization awq \
    --port 8000 --host 0.0.0.0

Các mô hình lượng tử hóa cho phép bạn chạy những mô hình lớn hơn nhiều trên cùng một GPU, thậm chí trên GPU có ít VRAM hơn. Cá nhân mình từng chạy thành công Llama 2 70B AWQ trên card RTX 3090. Đây là một kết quả ấn tượng.

3. Gọi API từ Client

Khi server vLLM đã chạy, bạn có thể gửi các yêu cầu inference. API này tương thích với OpenAI API, mang lại sự tiện lợi lớn.

Sử dụng cURL


curl http://localhost:8000/v1/completions \
    -H "Content-Type: application/json" \
    -d '{ 
        "model": "meta-llama/Llama-2-7b-hf", 
        "prompt": "Viết một đoạn văn ngắn về lợi ích của việc học lập trình.", 
        "max_tokens": 100, 
        "temperature": 0.7
    }'

Sử dụng Python Client

Với Python, bạn có thể dùng thư viện openai hoặc httpx để tương tác dễ dàng:


import openai

client = openai.OpenAI(
    api_key="EMPTY", # vLLM không yêu cầu API key
    base_url="http://localhost:8000/v1"
)

response = client.completions.create(
    model="mistralai/Mistral-7B-Instruct-v0.2",
    prompt="Viết một câu chuyện ngắn về một chú mèo thám tử.",
    max_tokens=150,
    temperature=0.8,
)

print(response.choices[0].text)

Bạn cũng có thể sử dụng endpoint /v1/chat/completions nếu mô hình của bạn là chat model:


import openai

client = openai.OpenAI(
    api_key="EMPTY",
    base_url="http://localhost:8000/v1"
)

response = client.chat.completions.create(
    model="mistralai/Mistral-7B-Instruct-v0.2",
    messages=[
        {"role": "system", "content": "Bạn là một trợ lý AI hữu ích."},
        {"role": "user", "content": "Viết một câu chuyện ngắn về một chú mèo thám tử."}
    ],
    max_tokens=150,
    temperature=0.8,
)

print(response.choices[0].message.content)

Sử dụng API chuẩn OpenAI giúp mình dễ dàng chuyển đổi các ứng dụng từ OpenAI sang vLLM mà không cần sửa đổi nhiều code.

Kiểm tra hiệu năng và Monitoring

Khi server vLLM chạy ổn định, việc kiểm tra và theo dõi hiệu năng là bước không thể thiếu. Nó đảm bảo hệ thống hoạt động đúng như mong đợi.

1. Kiểm tra throughput và latency

vLLM cung cấp script benchmark tích hợp. Script này giúp bạn dễ dàng đo lường thông lượng và độ trễ. Đây là một cách hiệu quả để so sánh hiệu năng trước và sau khi tối ưu.


python -m vllm.benchmarks.benchmark_throughput \
    --model meta-llama/Llama-2-7b-hf \
    --dataset sharegpt \
    --num-prompts 1000

Tham số --dataset sharegpt sử dụng bộ dữ liệu prompt thực tế để mô phỏng tải. Bạn có thể thay đổi --num-prompts để kiểm tra với các số lượng yêu cầu khác nhau. Kết quả sẽ hiển thị các chỉ số như Prompt throughput (tokens/s), Generation throughput (tokens/s), và Latency.

Ngoài ra, bạn có thể tự viết script Python để gửi nhiều request đồng thời và đo thời gian phản hồi trung bình. Hoặc, hãy sử dụng các công cụ stress testing như ApacheBench (ab) hay Locust.

2. Theo dõi VRAM và tài nguyên hệ thống

Khi vLLM server đang chạy và xử lý yêu cầu, bạn nên thường xuyên theo dõi mức sử dụng VRAM của GPU. Lệnh nvidia-smi là lựa chọn phù hợp cho việc này:


nvidia-smi

Bạn sẽ thấy thông tin về tổng VRAM, VRAM đã sử dụng, và các tiến trình đang chiếm dụng VRAM. Hãy chú ý xem vLLM dùng bao nhiêu VRAM, liệu nó có vượt quá giới hạn --gpu-memory-utilization bạn đã đặt không. Nếu VRAM quá tải, bạn có thể cần giảm --gpu-memory-utilization, chuyển sang mô hình nhỏ hơn, hoặc dùng mô hình đã lượng tử hóa.

Đừng quên theo dõi CPU và RAM của hệ thống. Dù vLLM tối ưu GPU, một số tác vụ tiền xử lý hoặc hậu xử lý vẫn cần CPU. Mô hình cũng cần một lượng RAM nhất định để tải.

3. Tối ưu dựa trên kết quả Monitoring

  • Nếu thông lượng thấp:
    • Kiểm tra lại --gpu-memory-utilization. Nếu quá thấp, vLLM có thể không batch được nhiều request.
    • Xem xét tăng --max-num-seqs (số lượng sequence tối đa đồng thời) nếu bạn có đủ VRAM.
    • Đảm bảo rằng bạn đang sử dụng --dtype float16 hoặc bfloat16.
    • Nếu có nhiều GPU, hãy thử tăng --tensor-parallel-size.
  • Nếu VRAM đầy:
    • Giảm --gpu-memory-utilization.
    • Sử dụng mô hình lượng tử hóa (AWQ, GPTQ).
    • Chuyển sang một mô hình nhỏ hơn.
    • Nếu đang chạy multi-GPU, đảm bảo --tensor-parallel-size được cấu hình đúng.
  • Nếu độ trễ cao:
    • Kiểm tra tải trên server. Nếu quá nhiều request, server có thể bị quá tải.
    • Tối ưu prompt và output length.

Mình nhận thấy đây là một vòng lặp liên tục: cấu hình → kiểm tra → theo dõi → tinh chỉnh. Không có cấu hình “hoàn hảo” cho mọi trường hợp. Nó phụ thuộc rất nhiều vào mô hình, loại GPU và lượng tải dự kiến của bạn.

Lời kết

Triển khai LLM inference hiệu quả là yếu tố quyết định sự thành công của các ứng dụng AI hiện đại. vLLM, nhờ PagedAttention và continuous batching, đã chứng tỏ mình là một giải pháp hiệu quả để đạt thông lượng cao và tiết kiệm VRAM.

Qua những chia sẻ từ kinh nghiệm thực tế, mình hy vọng bạn đã hiểu rõ hơn về cách cài đặt, cấu hình và tối ưu vLLM trên Linux. Đừng ngần ngại thử nghiệm các tham số khác nhau để tìm ra cấu hình tối ưu nhất cho hệ thống của bạn. Chúc bạn thành công!

Share: