Skip to content

Extended Resources & GPU Scheduling

Vì sao GPU scheduling là một thế giới riêng

Lập lịch CPU và memory là bài toán "chia phần co giãn": scheduler đóng gói các yêu cầu phân số (500m CPU, 256Mi) vào allocatable của node. GPU thì khác về bản chất: nó là tài nguyên rời rạc, không chia được, và cực kỳ đắt. Một node A100 hay H100 có giá gấp hàng chục lần node CPU thường, và một GPU hoặc được cấp trọn cho một container hoặc không — không có "500m GPU" theo nghĩa mặc định. Hệ quả là mọi sai lầm scheduling GPU đều đắt: một Pod thường rơi nhầm lên GPU node là lãng phí lớn nhất một cluster có thể tạo ra; một GPU bị giữ chỗ mà không dùng là tiền đốt theo giờ.

GPU trên Kubernetes được mô hình hóa qua extended resources — cơ chế tổng quát cho tài nguyên ngoài CPU/memory/storage. Hiểu extended resources trước, rồi GPU là một ứng dụng cụ thể của nó.

Internal model: Extended Resources

Extended resources là tài nguyên có tên đầy đủ (fully-qualified) ngoài bộ built-in, ví dụ nvidia.com/gpu, example.com/dongle. Chúng được node quảng cáo trong status.capacity/status.allocatable, và scheduler xử lý chúng trong Filter NodeResourcesFit y như CPU/memory — node phải còn đủ extended resource để chứa Pod.

Quy tắc sống còn, khác biệt cơ bản với CPU/memory: với extended resources, bạn phải đặt requests bằng limits (hoặc chỉ đặt limits, Kubernetes tự đặt requests bằng). Tài liệu nêu rõ extended resources không thể overcommit (Resource Management). Lý do: extended resources rời rạc và không nén được — không có khái niệm "burst lên dùng nhiều GPU hơn requests". Một Pod hoặc được cấp trọn N đơn vị hoặc không.

yaml
resources:
  limits:
    nvidia.com/gpu: 2     # requests tự động = 2; KHÔNG được requests != limits

Hệ quả về QoS (file 6): vì requests==limits cho extended resource, và GPU node thường cũng đặt CPU/memory chặt, Pod GPU thường rơi vào Guaranteed — và phải như vậy. GPU không bao giờ nên là Burstable/BestEffort theo chiều GPU: bạn không thể "burst" GPU, và để Pod GPU dễ bị evict là phá hỏng workload training chạy nhiều giờ.

GPU trên GKE: cơ chế đầy đủ

Yêu cầu GPU và targeting loại GPU

Cấp GPU cho container qua limits (GKE GPUs):

yaml
spec:
  nodeSelector:
    cloud.google.com/gke-accelerator: nvidia-tesla-t4   # chọn loại GPU
  containers:
  - name: cuda
    resources:
      limits:
        nvidia.com/gpu: 1

Node selector cloud.google.com/gke-accelerator nhắm đúng loại GPU (T4, L4, A100, H100...). Nếu bỏ trống mà cluster có nhiều loại GPU, scheduler có thể đặt lên loại đắt hơn cần thiết — luôn chỉ định khi có nhiều loại.

Taint GPU + ExtendedResourceToleration: cô lập tự động

GKE tự gắn taint nvidia.com/gpu=present:NoSchedule lên GPU node để ngăn Pod thường rơi vào (GKE GPUs). Đặc biệt, GKE chạy ExtendedResourceToleration admission controller, "tự động áp toleration để chỉ Pod yêu cầu GPU mới được lập lịch lên GPU node". Nghĩa là: Pod khai nvidia.com/gpu trong limits tự động nhận toleration cho taint GPU — bạn không phải viết toleration thủ công. Cơ chế kép này (taint đẩy Pod thường + auto-toleration kéo Pod GPU) là cách GKE bảo vệ GPU node đắt tiền.

Lưu ý từ file 5: khi thêm GPU node pool vào cluster đã có non-GPU pool, GKE thêm taint này; nhưng cần xác nhận taint thực sự có mặt — sai sót cấu hình có thể để Pod thường chiếm GPU node.

