Đội nào mình nói chuyện năm nay cũng mở cùng một file Excel: một cột “chất lượng model”, một cột “hóa đơn GPU hàng tháng”, và một sếp hỏi tại sao không thể có cả hai theo hướng mình muốn. AWS và Unsloth vừa công bố loạt pattern triển khai biến cái trade-off đó từ lý thuyết thành con số cụ thể — sau khi chạy thử một pattern trong số đó trên model 8B fine-tune thật tuần này, mình muốn phân tích các con số đó thực sự có ý nghĩa gì khi bạn là người cầm pager lúc nửa đêm.

Trick cốt lõi: quantize động, không đồng đều

Quantize thông thường lấy mọi layer của model và giảm về cùng một bit-width — ví dụ 4-bit toàn bộ — và chịu thiệt hại chất lượng đồng đều, dù một số layer (attention projection, layer embedding đầu) nhạy cảm với việc mất precision hơn hẳn các layer khác (block FFN phía sau). Unsloth Dynamic chạy phân tích độ nhạy theo từng layer trước, rồi phân bổ precision không đồng đều: layer nhạy cảm giữ gần 16-bit, phần còn lại nén mạnh. Theo Daniel Han, kết quả là model giảm khoảng 86% kích thước mà chỉ mất khoảng 14% độ chính xác khi eval — một sự bất đối xứng mà quantize 4-bit thuần túy không đạt được.

Cụ thể, model 8B tham số đi từ ~16GB ở BF16 xuống còn ~5GB ở GGUF 4-bit. Đó là con số thực sự quan trọng cho việc lập kế hoạch hạ tầng, vì nó quyết định bạn có fit vừa instance g5.xlarge giá $1.41/giờ thay vì g5.12xlarge giá $7.09/giờ hay không — chênh lệch khoảng 5 lần chi phí cho workload mà, trong test nội bộ của mình, vẫn ổn về latency với mọi thứ trừ batch job context dài.

Export model

Có hai đường export, và chọn sai là lỗi phổ biến nhất mình thấy khi tái hiện lại quy trình này:

# File GGUF đơn — để serve bằng runtime họ llama.cpp
model.save_pretrained_gguf(
    "gguf_model", tokenizer, quantization_method="q4_k_xl"
)

# Safetensors đã merge — để serve bằng vLLM/SGLang/HF-native
model.save_pretrained_merged(
    "finetuned_model", tokenizer, save_method="merged_16bit"
)

GGUF và safetensors đã merge không thể hoán đổi cho nhau lúc deploy — GGUF chỉ có ý nghĩa khi bạn đã chốt dùng runtime họ llama.cpp. Nếu đội bạn chuẩn hóa dùng vLLM để tối ưu batching, export GGUF là ngõ cụt bạn sẽ phát hiện sau khi build container, chứ không phải trước. Hãy chốt runtime trước, rồi mới export.

Bốn pattern, bốn tư thế vận hành rất khác nhau

#Hạ tầngRuntimeThực sự dành cho ai
1EC2llama.cpp / llama-serverĐội nhỏ, prototype, ai muốn có SSH access khi nó hỏng lúc 2 giờ sáng
2SageMaker custom containerllama.cpp trong containerĐội muốn autoscaling managed nhưng chưa sẵn sàng bỏ sự đơn giản của llama.cpp
3SageMaker LMIvLLM / SGLangTraffic production, GPU efficiency quan trọng, có concurrency thật
4EKS / ECSBất kỳ stack containerized nàoBạn đã chạy inference cạnh các service khác và không muốn thêm một lớp orchestration

Pattern 1 là nơi mình sẽ bắt đầu cho bất cứ thứ gì phục vụ nội bộ — model phân loại ticket support, reranker tìm kiếm nội bộ. Chỉ cần một binary và một flag:

llama-server --model /models/my-model.gguf --ctx-size 8192 --host 0.0.0.0 --port 8080

rồi nó trở thành endpoint tương thích OpenAI:

client = OpenAI(base_url="http://<ip>:8080/v1", api_key="not-required")
response = client.chat.completions.create(model="my-model", messages=[...])

Pattern 3 là nơi mình sẽ đẩy mọi thứ hướng khách hàng, vì container LMI có sẵn rolling-batch scheduling, điều quan trọng ngay khi concurrency lên đến hai chữ số:

HF_MODEL_ID = "Qwen/Qwen3-VL-8B-Instruct"
OPTION_DTYPE = "bf16"
OPTION_ROLLING_BATCH = "vllm"
OPTION_TENSOR_PARALLEL_DEGREE = "4"

Những gì con số benchmark không nói cho bạn

Mình đã triển khai hai model quantize trong năm nay, và cả hai lần vấn đề không nằm ở chất lượng — mà là độ trôi vận hành chỉ lộ ra khi có tải:

  1. Trôi prompt format là kẻ giết người thầm lặng. Nếu template prompt lúc fine-tune và chat template lúc serve lệch nhau dù chỉ một token khoảng trắng, chất lượng giảm theo cách trông y hệt như mất mát do quantize. Hãy pin template trong version control cạnh model weights, không để trong application code nơi ai đó sẽ “tiện tay” chỉnh sửa sau này.
  2. Benchmark toàn bộ hình dạng triển khai, không chỉ model đơn lẻ. Một model quantize nhanh ở ctx-size 2048 và concurrency một request có thể rớt thảm ở ctx-size 8192 và 20 request đồng thời — KV cache, chứ không phải weights, mới là nút thắt bộ nhớ. Hãy load-test đúng hình dạng bạn sẽ chạy production trước khi chốt kích thước instance.
  3. S3 luôn là nguồn chân lý. Đừng để artifact đã quantize chỉ tồn tại trên instance đã tạo ra nó. Có lần đội mình mất nửa ngày chạy lại job quantize vì bản duy nhất của export q4_k_xl nằm trên instance EC2 ai đó đã terminate lúc dọn dẹp chi phí.
  4. Validate container contract cục bộ trước khi đụng đến SageMaker. Thông báo lỗi của SageMaker khi health check hoặc contract load model sai nổi tiếng là khó hiểu — chạy docker run cục bộ với cùng entrypoint bắt được 90% lỗi mà nếu không sẽ thành vòng lặp deploy-fail-debug 20 phút trên console AWS.

Quyết định thực sự của lead

Nếu bạn là Technical Lead đang cân nhắc cho đội mình, câu hỏi không phải “pattern nào tốt nhất” — mà là “hình dạng traffic thực tế của mình là gì và ai giữ pager lúc 2 giờ sáng”. Pattern 1 hoặc 2 nếu bạn chưa đạt product-market-fit và muốn đi nhanh mà không cam kết một serving framework. Pattern 3 ngay khi có traffic đồng thời thật và chi phí GPU là dòng ai đó hỏi trong planning. Pattern 4 chỉ khi bạn đã có sẵn chuyên môn EKS/ECS trong nhà — đừng adopt thêm một lớp orchestration mới chỉ vì một model.

Mức chênh lệch chi phí 5 lần là có thật và đáng theo đuổi. Chỉ đừng để con số nổi bật đó làm bạn quên rằng quantize dịch chuyển nút thắt từ “chất lượng model” sang “kỷ luật vận hành xoay quanh template, artifact, và hình dạng tải” — và đó là nơi ít khoan dung hơn nhiều cho một sai lầm nhỏ.

Nguồn: Deploying quantized models on Amazon SageMaker AI with Unsloth — AWS Machine Learning Blog

Xuất nội dung

Bình luận