Làm chủ llama.cpp và GGUF: Quantize LLM từ Hugging Face cho phần cứng cục bộ

AI tutorial - IT technology blog
AI tutorial - IT technology blog

Bối cảnh & Lý do

Sáu tháng trước, đội ngũ bảo mật của chúng tôi đã đặt ra một ranh giới nghiêm ngặt: không một dòng mã nguồn khách hàng hay dữ liệu telemetry nội bộ nào được phép rời khỏi mạng riêng. Điều đó đồng nghĩa với việc loại bỏ hoàn toàn các hosted API như OpenAI và Anthropic. Chúng tôi buộc phải chạy các mô hình 8B và 14B đủ mạnh ngay trên máy trạm của lập trình viên và máy chủ on-premise sẵn có. Và gần như ngay lập tức, chúng tôi đụng phải rào cản VRAM.

Một mô hình 8B 16-bit (FP16/BF16) chưa quantize đòi hỏi khoảng 16 GB VRAM chỉ để chứa trọng số gốc. Khi cấp phát thêm KV cache cho context window 8k, bạn sẽ tiêu tốn thêm 1,2 GB nữa. Nếu thử chạy mức cấu hình này trên một chiếc RTX 3060 hoặc 4070 12 GB tiêu chuẩn, hệ thống sẽ sập ngay vì lỗi Out-of-Memory (OOM) trước cả khi sinh ra được một token đầu tiên.

Việc tải về các file GGUF đã được quantize sẵn từ cộng đồng trên Hugging Face thoạt nhìn rất hấp dẫn. Ban đầu chúng tôi cũng làm vậy, nhưng nó đã phản tác dụng trên môi trường staging. Các bản quantize của cộng đồng thường lọt qua khâu kiểm soát phiên bản với chat template lỗi thời, token BOS/EOS bị lỗi, hoặc sử dụng ma trận quantization chung chung làm suy giảm perplexity trên codebase độc quyền của chúng tôi. Việc tự mình quantize từ file safetensors gốc chỉ mất mười phút mà lại mang lại toàn quyền kiểm soát đối với file nhị phân đầu ra.

Định dạng GGUF giúp hiện thực hóa điều này. Khác với GGML cũ, GGUF đóng gói tokenizer, kiến trúc hyperparameter và các tensor vào một file nhị phân tự mô tả duy nhất. Nhờ cơ chế ánh xạ trực tiếp vào bộ nhớ qua mmap, bạn sẽ có được thời gian tải gần như tức thì cùng khả năng offload từng layer mượt mà giữa RAM hệ thống và VRAM của GPU.

Cài đặt

Bạn cần chuẩn bị hai phần trên máy build: các file nhị phân C/C++ đã được biên dịch của llama.cpp và một môi trường Python cô lập để chuyển đổi metadata.

1. Build llama.cpp từ mã nguồn

Hãy biên dịch trực tiếp các file nhị phân trên phần cứng mục tiêu để trình biên dịch tận dụng được tập lệnh gốc của CPU (như AVX-512 hoặc ARM Neon).

# Clone repository llama.cpp
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp

# Cấu hình bằng CMake (Bật CUDA cho GPU Nvidia)
cmake -B build -DGGML_CUDA=ON -DCMAKE_BUILD_TYPE=Release

# Build các file nhị phân bằng tất cả core CPU
cmake --build build --config Release -j $(nproc)

Trên Apple Silicon, hãy đổi -DGGML_CUDA=ON thành -DGGML_METAL=ON để biên dịch tương thích với Metal framework.

2. Thiết lập môi trường chuyển đổi Python

Script chuyển đổi sẽ phân tích các file safetensors từ Hugging Face, đọc cấu hình tokenizer và đóng gói lại thành container GGUF FP16 chưa nén. Hãy cô lập các dependency này trong một virtual environment riêng biệt:

python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
# Cài đặt các dependency cần thiết cho việc chuyển đổi GGUF
pip install -r requirements/requirements-convert_hf_to_gguf.txt
pip install huggingface_hub