Device plugin DaemonSet và driver

GPU không "tự xuất hiện" với Kubernetes. Một device plugin (chạy dưới dạng DaemonSet) đăng ký GPU của node với kubelet, để kubelet quảng cáo nvidia.com/gpu trong allocatable. Trên GKE, device plugin và việc cài NVIDIA driver được quản lý qua tham số gpu-driver-version với các lựa chọn default, latest, hoặc disabled (GKE GPUs). Nếu để disabled, bạn phải tự cài driver — thường không nên trừ khi có nhu cầu đặc biệt.

Hệ quả scheduling: device plugin DaemonSet phải Ready trước khi node quảng cáo GPU. Có một khoảng thời gian sau khi GPU node join cluster mà GPU chưa khả dụng (driver đang cài). Pod GPU lập lịch trong khoảng này có thể Pending tạm thời — bình thường, không phải lỗi.

Cài driver chặn workload nếu thiếu

Pattern đáng lưu ý: nếu driver chưa sẵn sàng, Pod GPU không lên được dù node "có" GPU vật lý. Đây là lý do nên dùng driver do GKE quản lý (default/latest) thay vì tự cài — giảm khoảng trống và lỗi vận hành.

GPU sharing: tăng hiệu suất sử dụng GPU đắt

Vì GPU đắt và nhiều workload (inference nhẹ, notebook, dev) không dùng hết một GPU, GKE hỗ trợ chia sẻ GPU theo nhiều chiến lược (GKE GPUs):

Chiến lượcCơ chếCô lậpUse case
Time-sharingNhiều Pod luân phiên dùng cùng GPU theo thời gianYếu (không cô lập bộ nhớ)Inference nhẹ, dev/test, workload burst rời rạc
Multi-Instance GPU (MIG)Chia GPU vật lý thành nhiều instance phần cứng độc lậpMạnh (cô lập bộ nhớ + compute)Đa tenant cần cô lập, chạy nhiều job nhỏ song song
Không chia (mặc định)Một Pod độc chiếm một GPUTuyệt đốiTraining nặng, workload cần trọn GPU

Trade-off cốt lõi:

  • Time-sharing tăng hiệu suất sử dụng nhưng các Pod tranh compute — một Pod ngốn sẽ làm chậm Pod khác, và không cô lập bộ nhớ (một Pod OOM GPU có thể ảnh hưởng). Phù hợp khi workload không nhạy và muốn nhồi nhiều Pod lên GPU rảnh.
  • MIG (chỉ trên GPU hỗ trợ như A100/H100) chia phần cứng thật, cho cô lập mạnh, nhưng số instance cố định theo profile MIG — kém linh hoạt hơn time-sharing, và mỗi instance nhỏ hơn GPU đầy đủ.

Quyết định: training production nặng → GPU trọn; inference đa tenant cần cô lập → MIG; dev/test/burst → time-sharing. Sai lầm phổ biến là dùng time-sharing cho training nặng (các job làm chậm nhau) hoặc độc chiếm GPU cho inference nhẹ (lãng phí).

Production architecture patterns

Pattern: node pool GPU riêng + ComputeClass

GPU node nên ở node pool riêng, taint sẵn, và quản lý qua ComputeClass (file 9) để GKE tự tạo/scale đúng loại GPU theo nhu cầu, kèm fallback (ví dụ "ưu tiên A100, không có thì L4"). Kết hợp với cluster autoscaler scale-to-zero để GPU node tắt khi không có job — tiết kiệm chi phí lớn nhất với GPU.

Pattern: gang scheduling cho training phân tán

Training phân tán (nhiều worker phải khởi động cùng lúc hoặc không khởi động ai) cần gang scheduling — đảm bảo cả nhóm Pod được cấp GPU đồng thời, tránh deadlock kiểu "mỗi job giữ vài GPU và chờ phần còn lại". kube-scheduler mặc định không gang-schedule; cần scheduler bổ sung như Kueue hoặc dùng khả năng Permit "wait" của Scheduling Framework (file 1). Trên GKE, Dynamic Workload Scheduler hỗ trợ gang scheduling cho job GPU/TPU — chủ đề mở rộng ở chương về AI/ML workload.

