# Chạy CI có GPU cho dự án ML: kiểm thử trên máy GPU thuê theo phút

URL: https://cloudj.net/vi/blog/gpu-ci-runner-ml-tests
Published: 2026-11-12 · Updated: 2026-11-12

> Cách chạy CI có GPU cho dự án ML: CI runner trên VPS gọi instance GPU ngắn hạn qua SSH, tính tiền theo phút, chọn khu vực gần nơi chứa mã nguồn và xóa máy sau khi chạy.

Phần lớn bộ test của một dự án ML chạy ổn trên CPU. Phần còn lại, như kernel CUDA, nhánh code mixed precision, code nhiều GPU hay việc nạp mô hình trên phần cứng thật, thường hỏng âm thầm cho tới khi ai đó tự tay chạy trên GPU. Chạy CI có GPU cho dự án ML giúp lấp khoảng trống đó. Thay vì mua hẳn một máy GPU cho CI, bạn giữ CI runner trên một VPS nhỏ và chỉ gửi các test cần GPU sang một instance GPU thuê qua SSH, trả tiền theo phút và xóa máy khi hết đợt kiểm thử. Cách làm này đặc biệt hợp với các team AI nhỏ ở Việt Nam, nơi một chiếc GPU nằm không trong văn phòng là khoản chi khó biện minh.

