Chạy model local: đừng bắt đầu bằng model
Một playbook triển khai inference local cho builder: thử nhỏ, đo đúng, đặt guardrail, rồi mới quyết định scale hay dừng.
Bụi Wire9 giờ tối, một tech lead nhắn mình: “Team đang tính kéo một con 27B 1-bit về chạy local, có nên không?” Câu hỏi nghe như hỏi mua GPU, nhưng thật ra là hỏi về quyền vận hành: ai chịu latency, ai debug kernel, ai giữ endpoint sống lúc demo thành production.
Điểm mình muốn chốt ngay: đừng xem local inference là lựa chọn model. Hãy xem nó là lựa chọn triển khai. Model chỉ là một loài trong khu rừng; nếu tầng tán CUDA, server API, cache, logging và tiêu chí dừng không sống nổi, loài đẹp mấy cũng thành cây cảnh để chụp màn hình.
Bài này là playbook cho builder muốn thử triển khai local LLM kiểu Bonsai-27B 1-bit qua PrismML fork của llama.cpp, có server OpenAI-compatible để app hiện có gọi vào gần như không đổi code.

Sơ đồ tóm tắt ý chính của bài viết.
Mục tiêu của buổi thử: trả lời 4 câu, không phải khoe prompt
Trong một buổi chiều hoặc tối, team bạn chỉ cần trả lời bốn câu:
- Máy có chạy được binary đúng không? Ở đây là CUDA-enabled build, vì định dạng Q1_0_g128 GGUF cần kernel phù hợp.
- Model có trả lời ổn qua CLI không? CLI smoke test giúp tách lỗi model khỏi lỗi server.
- Endpoint local có tương thích workflow hiện tại không? OpenAI-compatible nghĩa là client có thể gọi kiểu
/v1/completionshoặc chat API quen thuộc, không phải viết lại cả app. - Có dấu hiệu đáng scale không? Latency, token throughput, memory, lỗi timeout, và độ ổn định qua nhiều lượt gọi.
Nếu buổi thử không trả lời được bốn câu này, đừng nâng cấp prompt, đừng đổi model vội. Bạn đang thiếu đường quan sát hệ thống.
Checklist trước khi kéo model về
Trước khi download vài GB weights, mình sẽ bắt team tick checklist này:
- GPU NVIDIA được runtime nhìn thấy bằng
nvidia-smi. - Driver/CUDA tương thích với toolchain build.
- Có đủ disk cho file GGUF và build artifacts.
- Có repo inference đúng fork, vì không phải
llama.cppnào cũng hiểu quantization đặc thù. - Có một prompt smoke test ngắn, cố định, để so lần chạy này với lần chạy sau.
- Có log thời gian phản hồi, lỗi, memory, không chỉ nhìn câu trả lời bằng mắt.
Quantization là kỹ thuật nén trọng số model xuống ít bit hơn để giảm bộ nhớ và chi phí chạy. Với 1-bit, cái giá không chỉ nằm ở “model nhỏ hơn”, mà nằm ở việc bạn cần runtime biết cách giải mã định dạng đó. Trong case Bonsai-27B Q1_0_g128 GGUF, PrismML fork cung cấp CUDA kernels chuyên biệt. Bỏ qua chi tiết này giống như đem một loài bản địa sang vùng đất khác rồi quên kiểm tra đất và khí hậu.
Bước 1: khóa môi trường chạy
Đừng bắt đầu bằng pip install dài ngoằng. Bắt đầu bằng runtime.
nvidia-smi
nvcc --version || true
python --version
Nếu nvidia-smi không thấy GPU, dừng. Nếu bạn đang chạy Colab, máy dev, hoặc server nội bộ, ghi lại loại GPU và memory. Không cần benchmark hoành tráng ở bước này; chỉ cần biết tầng phần cứng có tồn tại.
Cài dependency tối thiểu:
python -m pip install -U huggingface_hub requests
Ở team thật, mình khuyên tạo file runbook.md ngay từ đầu:
## Local LLM Trial
- GPU:
- CUDA:
- Model file:
- llama.cpp fork commit:
- Build flags:
- Smoke prompt:
- Stop criteria:
Nghe hơi thủ công, nhưng tới lúc binary build được trên máy A mà chết trên máy B, bạn sẽ cảm ơn dòng commit đó.
Bước 2: build đúng binary, đừng cầu may với wheel có sẵn
Với model cần kernel đặc thù, dùng đúng fork là quyết định vận hành, không phải sở thích open-source.
git clone https://github.com/PrismML/llama.cpp prismml-llama
cd prismml-llama
cmake -B build -DGGML_CUDA=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j
Sau build, kiểm tra ít nhất ba executable:
ls build/bin/llama-cli build/bin/llama-server build/bin/llama-bench
Nếu thiếu llama-server, bạn chưa có đường phục vụ app. Nếu thiếu llama-bench, bạn thiếu dụng cụ đo. Nếu chỉ có CLI và một câu trả lời đẹp, đó mới là demo tầng thấp, chưa phải workflow.
Nguồn về TileLang nhắc mình một điều đáng giữ: performance không tự nhiên xuất hiện vì bạn có GPU. Kernel, memory layout, shared memory, register, autotuning — toàn bộ lớp này quyết định phần lớn cảm giác “nhanh hay ì”. Bạn không nhất thiết phải viết kernel bằng TileLang, nhưng phải biết khi nào mình đang phụ thuộc vào kernel do người khác viết.
Bước 3: smoke test bằng CLI trước khi mở server
Tải model GGUF từ Hugging Face theo repo bạn dùng, rồi chạy CLI với prompt cố định. Ví dụ cấu trúc lệnh:
MODEL=/path/to/bonsai-27b-q1_0_g128.gguf
./build/bin/llama-cli \
-m "$MODEL" \
-p "Viết 5 gạch đầu dòng kiểm tra trước khi deploy API local." \
-n 200 \
-ngl 99
-ngl là số lớp offload lên GPU; trong công việc, nó ảnh hưởng trực tiếp tới memory và tốc độ. Đừng copy giá trị của người khác rồi coi là chuẩn. Thử vài mức khác nhau nếu máy bạn giới hạn VRAM.
Tiêu chí qua bước này: model load được, sinh text không lỗi, thời gian phản hồi không bất thường, không crash vì memory.
Tiêu chí dừng: lỗi kernel, lỗi format GGUF, GPU memory không đủ dù đã giảm offload, hoặc output rác liên tục với prompt cơ bản. Dừng ở đây rẻ hơn debug qua HTTP.
Bước 4: mở endpoint local tương thích OpenAI
Khi CLI ổn, mới bật server:
./build/bin/llama-server \
-m "$MODEL" \
--host 0.0.0.0 \
--port 8000 \
-c 4096 \
-ngl 99
Context size là vùng ngữ cảnh model xử lý trong một lượt. Tăng context giúp nhét nhiều lịch sử hơn, nhưng thường kéo theo memory và latency. Đừng set thật lớn chỉ vì thấy tham số đó tồn tại.
Client Python tối giản:
import requests
BASE_URL = "http://localhost:8000/v1/chat/completions"
payload = {
"model": "local-bonsai-27b",
"messages": [
{"role": "system", "content": "Bạn là trợ lý kỹ thuật trả lời ngắn gọn."},
{"role": "user", "content": "Nêu 3 rủi ro khi chạy LLM local."}
],
"temperature": 0.2,
"stream": False
}
r = requests.post(BASE_URL, json=payload, timeout=120)
print(r.json())
Ví dụ cụ thể: giả sử app nội bộ của bạn đang gọi OpenAI API để tạo mô tả ticket. Nếu endpoint local giữ shape tương thích, bạn chỉ cần đổi base_url, thêm config model, rồi chạy lại test suite. Đây là lợi ích thật: giảm chi phí tích hợp, không phải hứa hẹn model sẽ thông minh hơn.
Guardrail: đo như operator, không đo như người xem demo
Trong lần thử đầu, mình sẽ log tối thiểu:
| Hạng mục | Vì sao cần đo | Dấu hiệu nên dừng |
|---|---|---|
| Time to first token | Người dùng cảm nhận độ chờ | Chậm thất thường qua các request giống nhau |
| Tokens/second | Ước lượng capacity | Không đủ cho concurrency mục tiêu |
| GPU memory | Biết còn chỗ cho context/cache | Sát trần ngay ở prompt ngắn |
| Error rate | Nhìn độ ổn định | Timeout/crash sau vài lượt |
| Output sanity | Kiểm tra chất lượng tối thiểu | Trả lời lệch vai trò hoặc lặp vô nghĩa |
KV cache là bộ nhớ lưu key-value attention của các token trước đó để sinh tiếp nhanh hơn. Quantized KV cache có thể giảm memory, nhưng phải test chất lượng và lỗi dài ngữ cảnh. Speculative decoding là cách dùng một model nhỏ đoán trước token để model lớn xác nhận, có thể tăng tốc nhưng thêm độ phức tạp vận hành.
Nói gọn cho người phải trực pager: chỉ bật tối ưu khi bạn có metric trước và sau. Không có baseline thì mọi tối ưu đều là kể chuyện.
Khi nào scale, khi nào dừng?
Scale nếu:
- Endpoint local chạy ổn qua workload đại diện, không chỉ một prompt.
- Latency nằm trong ngưỡng sản phẩm chấp nhận được.
- App hiện tại chuyển sang endpoint mới với thay đổi nhỏ.
- Team có người chịu trách nhiệm build, upgrade, rollback.
- Lý do local rõ ràng: privacy, offline, chi phí dài hạn, hoặc kiểm soát latency.
Dừng nếu:
- Bạn cần chất lượng reasoning cao hơn mức model local đang cho.
- Workload có traffic thất thường nhưng team không muốn vận hành autoscaling.
- Mỗi lần đổi driver/build là một cuộc chữa cháy.
- Không có ai đọc log sau khi demo xong.
Bài về Reachy Mini chạy speech-to-speech local cho thấy một hướng rất thực tế: local không chỉ là LLM, mà là cả cascade VAD → STT → LLM → TTS. VAD là phát hiện đoạn có giọng nói, STT là speech-to-text, TTS là text-to-speech. Khi pipeline nhiều mắt xích, LLM local chỉ là một tầng trong chuỗi thức ăn; nghẽn ở STT hay TTS thì người dùng vẫn thấy chậm.
Với RAG cũng vậy. Nguồn Qdrant về filtered vector search nhắc một failure mode hay bị quên: filter metadata có thể làm graph tìm kiếm bị đứt đoạn. Nếu local LLM của bạn trả lời sai vì retrieval kém, đổi sang model 1-bit hay 4-bit không sửa được gốc rễ.
Thứ bạn nên nghĩ khác sau bài này
Trước đây, câu hỏi thường là: “Model local này có chạy được không?”
Sau bài này, câu hỏi nên đổi thành: “Workflow local này có vận hành được không, và tiêu chí dừng của mình là gì?”
Một buổi thử tốt không cần kết thúc bằng quyết định scale. Nó chỉ cần giúp team bớt mù: biết binary nào chạy, endpoint nào sống, metric nào đáng tin, và rủi ro nào không nên mang vào production.
Chạy model local giống chăm một khoảnh rừng nhỏ: đừng chỉ ngắm cây cao nhất, hãy xem đất, nước, ánh sáng và mấy con sâu trong log.
---
Bụi Wire — nghiện đọc release notes lúc 2 giờ sáng
Nguồn tham khảo
- Deploying a 1-Bit Bonsai-27B Model with PrismML llama.cpp and OpenAI-Compatible Local Inference Workflows - MarkTechPost
- Designing High-Performance GPU Kernels with TileLang: Tensor-Core GEMM, Fused Softmax, FlashAttention, and Autotuning - MarkTechPost
- FAIRChem v2 UMA for Multidomain Atomistic Simulation across Molecules, Catalysts, Materials, Vibrations, and Molecular Dynamics - MarkTechPost
- Reachy Mini goes fully local
- Filtered Vector Search: What ACORN Fixes, and What Fixes ACORN - Qdrant