Pattern: scale-to-zero GPU pool

Vì GPU đắt theo giờ, cấu hình GPU node pool min size = 0. Khi không có Pod GPU, autoscaler xóa GPU node; khi có job, nó tạo lại. Đánh đổi: độ trễ scale-up (tạo node + cài driver) có thể vài phút — chấp nhận được cho batch training, không phù hợp inference cần phản hồi tức thì (giữ tối thiểu vài node ấm cho inference).

Real-world scenarios

Tình huống: Pod thường chiếm GPU node

Như đã nêu ở file 5: cluster thêm GPU pool nhưng taint không được áp đúng (ví dụ tạo cluster mới với GPU pool ngay từ đầu, hoặc gỡ taint nhầm). Pod CPU thường rơi lên node H100, chiếm CPU/memory khiến Pod GPU thật bị Pending. Hậu quả tài chính lớn vì H100 đắt. Phòng: luôn xác nhận taint nvidia.com/gpu=present:NoSchedule hiện diện, và không gán toleration GPU cho Pod thường.

Tình huống: training job deadlock vì thiếu gang scheduling

Một training 8 worker, mỗi worker 1 GPU, trên cluster có đúng 8 GPU rải nhiều job. Hai training job cùng submit: job A chiếm 5 GPU, job B chiếm 3 GPU, cả hai chờ đủ 8 — deadlock, cả hai treo, 8 GPU đắt tiền nằm chờ. Sửa: dùng Kueue/gang scheduling để một job chỉ được cấp khi đủ toàn bộ 8 GPU, hoặc không cấp gì. Đây là lý do batch GPU cần scheduler chuyên dụng, không dùng kube-scheduler mặc định.

Tình huống: inference lãng phí GPU

Một dịch vụ inference chạy mỗi Pod độc chiếm một L4 nhưng mỗi Pod chỉ dùng ~20% GPU. Chi phí gấp 5 lần cần thiết. Chuyển sang time-sharing (hoặc MIG nếu cần cô lập), nhồi 4–5 Pod inference lên mỗi GPU, giảm chi phí tương ứng. Phù hợp vì inference nhẹ, không nhạy với việc chia sẻ compute.

Cơ chế sâu hơn: vì sao GPU không phân số được như CPU

CPU là tài nguyên thời gian: kernel chia thời gian CPU thành lát, nên "0.5 CPU" có nghĩa rõ ràng (50% thời gian một core). GPU không có cơ chế chia sẻ thời gian gốc trong scheduler Kubernetes — nvidia.com/gpu chỉ nhận giá trị nguyên. Time-sharing và MIG là các cơ chế bổ sung ở tầng device plugin/phần cứng, không phải khả năng phân số của scheduler. Đây là lý do bạn không thể viết nvidia.com/gpu: 0.5 — scheduler không hiểu, và overcommit GPU (đặt requests < limits) bị cấm.

Hệ quả với device plugin: khi bật time-sharing trên GKE, device plugin quảng cáo nhiều "GPU ảo" trên một GPU vật lý (vd một GPU thành 4 "client"), nên node báo nvidia.com/gpu: 4 dù chỉ có 1 GPU thật. Scheduler vẫn làm việc với số nguyên, nhưng giờ "4 đơn vị" cùng chia một phần cứng. Quan trọng: time-sharing không cô lập bộ nhớ GPU — bốn Pod chia 4 "client" cùng tranh VRAM thật; một Pod ngốn VRAM có thể làm Pod khác lỗi out-of-memory GPU. MIG thì khác: nó chia phần cứng thật nên mỗi instance có VRAM riêng, scheduler thấy các tài nguyên MIG riêng biệt (vd nvidia.com/mig-1g.5gb).

Cơ chế sâu hơn: bài toán đóng gói GPU và stranded resources

