Tối ưu hóa tốc độ suy luận cho LLM với vLLM và Ollama trong môi trường Docker
Trong kỷ nguyên của trí tuệ nhân tạo, việc triển khai các mô hình ngôn ngữ lớn (LLM) tại chỗ (on-premise) hoặc trên máy chủ riêng đang trở thành nhu cầu cấp thiết của nhiều doanh nghiệp và kỹ sư phần mềm. Tuy nhiên, một trong những thách thức lớn nhất khi chạy các mô hình này không phải là chất lượng của đầu ra, mà là thời gian phản hồi (latency) và thông lượng (throughput). Hai công cụ nổi bật hiện nay giải quyết vấn đề này chính là vLLM, một framework được thiết kế đặc biệt cho việc suy luận hiệu suất cao, và Ollama, một công cụ đơn giản hóa việc quản lý và chạy các mô hình LLM. Bài viết này sẽ hướng dẫn các bạn cách kết hợp sức mạnh của cả hai để xây dựng một dịch vụ chat AI siêu tốc, tối ưu hóa việc sử dụng GPU thông qua cơ chế KV Cache của vLLM, đồng thời duy trì sự tiện lợi trong việc quản lý container của Ollama.
Giới thiệu về kiến trúc vLLM và Ollama
vLLM là một engine suy luận mã nguồn mở cho phép tăng tốc đáng kể quá trình tạo văn bản của LLM. Điểm khác biệt cốt lõi của vLLM so với các phương pháp truyền thống như Hugging Face Transformers hay PyTorch thuần túy nằm ở thuật toán PagedAttention. Thuật toán này cho phép vLLM quản lý bộ nhớ đệm (KV Cache) một cách hiệu quả như hệ điều hành quản lý bộ nhớ ảo, giúp giảm thiểu sự phân mảnh bộ nhớ GPU và cho phép các yêu cầu có độ dài ngữ cảnh lớn được xử lý đồng thời mà không bị tràn bộ nhớ. Ngược lại, Ollama nổi tiếng với sự đơn giản hóa cực đại. Nó đóng gói mô hình, các file cấu hình, và runtime (thường là llama.cpp hoặc vLLM tùy cấu hình) vào một hệ sinh thái duy nhất, giúp người dùng chỉ cần một lệnh ollama run là có thể tương tác với mô hình. Tuy nhiên, mặc định Ollama sử dụng llama.cpp (thường chạy trên CPU hoặc GPU với hiệu năng chưa tối ưu nhất cho workload lớn). Mục tiêu của hướng dẫn này là cấu hình Ollama để sử dụng vLLM làm backend inference engine, mang lại hiệu suất gấp nhiều lần cho các kịch bản sản xuất.
Chuẩn bị môi trường và cài đặt yêu cầu cơ bản
Trước khi đi vào chi tiết kỹ thuật, chúng ta cần đảm bảo môi trường phần cứng và phần mềm của bạn đã sẵn sàng. Bạn sẽ cần một máy chủ hoặc máy trạm có card đồ họa NVIDIA hỗ trợ tính năng CUDA. Để đạt được hiệu suất tốt nhất với vLLM, bộ nhớ VRAM trên card đồ họa là yếu tố quyết định quy mô mô hình bạn có thể chạy. Về phần mềm, hệ điều hành Linux (như Ubuntu 20.04 hoặc 22.04) là lựa chọn tối ưu. Bạn cần cài đặt Docker và Docker Compose để quản lý container, đồng thời cài đặt trình điều khiển NVIDIA (NVIDIA Driver) và NVIDIA Container Toolkit để Docker có thể truy cập vào phần cứng GPU.
Ngoài ra, hãy đảm bảo rằng bạn đã cài đặt Ollama trên hệ điều hành host của mình. Việc này cần thiết để bạn có thể tải xuống các mô hình và tương tác với API quản lý của Ollama trước khi đưa vào container. Nếu bạn chưa có Ollama, hãy sử dụng lệnh sau để cài đặt phiên bản mới nhất trên Linux:
curl -fsSL https://ollama.com/install.sh | sh
Sau khi cài đặt xong, bạn cần kiểm tra xem Docker có thể nhận diện được GPU của bạn hay không bằng cách chạy một container thử nghiệm nhỏ. Đây là bước quan trọng để xác minh rằng NVIDIA Container Toolkit đã được cấu hình đúng đắn.
Tải và chuẩn bị mô hình LLM
Để thực hiện bài hướng dẫn này, chúng ta sẽ sử dụng mô hình Llama 3 hoặc Llama 3.1, một trong những mô hình mã nguồn mở mạnh mẽ nhất hiện nay với khả năng thích ứng cao. Bạn cần tải mô hình này về trước thông qua Ollama trên host. Lưu ý rằng, khi chạy trong môi trường vLLM, Ollama sẽ không thực sự "chạy" mô hình mà đóng vai trò là cầu nối, nhưng việc tải model về trước giúp đảm bảo file weights sẵn sàng trong thư mục dữ liệu.
Hãy mở terminal và thực hiện lệnh sau để tải mô hình phiên bản 8B (phù hợp với card GPU có từ 16GB VRAM trở lên):
ollama pull llama3
Quá trình tải về này sẽ lưu trữ các file trọng số của mô hình vào thư mục ~/.ollama/models trên Linux. Trong bước tiếp theo, chúng ta sẽ map thư mục này vào trong container Docker để vLLM có thể đọc trực tiếp các file này mà không cần tải lại, giúp tiết kiệm băng thông mạng và thời gian khởi động.
Cấu hình container Docker cho vLLM
Đây là bước then chốt trong toàn bộ quy trình. Thay vì sử dụng container mặc định của Ollama, chúng ta sẽ tạo một container riêng biệt chạy engine vLLM, nhưng được cấu hình để lắng nghe trên cùng một cổng (thường là cổng 11434) mà Ollama thường dùng, hoặc tạo một dịch vụ riêng và cấu hình Ollama để trỏ vào dịch vụ này. Cách tiếp cận phổ biến và linh hoạt nhất hiện nay là chạy container vLLM độc lập và chỉ định đường dẫn mô hình đã tải trước đó.
Chúng ta sẽ sử dụng image Docker chính thức của vLLM. Trong container này, vLLM sẽ tự động khởi tạo một API Server tương thích với chuẩn của OpenAI hoặc LLM của riêng nó. Để đơn giản hóa việc quản lý, chúng ta sẽ sử dụng file cấu hình docker-compose.yml để khai báo các tham số cần thiết.
Đầu tiên, tạo một thư mục mới trên máy chủ của bạn, ví dụ llm-server, và tạo file docker-compose.yml với nội dung như sau. Lưu ý quan trọng: chúng ta sẽ mount thư mục ~/.ollama/models vào container để vLLM truy cập mô hình Llama 3 mà bạn đã tải ở bước trước.
version: '3.8'
services:
vllm-server:
image: vllm/vllm:latest
container_name: vllm-inference
ports:
- "8000:8000"
environment:
- HUGGING_FACE_HUB_TOKEN=${HUGGING_FACE_HUB_TOKEN:-}
- MODEL_NAME=llama3
volumes:
- ${HOME}/.ollama/models:/root/.ollama/models
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
command:
- --model=/root/.ollama/models/llama3
- --served-model-name=llama3
- --tensor-parallel-size=1
- --gpu-memory-utilization=0.9
restart: unless-stopped
Tuy nhiên, để tích hợp hoàn hảo với Ollama, có một cách tiếp cận tinh vi hơn. Ollama có khả năng tự động phát hiện và sử dụng vLLM nếu nó được cấu hình đúng trong một container đặc biệt gọi là "ollama vLLM server". Phương pháp này cho phép bạn vẫn dùng lệnh ollama run hoặc giao diện quen thuộc, nhưng backend thực thi lại là vLLM cực nhanh. Để làm được điều này, chúng ta cần sử dụng image ollama/ollama:latest nhưng cấu hình biến môi trường để nó khởi tạo vLLM thay vì llama.cpp mặc định.
Tuy nhiên, cách tiếp cận ổn định nhất và được cộng đồng khuyến nghị cho các kịch bản yêu cầu hiệu năng cao là chạy hai dịch vụ song song: Ollama đóng vai trò là "Client" và "Orchestrator" trên host (hoặc container riêng), và vLLM đóng vai trò là "Inference Engine" trong một container khác. Dưới đây là cấu hình Docker Compose tối ưu để chạy vLLM làm backend và cấu hình Ollama để trỏ vào nó:
version: '3.8'
services:
vllm:
image: vllm/vllm:latest
container_name: vllm-backend
ports:
- "8000:8000"
environment:
- MODEL_NAME=/models/llama3
volumes:
- ${HOME}/.ollama/models:/models
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
command:
- --model=/models/llama3
- --served-model-name=llama3
- --tensor-parallel-size=1
- --max-model-len=8192
- --served-model-name=llama3
ollama:
image: ollama/ollama:latest
container_name: ollama-client
ports:
- "11434:11434"
environment:
- OLLAMA_HOST=0.0.0.0
- OLLAMA_ORIGINS=*
- OLLAMA_KEEP_ALIVE=5m
volumes:
- ollama_data:/root/.ollama
depends_on:
- vllm
command:
- serve
restart: unless-stopped
volumes:
ollama_data:
Cấu hình trên có một điểm mấu chốt cần lưu ý: Ollama mặc định sẽ tự tải model nếu nó không tìm thấy. Để Ollama sử dụng vLLM backend, bạn cần cấu hình Ollama để trỏ đến API của vLLM thông qua biến môi trường OLLAMA_HOST hoặc cấu hình file .ollama/config.yaml (tùy phiên bản). Tuy nhiên, cách đơn giản nhất để tận dụng vLLM thông qua Ollama hiện nay là sử dụng image Ollama đã được build sẵn để tích hợp vLLM, hoặc cấu hình Ollama để sử dụng proxy. Trong thực tế, vLLM thường chạy độc lập và Ollama đóng vai trò là frontend giao tiếp với API vLLM. Vì vậy, hãy tập trung vào việc khởi động container vLLM đầu tiên.
Để khởi động cụm dịch vụ vLLM đã cấu hình ở trên (bản rút gọn chỉ tập trung vào vLLM để đảm bảo tính ổn định), hãy thực thi lệnh:
docker-compose up -d
Sau khi khởi động, bạn có thể kiểm tra trạng thái của container bằng lệnh docker ps để đảm bảo không có lỗi khởi động liên quan đến GPU hoặc thiếu file mô hình.
Khởi động dịch vụ và kiểm thử hiệu năng
Khi container vLLM đã chạy ổn định, bạn sẽ thấy nó đang lắng nghe trên cổng 8000. Lúc này, bạn có thể tương tác trực tiếp với API của vLLM hoặc cấu hình Ollama để trỏ vào đây. Nếu bạn muốn sử dụng giao diện CLI quen thuộc của Ollama để gọi các mô hình chạy trên vLLM, bạn cần cấu hình Ollama trên host để trỏ về IP của container vLLM. Tuy nhiên, một cách đơn giản hơn để kiểm chứng hiệu năng ngay lập tức là sử dụng lệnh curl để gọi trực tiếp API của vLLM.
Thực hiện lệnh sau để gửi một yêu cầu mẫu đến vLLM và đo thời gian phản hồi:
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "llama3",
"prompt": "Hãy giải thích ngắn gọn về định luật Newton thứ nhất.",
"max_tokens": 150,
"stream": false
}'
Kết quả trả về sẽ là một đối tượng JSON chứa nội dung câu trả lời. Điều quan trọng là hãy quan sát thời gian thực hiện. Nếu bạn so sánh kết quả này với việc chạy ollama run llama3 (sử dụng llama.cpp mặc định) trên cùng một phần cứng, bạn sẽ thấy vLLM thường cho tốc độ sinh token (tokens per second) cao hơn đáng kể, đặc biệt là khi xử lý các prompt dài hoặc nhiều yêu cầu cùng lúc. vLLM tối ưu hóa bộ nhớ GPU bằng kỹ thuật PagedAttention, cho phép nó lưu trữ các ngữ cảnh (context) hiệu quả hơn nhiều so với llama.cpp.
Nếu bạn muốn Ollama (chạy trên host) sử dụng vLLM (chạy trong container) như một backend, bạn có thể cấu hình Ollama thông qua biến môi trường OLLAMA_HOST để trỏ về container vLLM, nhưng điều này đòi hỏi vLLM phải hỗ trợ chính xác giao thức API của Ollama, điều mà vLLM mặc định chỉ hỗ trợ chuẩn OpenAI. Do đó, cách tiếp cận phổ biến trong sản xuất là: Ollama quản lý model và user interface, vLLM chạy riêng biệt và bạn viết một lớp proxy hoặc sử dụng các công cụ như llama-cpp-python cấu hình để gọi API vLLM. Tuy nhiên, với phiên bản Ollama mới nhất, tính năng "Modellib" cho phép đăng ký các endpoint ngoài. Bạn có thể thử nghiệm bằng cách tạo một file Modelfile trỏ đến endpoint của vLLM, nhưng để đơn giản và ổn định nhất, hãy giữ vLLM làm dịch vụ độc lập và truy cập qua API.
Lưu ý quan trọng về hiệu năng và bảo mật
Khi triển khai giải pháp này vào môi trường thực tế, có một số điểm cần đặc biệt lưu ý để đảm bảo hệ thống hoạt động bền vững. Thứ nhất, về quản lý bộ nhớ GPU: vLLM rất "đói" bộ nhớ. Tham số --gpu-memory-utilization=0.9 trong lệnh khởi động cho phép vLLM sử dụng 90% VRAM. Nếu bạn chạy nhiều mô hình cùng lúc hoặc có các ứng dụng khác sử dụng GPU (như render video hoặc đào coin), bạn cần giảm con số này xuống (ví dụ 0.8) để tránh bị lỗi Out Of Memory (OOM) khiến container bị sập đột ngột.
Thứ hai, về bảo mật: Container Docker mặc định có các hạn chế nhất định về quyền truy cập. Khi bạn map thư mục ~/.ollama/models vào container, hãy đảm bảo rằng chỉ có người dùng có quyền cần thiết mới có thể truy cập vào thư mục này. Ngoài ra, API Server của vLLM (lắng nghe trên cổng 8000) không có cơ chế xác thực mặc định. Nếu bạn phơi bày dịch vụ này ra Internet, hãy luôn đặt nó sau một Reverse Proxy (như Nginx hoặc Traefik) để thêm cơ chế xác thực (API Key, OAuth) và bảo vệ khỏi các cuộc tấn công DDoS hoặc truy cập trái phép.
Thứ ba, về tính tương thích của mô hình: Không phải tất cả các mô hình trong thư viện của Ollama đều được tối ưu hóa tốt cho vLLM. vLLM hoạt động tốt nhất với các mô hình được đóng gói dưới định dạng GGUF hoặc các model weights gốc từ Hugging Face được chuyển đổi sang định dạng vLLM. Nếu bạn gặp lỗi khi load một mô hình cụ thể, hãy thử tải lại mô hình đó từ nguồn chính thức của Hugging Face và chuyển đổi nó sang định dạng thích hợp, hoặc sử dụng các biến thể đã được cộng đồng xác nhận là tương thích với vLLM.
Kết luận
Việc kết hợp vLLM và Ollama trong một kiến trúc Docker không chỉ đơn thuần là nâng cấp phần mềm mà là một bước tiến về kiến trúc hệ thống cho các ứng dụng AI hiện đại. Ollama mang lại sự tiện lợi trong quản lý mô hình, tải về và phân phối, trong khi vLLM đóng vai trò là động cơ mạnh mẽ phía sau, đảm bảo hiệu suất suy luận ở mức cao nhất có thể. Bằng cách tách biệt hai thành phần này và chạy chúng trong các container Docker được cấu hình tối ưu, các kỹ sư phần mềm và sysadmin có thể xây dựng được các dịch vụ chatbot, trợ lý ảo hoặc hệ thống xử lý ngôn ngữ tự nhiên (NLP) có khả năng mở rộng, phản hồi nhanh và tận dụng tối đa phần cứng GPU.
Hướng dẫn này đã cung cấp cho bạn lộ trình từ việc chuẩn bị môi trường, tải mô hình, đến việc cấu hình Docker Compose để chạy vLLM và tương tác với nó. Hy vọng những kiến thức này sẽ giúp bạn tự tin triển khai các giải pháp AI mạnh mẽ cho doanh nghiệp hoặc dự án cá nhân. Trong tương lai, khi các mô hình ngày càng lớn và yêu cầu về tốc độ ngày càng khắt khe, việc hiểu rõ và vận hành các engine inference như vLLM sẽ là kỹ năng cốt lõi của mọi kỹ sư AI và DevOps.