📦 Phục vụ model tiết kiệm: batch, container nhẹ, autoscale

By Ngọc Vương Trương

Bài trước bạn đã chọn được chỗ đặt hạ tầng: VPS giá rẻ, spot instance cho job chịu được gián đoạn, hoặc serverless. Bài này trả lời câu hỏi tiếp theo, cũng là câu hỏi quyết định hoá đơn: cùng một model đó, chạy kiểu gì và đóng gói ra sao để mỗi tháng chỉ trả vài đô?

Sau bài này bạn sẽ:

  • Phân biệt batch inference và online inference, và chọn đúng theo nhịp truy cập dữ liệu
  • Đóng gói model vào container đủ nhẹ để chạy trên VPS 512MB - 1GB RAM
  • Cấu hình autoscale có scale-to-zero cho tải thất thường
  • Nhận ra ba cấu hình tốn tiền vô ích mà tôi gặp nhiều nhất ở phần serving

Chọn kiểu serving theo nhịp của dữ liệu, không theo sở thích

Nguyên tắc gốc: online serving tính tiền theo thời gian process tồn tại, batch tính tiền theo thời gian thực sự tính toán. Một endpoint FastAPI nằm chờ 24/7 vẫn ngốn tiền dù hôm đó không ai gọi.

$$ \text{Chi phí online} = \text{giờ chạy} \times \text{giá/giờ} \times \text{số replica} $$

Kiểu Độ trễ Cách tính tiền Hạ tầng hợp Ví dụ trong capstone
Batch Phút đến giờ Theo phút job chạy Spot hoặc VPS bật theo lịch Chấm điểm toàn bộ catalogue mỗi đêm
Online Dưới 1 giây Theo giờ process sống VPS nhỏ, scale-to-zero API dự đoán khi người dùng bấm nút
Near-real-time Vài giây Theo giờ consumer sống Container nhỏ, hàng đợi Cập nhật điểm khi có sự kiện mới

Một model, hai kiểu phục vụ, chênh nhau có thể 20 đến 50 lần chi phí. Nếu sản phẩm chấp nhận kết quả "cập nhật mỗi đêm", batch trên spot gần như luôn rẻ hơn.

Bài tập 1: Bạn cần gợi ý sản phẩm cho 50.000 người dùng, làm mới mỗi đêm. Endpoint online hay job batch?

Gợi ý đáp án

Job batch. Không ai cần gợi ý tính lại lúc 3 giờ sáng theo thời gian thực. Ghi kết quả vào bảng trong Postgres hoặc file Parquet trên MinIO, tầng ứng dụng chỉ đọc ra. Chạy trên spot, bật theo cron, tắt khi xong.

Container nhẹ: mỗi MB và mỗi MB RAM đều là tiền

Image càng lớn thì thời gian kéo image càng lâu, cold start càng dài, và autoscaler càng dễ phản ứng sai. Bốn việc làm giảm kích thước gần như ngay lập tức:

  • Dùng base python:3.11-slim, không dùng image có sẵn CUDA khi chạy CPU
  • Multi-stage build: compiler và dependency build nằm ở stage đầu, runtime chỉ copy kết quả
  • Chỉ cài đúng thứ cần, không mang theo jupyter, matplotlib, pandas nếu chỉ cần numpy
  • Export sang ONNX hoặc quantize để giảm RAM và CPU cho mỗi request
FROM python:3.11-slim AS build
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

FROM python:3.11-slim
COPY --from=build /install /usr/local
COPY app.py model.onnx ./
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

Bảng dưới là chênh lệch tôi đo trên cùng một model phân loại văn bản nhỏ:

Cách đóng gói Kích thước image RAM lúc chạy Cold start
Image đầy đủ có torch + CUDA 6,2 GB 1,4 GB khoảng 45 giây
slim + torch CPU 1,1 GB 480 MB khoảng 12 giây
slim + onnxruntime 260 MB 180 MB khoảng 4 giây

Chuyển từ dòng đầu xuống dòng cuối cho phép bạn hạ VPS từ 4GB xuống 1GB RAM. Trong capstone, đó là khác biệt giữa 24 đô và 6 đô mỗi tháng.

Bài tập 2: Image serving của bạn nặng 2,4GB, docker stats cho thấy RAM 1,8GB dù model chỉ 40MB. Bạn kiểm tra gì trước?

Gợi ý đáp án

Kiểm tra ba thứ theo thứ tự: thư viện kéo vào có phải bản CUDA không (cài torch bản CPU hoặc onnxruntime), có thư viện nào chỉ dùng lúc train nhưng vẫn nằm trong requirements.txt của serving không, và image có copy cả thư mục dữ liệu hay virtualenv cũ vào không. Sửa .dockerignore thường là bước rẻ nhất.

Autoscale: trả tiền khi có việc, không trả tiền khi ngồi không

Autoscale tiết kiệm chỉ khi bạn chọn đúng tín hiệu để scale và chấp nhận đánh đổi cold start.

  • Scale-to-zero cho công cụ nội bộ, endpoint demo, API ít gọi. Rảnh thì về 0 replica.
  • min replicas = 1 cho endpoint có SLA hoặc cần độ trễ ổn định. Đây là khoản phí "bảo hiểm" hợp lý.
  • Online: scale theo concurrency hoặc RPS, sát tải thật hơn CPU.
  • Batch worker: scale theo độ dài hàng đợi, hết việc thì về 0.
  • Ngưỡng target khoảng 60 đến 80 phần trăm. Đặt 50 phần trăm sẽ gây scale loan và tốn hơn.