GPU node thường là máy lớn (nhiều CPU, nhiều RAM) đi kèm GPU. Một cạm bẫy đóng gói: nếu Pod GPU khai CPU/memory quá cao, một node 8-GPU có thể chỉ chứa được 2 Pod GPU dù còn 6 GPU rảnh — vì hết CPU/memory trước khi hết GPU. 6 GPU đắt tiền bị "stranded" (mắc kẹt, không dùng được). Ngược lại, nếu Pod GPU khai CPU/memory quá thấp, nó có thể bị throttle/OOM.

Lời giải là cân đối requests CPU/memory với số GPU mỗi Pod dùng, sao cho tỷ lệ (CPU per GPU, RAM per GPU) khớp với tỷ lệ của node GPU. Ví dụ node a2-highgpu-8g có 8 GPU, 96 vCPU, 680GB RAM → mỗi GPU "đi kèm" ~12 vCPU, ~85GB. Pod dùng 1 GPU nên khai CPU/RAM quanh tỷ lệ đó để node nhồi đủ 8 Pod GPU. Đây là phiên bản GPU của bài toán cân bằng NodeResourcesBalancedAllocation (file 2) — nhưng hậu quả tài chính lớn hơn nhiều vì GPU đắt.

Cơ chế sâu hơn: TPU và Dynamic Workload Scheduler

Trên GKE, ngoài GPU còn có TPU (Tensor Processing Unit) — accelerator riêng của Google cho ML. TPU được mô hình hóa tương tự extended resource nhưng có ràng buộc topology đặc thù: TPU slice (vd v5e, topology 2x4) yêu cầu các Pod được đặt theo cấu trúc liên kết vật lý cụ thể, nên scheduling TPU gần như luôn cần gang scheduling. ComputeClass hỗ trợ khai tpu với version/topology/chip count (file 9).

Dynamic Workload Scheduler (DWS) là dịch vụ GKE cho phép đặt trước (provisioning request) một lô accelerator được cấp đồng thời — giải quyết bài toán "cần 64 GPU cùng lúc cho training, không chấp nhận lấy 30 rồi chờ". DWS cấp toàn bộ hoặc không, đúng tinh thần gang scheduling, và tích hợp với hàng đợi capacity của Google. Với training quy mô lớn trên GKE, DWS thường là con đường đúng thay vì để Pod GPU Pending rời rạc chờ capacity.

Real-world scenario bổ sung: notebook đa người dùng

Một nền tảng data science cấp Jupyter notebook cho hàng chục nhà khoa học dữ liệu, mỗi người thỉnh thoảng chạy thử nghiệm GPU nhẹ. Cấp mỗi notebook một GPU trọn là lãng phí khủng khiếp — phần lớn thời gian GPU rảnh. Lời giải: time-sharing cho phép nhiều notebook chia một GPU; khi ai đó chạy job nặng thật, họ chuyển sang node pool GPU trọn (qua ComputeClass/nodeSelector). Đây là minh họa kinh điển: time-sharing cho workload rời rạc, nhẹ, không nhạy; GPU trọn cho job nặng liên tục. Chọn sai chiến lược sharing là sai lầm chi phí lớn nhất với GPU.

Khung quyết định: chọn chiến lược GPU

WorkloadChiến lượcLý do
Training nặng, dài giờGPU trọn (1 Pod/GPU)Cần toàn bộ compute + VRAM; gián đoạn = mất giờ
Training phân tán đa nodeGPU trọn + gang scheduling (Kueue/DWS)Tránh deadlock cấp một phần
Inference nhẹ, đa PodTime-sharingTăng hiệu suất, chấp nhận tranh compute
Đa tenant cần cô lập VRAMMIGCô lập phần cứng thật
Notebook/dev rời rạcTime-sharingGPU phần lớn rảnh

Common mistakes / anti-patterns

  • Đặt requests != limits cho extended resource/GPU → Kubernetes từ chối; GPU không overcommit được.
  • Để Pod GPU thành Burstable theo các tài nguyên khác mà bỏ lơ → vẫn ổn về GPU, nhưng nếu CPU/memory BestEffort thì Pod GPU dễ bị evict, giết training nhiều giờ. Đặt requests/limits CPU/memory chặt cho Pod GPU.
  • Quên chỉ định loại GPU khi cluster đa loại → rơi lên GPU đắt hơn cần thiết.
  • Dùng time-sharing cho training nặng → các job làm chậm nhau, không cô lập bộ nhớ.
  • Độc chiếm GPU cho inference nhẹ → lãng phí 5–10 lần.
  • Dùng kube-scheduler mặc định cho training phân tán → deadlock thiếu gang scheduling.
  • Tự cài driver (gpu-driver-version=disabled) không có lý do → tăng lỗi vận hành và khoảng trống Pending.

GCP-native implementation guidance

Tạo GPU node pool có driver do GKE quản lý và scale-to-zero:

bash
gcloud container node-pools create gpu-pool \
  --cluster=<cluster> --location=<region> \
  --accelerator=type=nvidia-l4,count=1,gpu-driver-version=latest \
  --machine-type=g2-standard-8 \
  --enable-autoscaling --min-nodes=0 --max-nodes=8 \
  --node-taints=nvidia.com/gpu=present:NoSchedule

Kiểm tra GPU đã được node quảng cáo (device plugin Ready):

bash
kubectl get nodes -o custom-columns=NAME:.metadata.name,GPU:.status.allocatable.'nvidia\.com/gpu'
# Cột GPU rỗng/0 trên node mới → driver/device plugin chưa sẵn sàng, Pod GPU sẽ Pending tạm thời

Tổng kết mental model

  • Extended resources (gồm GPU) rời rạc, không overcommit → requests phải bằng limits.
  • GPU node được GKE taint nvidia.com/gpu=present:NoSchedule; ExtendedResourceToleration tự inject toleration cho Pod yêu cầu GPU.
  • Device plugin DaemonSet + driver phải Ready trước khi GPU khả dụng; dùng driver GKE quản lý.
  • GPU sharing: time-sharing (rẻ, không cô lập) vs MIG (cô lập phần cứng) vs trọn GPU (training nặng).
  • Training phân tán cần gang scheduling (Kueue/DWS), không phải kube-scheduler mặc định.
  • GPU không bao giờ nên là Burstable/BestEffort theo bất kỳ chiều nào ảnh hưởng tới khả năng bị evict.

FAQ thực chiến

Vì sao không đặt được nvidia.com/gpu: 0.5?

Vì scheduler chỉ hiểu số nguyên cho extended resource, và GPU không có cơ chế chia sẻ thời gian gốc trong scheduler. Chia sẻ GPU (time-sharing, MIG) là cơ chế tầng device plugin/phần cứng — device plugin quảng cáo nhiều "đơn vị" trên một GPU vật lý, nhưng mỗi đơn vị vẫn là số nguyên với scheduler.

Pod GPU của tôi Pending dù node có GPU, vì sao?

Khả năng cao nhất: device plugin/driver chưa Ready trên node mới (đang cài driver) nên node chưa quảng cáo nvidia.com/gpu trong allocatable. Kiểm tra bằng kubectl get nodes -o ...allocatable. Khả năng khác: thiếu nodeSelector loại GPU đúng, hoặc taint GPU không khớp toleration (thường ExtendedResourceToleration tự lo, nhưng xác nhận).

Time-sharing hay MIG cho đa tenant?

MIG nếu cần cô lập bộ nhớ giữa tenant (một tenant không thể làm tenant khác OOM GPU) — nhưng chỉ trên GPU hỗ trợ (A100/H100) và số instance cố định theo profile. Time-sharing nếu tenant tin cậy nhau và workload nhẹ/rời rạc — rẻ và linh hoạt hơn nhưng không cô lập VRAM.

Có nên scale-to-zero node pool GPU không?

Có, cho batch/training chịu được độ trễ scale-up vài phút (tạo node + cài driver). Không, cho inference cần phản hồi tức thì — giữ tối thiểu vài node ấm. GPU đắt theo giờ nên scale-to-zero là đòn bẩy tiết kiệm lớn nhất khi workload không liên tục.

References