3. Tải trọng số mô hình gốc

Tải các file safetensors chính thức trực tiếp từ upstream. Trong bài hướng dẫn này, chúng ta sẽ chuyển đổi mô hình Llama-3.1-8B-Instruct của Meta:

huggingface-cli download meta-llama/Llama-3.1-8B-Instruct \
  --local-dir ./models/Llama-3.1-8B-Instruct \
  --local-dir-use-symlinks False

Cấu hình & Quantization

Quá trình quantization diễn ra qua hai giai đoạn. Đầu tiên, giải nén và chuyển đổi các trọng số phân tán thành container GGUF FP16 chưa nén. Thứ hai, quantize các trọng số thành biểu diễn số nguyên với độ sâu bit mong muốn.

Bước 1: Chuyển đổi Safetensors sang GGUF chuẩn (FP16)

Chạy công cụ chuyển đổi trên thư mục mô hình đã tải về:

python3 convert_hf_to_gguf.py ./models/Llama-3.1-8B-Instruct \
  --outfile ./models/llama-3.1-8b-fp16.gguf \
  --outtype f16

Lệnh này sẽ tạo ra một file trung gian nặng khoảng 16,1 GB. Đừng xóa file này vội. Nó sẽ đóng vai trò là bản master sạch để tạo ra nhiều mức quantization khác nhau.

Bước 2: Chọn phương pháp Quantization mục tiêu

Phiên bản llama.cpp hiện đại sử dụng “k-quants” (k-quantization), áp dụng độ chính xác hỗn hợp (mixed precision) trên các layer khác nhau thay vì cắt giảm đồng loạt mọi trọng số. Các layer quan trọng—như token embedding và attention output—sẽ được giữ độ chính xác bit cao hơn so với các tensor feed-forward ít nhạy cảm hơn.

  • Q8_0 (~8,5 GB): Giữ lại 99,9% perplexity. Hãy chọn mức này nếu bạn có từ 16 GB VRAM trở lên và cần kết quả gần như không suy hao cho các tác vụ chuyên sâu như phân tích pháp lý.
  • Q5_K_M (~5,7 GB): Lựa chọn mặc định được khuyến nghị cho máy trạm lập trình viên. Mức này bảo toàn khả năng viết code phức tạp và suy luận từng bước, đồng thời giảm tới 65% dung lượng ban đầu.
  • Q4_K_M (~4,9 GB): Tiêu chuẩn thực tế cho các dòng card phổ thông. Mức này nằm vừa vặn trên GPU 8 GB VRAM, để trống khoảng 3 GB cho context window 8k.
  • Q3_K_M (~4,0 GB): Nén tối đa. Phù hợp với phần cứng rất hạn chế, nhưng bạn sẽ nhận thấy lỗi cú pháp khi sinh JSON nghiêm ngặt và bị sót các tham số khi function calling.

Bước 3: Chạy llama-quantize

Truyền file master FP16 và mức quantization mong muốn vào công cụ quantize đã biên dịch:

# Tạo bản quant 4-bit trung bình
./build/bin/llama-quantize ./models/llama-3.1-8b-fp16.gguf \
  ./models/llama-3.1-8b-instruct-Q4_K_M.gguf Q4_K_M

# Tạo bản quant 5-bit trung bình với độ chính xác cao hơn
./build/bin/llama-quantize ./models/llama-3.1-8b-fp16.gguf \
  ./models/llama-3.1-8b-instruct-Q5_K_M.gguf Q5_K_M

Trên CPU 12 nhân hiện đại như Ryzen 9 7900X, quá trình này chỉ mất chưa đầy 90 giây. Sau khi các file quant được tạo xong, bạn có thể yên tâm xóa file FP16 16 GB để giải phóng dung lượng ổ đĩa cục bộ.

Kiểm thử & Đo hiệu năng (Benchmarking)