flowchart TD
    Start["📥 Dữ liệu cần dự đoán"] --> Q{"Cần kết quả trong vài giây?"}
    Q -->|"Không, theo lô"| Batch["🗂️ Batch trên spot<br/>chạy theo lịch, tắt khi xong"]
    Q -->|"Có"| Q2{"Tải có đều 24/7?"}
    Q2 -->|"Có"| Online["⚡ Online luôn bật<br/>min replicas = 1"]
    Q2 -->|"Thất thường"| Scale["📈 Scale-to-zero<br/>chấp nhận cold start"]

Warning: Cold start gồm ba phần: kéo image, khởi động process, và load model. Nếu model load từ MinIO qua mạng chậm, request đầu tiên có thể mất 30 giây. Hãy cache model trong image hoặc trong volume của VPS, và đo cold start trước khi bật scale-to-zero cho endpoint có người dùng thật.

Năm ý chính của bài, điền nhanh để tự kiểm tra:

Online serving tính tiền theo _____ chứ không phải thời gian tính toán. Với tải thất thường, _____ là đòn tiết kiệm lớn nhất, đổi lại là cold start. Base image _____ kết hợp multi-stage build làm image nhỏ hơn, kéo theo cold start ngắn hơn. Batch nên chạy trên _____ vì job bị thu hồi có thể chạy lại từ checkpoint. Đặt _____ bằng 0 cho công cụ nội bộ, nhưng giữ 1 cho endpoint có SLA.

Bài tập 3: Endpoint dự đoán của bạn nhận khoảng 300 request mỗi ngày, chủ yếu trong giờ hành chính. Bạn cấu hình gì?

Gợi ý đáp án

Đặt min replicas = 0, scale theo concurrency, giới hạn CPU và memory cho container, và chấp nhận cold start khoảng 5 giây bằng cách cache model trong image. Chỉ cần một lần đo cold start thực tế để chắc rằng nó không vượt ngưỡng chịu đựng của người dùng.

Ba cái bẫy hoá đơn ở phần serving

  • Giữ 2 replica "cho chắc" trên endpoint nội bộ 20 request mỗi ngày. Đây là khoản tiền thuần tuý lãng phí, không phải bảo hiểm.
  • Image nặng làm autoscaler phản ứng sai. Cold start 40 giây khiến request dồn hàng đợi, hệ thống tưởng quá tải và scale thêm nhiều replica cùng lúc, rồi tất cả cùng chậm.
  • Không đặt giới hạn CPU và memory cho container. Orchestrator sẽ cấp phát theo yêu cầu khai báo, không theo nhu cầu thật.

Quy tắc ngón tay: nếu một endpoint nhận dưới khoảng 1.000 request mỗi ngày, batch hoặc scale-to-zero gần như luôn rẻ hơn chạy thường trực. Và hãy đo bằng docker stats cùng log chi phí của VPS, đừng ước lượng bằng cảm giác.

Chốt lại và bước tiếp theo

  • Chọn kiểu serving theo nhịp dữ liệu: batch cho lô, online cho tương tác, và đừng trả tiền cho process ngồi không.
  • Container nhỏ là đòn tiết kiệm kép: ít RAM hơn và cold start ngắn hơn, cho phép hạ cấu hình VPS.
  • Autoscale chỉ có ý nghĩa khi tín hiệu scale đúng và bạn đã đo cold start.

Bài tiếp theo bạn sẽ mang toàn bộ phần này vào capstone: train model, đăng ký phiên bản trong MLflow, rồi deploy chính model đó lên hạ tầng rẻ theo đúng kiểu serving bạn vừa chọn.

Front: Batch inference phù hợp khi nào? Back: Khi kết quả không cần ngay, dữ liệu đến theo lô hoặc theo lịch.

Front: Online serving tính tiền theo cơ chế nào? Back: Theo thời gian process tồn tại, kể cả khi không có request nào.

Front: Vì sao batch hợp với spot instance? Back: Job bị thu hồi có thể chạy lại từ checkpoint mà không ảnh hưởng người dùng cuối.

Front: Multi-stage build giúp gì cho chi phí? Back: Giữ compiler và dependency build ngoài runtime image, nên image nhỏ và cold start ngắn hơn.

Front: Vì sao tránh image có CUDA khi chạy CPU? Back: Image nặng hàng GB, tốn đĩa, thời gian kéo image lâu và cold start dài.

Front: ONNX runtime hoặc quantization đem lại gì? Back: Giảm RAM và CPU cho mỗi request, cho phép dùng VPS nhỏ hơn.

Front: scale-to-zero là gì? Back: Đưa số replica về 0 khi không có request, gần như không tốn tiền lúc rảnh.

Front: Cái giá của scale-to-zero là gì? Back: Cold start, request đầu tiên chậm vì phải khởi động container và load model.

Front: Cold start gồm những phần nào? Back: Kéo image, khởi động process, và load model từ đĩa hoặc MinIO.

Front: Nên scale online serving theo tín hiệu nào? Back: Theo concurrency hoặc RPS, sát tải thực hơn CPU.

Front: Nên scale batch worker theo tín hiệu nào? Back: Theo độ dài hàng đợi, hết việc thì về 0 replica.

Front: Ngưỡng CPU target hợp lý cho autoscale là bao nhiêu? Back: Khoảng 60 đến 80 phần trăm, đặt quá thấp gây scale loan và tốn tiền.

Front: Khi nào nên giữ min replicas = 1? Back: Khi endpoint có SLA hoặc cần độ trễ ổn định cho người dùng thật.

Front: Đo chi phí thật của một container bằng cách nào? Back: Dùng docker stats cho RAM và CPU, nhân với giá VPS theo giờ.