Nếu bạn còn phân vân phần nào trong hệ thống thật sự cần GPU, hãy đọc trước bài [GPU server hay VPS cho AI](https://cloudj.net/vi/blog/gpu-instance-vs-vps-for-ai).

## Chạy CI có GPU cho dự án ML hoạt động thế nào

```text
git push -> CI runner trên VPS -> rsync code sang instance GPU -> chạy test GPU qua SSH -> trả kết quả về CI
```

- **CI runner** nằm trên VPS. Máy này chạy liên tục, rẻ, và giữ token của runner, secret và cache. Bài [chạy GitHub Actions runner trên VPS](https://cloudj.net/vi/blog/github-actions-runner-vps) hướng dẫn cách dựng.
- **Instance GPU** là thứ dùng xong bỏ. Nó chỉ giữ một bản sao code trong lúc chạy, không chứa gì quý giá.
- Hai bên nói chuyện qua SSH bằng một key riêng. Instance GPU trên Cloudj chỉ nhận SSH key.

Bài viết giả định bạn triển khai và xóa instance GPU từ console của Cloudj, ví dụ vào đầu tuần phát hành hoặc cho một khung test ban đêm, rồi đưa host và port của nó cho CI dưới dạng secret.

## Chọn card nhỏ

Test CI kiểm tra tính đúng đắn, không phải tốc độ. Một card nhỏ, giá vừa phải thường là đủ:

| Card | VRAM | Hợp với CI khi |
|---|---|---|
| [RTX 3060](https://cloudj.net/vi/gpu-cloud/rtx-3060) | 12 GB | Chạy unit test CUDA, forward và backward mô hình nhỏ, kiểm tra mixed precision |
| [RTX 4070](https://cloudj.net/vi/gpu-cloud/rtx-4070) | 12 GB | Cùng bộ nhớ trên kiến trúc Ada mới hơn, hữu ích khi cần test code riêng cho Ada |

Nếu bạn test code nhiều GPU, hãy chọn máy 2x; ô chọn số GPU có 1x, 2x, 4x và 8x trong cùng một máy. Giá "từ" theo thời gian thực có trên trang của từng mẫu và trên [GPU cloud](https://cloudj.net/vi/gpu-cloud).

## Chọn khu vực gần nơi chứa mã nguồn và dữ liệu

Với CI, khoảng cách tới người dùng cuối không quan trọng. Điều quan trọng là khoảng cách tới nơi chứa code, các gói wheel, dữ liệu và trọng số mô hình:

- nếu VPS chạy CI và cache gói nằm ở châu Âu, lọc `DE, NL`;
- nếu chúng ở Mỹ, dùng `US`;
- nếu test tải fixture từ một kho mô hình công khai, nước nào có kết nối tốt cũng được.

Mở [trang triển khai RTX 3060](https://app.cloudj.net/deploy/gpu?model=rtx-3060), nhập mã, và xem tốc độ tải xuống (Mbps) của từng máy, vì mỗi lần chạy CI đều kéo code và thư viện. Nếu không có máy, thêm mã nước lân cận hoặc xóa ô Country; tình trạng máy thay đổi trong ngày. Vị trí chỉ hiển thị đến cấp quốc gia.

## Giữ môi trường tái lập được

Mỗi lần chạy nên bắt đầu từ một trạng thái biết trước. Có hai cách:

- **Template cộng script cài đặt.** Triển khai bằng "Ubuntu 22.04 + CUDA (SSH)" hoặc "PyTorch + JupyterLab", rồi để job CI cài các thư viện của dự án mỗi lần chạy.
- **Image tự dựng.** Đóng một Docker image công khai với CUDA, Python và thư viện đã ghim phiên bản, rồi triển khai image đó. Lần chạy khởi động nhanh hơn và giống hệt nhau. Image tự chọn cần cấp tài khoản cao hơn và phải công khai; không hỗ trợ registry riêng. Xem bài [dùng Docker image riêng cho GPU](https://cloudj.net/vi/blog/custom-docker-image-gpu).

## Script cho CI

Một script tối giản để runner thực thi, với `GPU_HOST` và `GPU_PORT` lấy từ tab Connection của instance và lưu thành secret của CI:

```bash
#!/usr/bin/env bash
set -euo pipefail
SSH="ssh -p ${GPU_PORT} -o StrictHostKeyChecking=accept-new"
TARGET="root@${GPU_HOST}"

rsync -az --delete --exclude .git -e "$SSH" ./ "$TARGET:/workspace/repo/"
$SSH "$TARGET" 'cd /workspace/repo && nvidia-smi && pip install -q -r requirements.txt && pytest -m gpu -q --junitxml=gpu-results.xml'
rsync -az -e "$SSH" "$TARGET:/workspace/repo/gpu-results.xml" ./
```

Và một job GitHub Actions chạy script đó trên self-hosted runner:

```yaml
gpu-tests:
  runs-on: self-hosted
  steps:
    - uses: actions/checkout@v4
    - run: ./ci/run-gpu-tests.sh
      env:
        GPU_HOST: ${{ secrets.GPU_HOST }}
        GPU_PORT: ${{ secrets.GPU_PORT }}
```

Hãy gắn marker `pytest` cho các test chỉ chạy trên GPU, để bộ test CPU vẫn chạy trên runner thường.

## Giữ hóa đơn nhỏ

Instance GPU tính tiền theo phút từ số dư khi đang chạy. Giá hiển thị lúc triển khai được khóa, và một khoản cọc được tạm giữ khi triển khai. Team ở Việt Nam nạp số dư bằng chuyển khoản ngân hàng bằng VND.

- **Gom việc cần GPU.** Chạy test GPU khi merge vào nhánh chính hoặc theo lịch ban đêm, đừng chạy với mọi commit.
- **Xóa máy sau mỗi đợt.** Destroy xóa instance cùng ổ đĩa và dừng mọi khoản phí. Instance CI không chứa gì mà bạn không dựng lại được.
- **Tránh dùng Stop cho CI.** Stop giữ ổ đĩa nhưng nhả GPU, nên lần bật sau có thể kẹt ở "Waiting for GPU" và pipeline của bạn sẽ treo. Hãy xóa rồi triển khai mới. Bài [nên dừng hay xóa instance GPU](https://cloudj.net/vi/blog/stop-vs-destroy-gpu-instance) giải thích sự khác biệt.

## Xử lý êm khi không có GPU

Mỗi máy là máy chủ độc lập không có SLA phần cứng, và có ngày "available until" riêng. Hãy để job CI báo lỗi rõ ràng thay vì treo khi không kết nối được GPU: đặt thời gian chờ kết nối SSH ngắn và thông báo lỗi dễ đọc. Nếu máy chủ mất kết nối, việc tính tiền tạm dừng nhưng instance không được chuyển đi, nên hãy triển khai máy mới và cập nhật secret.

## Câu hỏi thường gặp

### Cả bộ test có cần GPU không?

Không. Hãy đánh dấu các test riêng cho GPU và chỉ gửi những test đó sang instance GPU.

### Nên dùng card nào cho CI?

Card 12 GB như RTX 3060 hay RTX 4070 đủ cho phần lớn test kiểm tra tính đúng đắn.

### Dùng registry Docker riêng được không?

Không. Image tự chọn phải công khai và cần cấp tài khoản cao hơn.

### Sao không dừng instance giữa các lần chạy?

Dừng máy sẽ nhả GPU, nên lần bật sau có thể phải chờ. Xóa rồi triển khai lại giúp CI ổn định hơn.

## Chuẩn bị đợt test GPU

Mở [trang triển khai RTX 3060](https://app.cloudj.net/deploy/gpu?model=rtx-3060), chọn nước gần nơi chứa mã nguồn và dữ liệu, rồi triển khai một instance nhỏ cho đợt test tới. So sánh các card phổ thông trên [GPU cloud](https://cloudj.net/vi/gpu-cloud).