Đừng bao giờ đưa một mô hình mới quantize thẳng lên production mà chưa kiểm tra định dạng token và độ ổn định bộ nhớ khi chạy tải liên tục.

1. Kiểm tra chức năng cơ bản (Sanity Check)

Xác nhận mô hình xử lý chính xác các instruction tag, stop token và system prompt:

./build/bin/llama-cli \
  -m ./models/llama-3.1-8b-instruct-Q4_K_M.gguf \
  -p "<|start_header_id|>system<|end_header_id|>
Bạn là một kỹ sư Linux kernel.<|eot_id|><|start_header_id|>user<|end_header_id|>
Giải thích vì sao epoll có khả năng mở rộng tốt hơn select trong hai câu.<|eot_id|><|start_header_id|>assistant<|end_header_id|>" \
  -n 128 \
  -ngl 99 \
  --temp 0.2

Quan sát log terminal trong quá trình khởi tạo. Cờ -ngl 99 yêu cầu engine đẩy toàn bộ các layer lên GPU. Hãy đảm bảo llm_load_tensors báo cáo toàn bộ 33 layer đã được offload sang CUDA hoặc Metal.

2. Đo thông lượng (Throughput) với llama-bench

Bỏ qua các ước tính cảm tính. Hãy đo đạc chính xác tốc độ xử lý prompt (PP) và sinh văn bản (TG) dưới các cấu hình batch được kiểm soát:

./build/bin/llama-bench \
  -m ./models/llama-3.1-8b-instruct-Q4_K_M.gguf \
  -p 512,2048 \
  -n 128 \
  -ngl 99

Trên card RTX 4070 (12 GB VRAM), mức baseline của chúng tôi cho Llama-3.1-8B-Instruct Q4_K_M đạt 2.140 token/giây đối với quá trình nạp prompt (PP 512) và 104 token/giây khi sinh văn bản (TG 128). Tổng mức cấp phát VRAM duy trì ổn định ở mức 5,4 GB, để lại nhiều không gian cho môi trường phục vụ đa người dùng đồng thời.

3. Những bẫy vận hành cần theo dõi

Một mô hình vượt qua prompt thử nghiệm nhanh vẫn có thể gặp sự cố khi chịu tải liên tục. Hãy lưu ý ba vấn đề thực tế sau:

  • Phình bộ nhớ KV Cache: Khi các cuộc trò chuyện kéo dài tới 16k token, chỉ riêng KV cache đã có thể chiếm tới 2,5 GB VRAM. Nếu tràn sang bộ nhớ RAM hệ thống, thông lượng của bạn sẽ sụt giảm nghiêm trọng từ hơn 100 token/giây xuống còn mức một chữ số. Hãy cân nhắc bật tính năng quantize KV cache 8-bit (-ctk q8_0 -ctv q8_0) để giảm một nửa mức tiêu hao này.
  • Suy giảm Perplexity: Nếu bạn quantize các mô hình fine-tune chuyên biệt (chẳng hạn như mô hình y tế hoặc SQL), hãy đo perplexity bằng ./build/bin/llama-perplexity trên tập dữ liệu kiểm thử (validation set) của miền đó. Đừng bao giờ mặc định rằng bản Q4_K_M thông thường sẽ bảo toàn tốt các thuật ngữ ngách mà không qua kiểm thử.
  • Bóp hiệu năng do nhiệt (Thermal Throttling): Các máy trạm nhỏ gọn có luồng khí thùng máy kém thường bị giảm 15% đến 25% xung nhịp GPU khi chạy các tác vụ batch liên tiếp. Hãy kiểm tra xung nhịp và biểu đồ quạt tản nhiệt bằng lệnh nvidia-smi dmon.

Xây dựng pipeline quantization riêng đòi hỏi rất ít công sức, giúp bảo vệ an toàn ranh giới dữ liệu và đảm bảo mọi mô hình chạy trên phần cứng doanh nghiệp đều đáp ứng chính xác các yêu cầu về độ trễ và độ chuẩn xác của bạn.

Share: