Great Lotus · Hạ tầng ảo hoá WP2

Chia một card đồ hoạ
cho nhiều máy ảo cùng lúc

Cách hệ thống Proxmox của Great Lotus cắt một chiếc GeForce GTX 1660 Ti thành nhiều card ảo độc lập — vì sao nó hoạt động được dù NVIDIA không cho phép, những con số nào tin được, và cách dựng lại toàn bộ từ đầu.

Máy chủ pve-wp2-02 Card GTX 1660 Ti 6 GB Chia tối đa 6 vGPU Kernel 6.14.11-9-pve Driver 550.163.02 Đo ngày 28/08/2026 Bổ sung 30/08/2026

Vấn đề cần giải

Một máy chủ ảo hoá có thể chia CPU và RAM cho hàng chục máy ảo rất dễ dàng. Nhưng card đồ hoạ thì không: theo mặc định, một card chỉ gán được cho đúng một máy ảo. Máy thứ hai muốn dùng thì máy thứ nhất phải tắt.

Với công việc cần GPU — dựng hình, chạy trình duyệt có tăng tốc phần cứng, xử lý ảnh — điều đó nghĩa là mỗi máy ảo phải có một card riêng. Rất tốn kém.

vGPU (virtual GPU) là lời giải: chia một card vật lý thành nhiều card ảo, mỗi máy ảo nhận một phần VRAM riêng và chia sẻ nhân xử lý theo lượt.

Hệ thống Great Lotus có gì

Thành phầnLà gìỞ đâu
pve-wp2-02Máy chủ ảo hoá Proxmox, Dual Xeon E5-2673 v3 (48 luồng), 94 GB RAM, GTX 1660 Ti172.16.20.102
pve-wp2-01Máy chủ Proxmox thứ hai, laptop, chỉ có đồ hoạ tích hợp Intel172.16.20.101
B43 Lotus CloudBảng điều khiển web kiểu Digital Ocean — tạo máy, gán vGPU, sao lưu:20430
B44 FastAPI-DLSMáy chủ cấp phép — thay thế máy chủ license của NVIDIA:20440

Hai cách dùng card, chọn một

Chia vGPUPassthrough nguyên card
Số máy dùng đượcNhiều (tới 6)Đúng 1
Hiệu năng mỗi máyChia sẻ theo lượtTrọn vẹn 100%
Driver trên máy chủCần driver vgpu-kvm đã váKhông cần gì
Phụ thuộc phiên bản kernelRất chặtKhông
Cần giấy phépCó (đã có B44)Không

Trang Cấu hình máy chủ của B43 có công tắc chuyển giữa hai chế độ này. Đổi chế độ đòi khởi động lại máy chủ.

Điều đáng nhớ nhất

NVIDIA cố tình khoá vGPU trên card GeForce để bán card datacenter đắt hơn. Toàn bộ phần cứng cần thiết đã nằm sẵn trong con chip — thứ chặn lại chỉ là một danh sách mã card trong phần mềm. Đó là lý do vá được.

Ba cách chia GPU, và cái ta dùng

CáchNguyên lýĐánh giá
API remotingMáy ảo gửi lệnh đồ hoạ về máy chủ vẽ hộChậm, hỏng nhiều phần mềm
PassthroughGiao thẳng cả card cho một máy ảoNhanh nhất, nhưng chỉ một máy
Mediated passthrough (mdev)Kernel tạo nhiều "card con" ảo từ một card thậtCái ta dùng

mdev (mediated device) là cơ chế của nhân Linux cho phép một thiết bị vật lý tự khai báo ra nhiều thiết bị con. Mỗi thiết bị con được gán cho một máy ảo qua VFIO, và máy ảo nhìn thấy nó như một card PCI thật.

Vì sao card GeForce bị chặn

Driver nvidia-vgpu-kvm mang theo tệp vgpuConfig.xml liệt kê đúng 27 mã card được phép — toàn bộ là card datacenter (Tesla, A100, L40…). Khi khởi động, tiến trình nvidia-vgpud đọc mã card thật rồi đối chiếu:

# Nhật ký khi chưa vá — mã 0x2182 là GTX 1660 Ti
nvidia-vgpud: GPU not supported by vGPU at PCI Id: 0:81:0:0 DevID: 0x10de / 0x2182
Đã thử và không ăn thua

Thêm tay mã 0x2182 vào vgpuConfig.xml không giải quyết được gì — thông báo từ chối y hệt. Phép kiểm nằm trong mã nhị phân của driver, không đọc từ tệp XML. Đó chính là lý do phải vá nhị phân.

Chuỗi mở khoá

Vì sao chọn Quadro RTX 6000 làm đích giả trang? Vì nó dùng cùng thế hệ chip Turing. Bảng ánh xạ nằm trong vgpu_unlock_hooks.c:

/* Turing */
case 0x1e02 ... 0x1ff9:
case 0x2182 ... 0x21d1:   /* TU116 — GTX 1660 Ti nằm ở đây */
    return 0x1e30;       /* Quadro RTX 6000 */
Chỉ vá driver là chưa đủ

Sau khi vá nhị phân, nvidia-vgpud vẫn báo GPU not supported (chỉ khác một chi tiết: subsystem đổi từ 0x8841 sang 0x0000). Phải có cả vgpu_unlock-rs nạp qua LD_PRELOAD, và khởi động lại máy chủ thì mdev_supported_types mới xuất hiện.

Ba dòng hồ sơ: Q, B, A

Tên hồ sơ có dạng GRID RTX6000-2Q: số là dung lượng VRAM tính bằng GB, chữ cái là mục đích sử dụng.

DòngDùng choĐặc điểm
Q — Máy trạm đồ hoạThiết kế, CAD, dựng phim, CUDA, AIĐa dụng nhất. Có CUDA, tới 7680×4320
B — Máy để bàn ảoVăn phòng, duyệt webNhiều màn hình, tối đa 5120×2880
A — Phát ứng dụngMáy chủ ứng dụng dùng chungKhoá ở 1280×1024, không CUDA

Nếu phân vân, chọn dòng Q. Nó bao trùm mọi nhu cầu của hai dòng kia.

Quy tắc đồng nhất

Khoá theo CỠ, không theo hồ sơ

Driver báo Homogeneous vGPUs: 1 — mọi vGPU trên cùng một card phải có cùng cỡ khung đệm (framebuffer). Không thể vừa có máy 1 GB vừa có máy 2 GB; đã thử và bị từ chối.

Nhưng ràng buộc chỉ nằm ở cỡ, không ở mã hồ sơ. Đọc sysfs khi một máy đang giữ một lát 2 GB thấy rõ điều đó — ba dòng Q / B / A cùng 2 GB vẫn mở:

/sys/bus/pci/devices/0000:81:00.0/mdev_supported_types/*/available_instances

nvidia-256 (1Q)   0    ← khác cỡ  ⇒ khoá
nvidia-257 (2Q)  11    ← cùng cỡ  ⇒ còn chỗ
nvidia-436 (2B)  11    ← cùng 2 GB, KHÁC dòng ⇒ vẫn mở
nvidia-438 (2A)  11    ← nt
nvidia-259 (4Q)   0    ← khác cỡ  ⇒ khoá

Hệ quả thực tế: phải quyết định trước sẽ chia thành mấy phần, vì đổi cỡ đòi tắt hết máy đang dùng card. Còn đổi dòng (2Q sang 2B) thì không cần.

Mẹo đọc số chỗ còn lại

available_instances bị thổi phồng y hệt max_instance — cả hai cùng tính theo card 24 GB mà driver tưởng mình đang là. Nhưng vì lệch cùng một lượng, hiệu của hai số thì đúng:

số máy đang giữ = max_instance(driver) − available(driver)   = 12 − 11 = 1
số chỗ còn lại  = VRAM thật ÷ framebuffer − số máy đang giữ  =  3 −  1 = 2

Nhờ vậy đọc được trạng thái card mà không cần hỏi từng file cấu hình máy ảo.

Con số nào tin được, con số nào không

Vì card đang giả trang thành Quadro RTX 6000 (24 GB), driver lấy luôn bảng thông số của card đó. Kết quả: mọi con số liên quan tới dung lượng đều sai.

Thông sốTin được?Vì sao
framebuffer (VRAM mỗi máy)ĐÚNGMáy ảo thật sự nhận đúng chừng đó. Đã kiểm: hồ sơ 2Q → máy khách báo 2048 MiB
max_resolution, num_headsĐÚNGDriver thật sự áp giới hạn này lên máy khách
frl_config (giới hạn fps)ĐÚNGBộ giới hạn khung hình thật sự hoạt động
max_instanceSAITính theo VRAM 24 GB của RTX 6000
available_instancesSAICùng lý do — nhưng hiệu max_instance − available thì đúng, vì hai số lệch cùng một lượng. Đó là cách đếm số máy đang giữ chỗ
Tên GRID RTX6000-*Gây hiểu nhầmChỉ là mặt nạ; phần cứng vẫn là TU116
Ví dụ cụ thể

Hồ sơ 2 GB: driver báo max_instance = 12. Thực tế chỉ chạy được 3 máy. Ai tin con số 12 mà lên kế hoạch sẽ phát hiện ra sai lầm ở máy thứ tư — lúc đó nó đơn giản là không khởi động.

Công thức đúng

Đơn giản hơn tưởng tượng — chia trọn vẹn, không trừ phần dự trữ nào:

số máy tối đa = VRAM thật ÷ framebuffer của hồ sơ
              = 6144 MiB ÷ 2048 MiB = 3 máy
Đây là kết quả đo, không phải suy luận

Ban đầu người viết đoán phải trừ ~64 MiB cho driver máy chủ (vì nvidia-smi báo 56 MiB lúc rảnh). Thí nghiệm bác bỏ giả thuyết đó: nếu có phần dự trữ thì hồ sơ 2 GB chỉ được 2 máy, nhưng thực tế chạy được 3. Công thức đúng là phép chia trọn vẹn.

Bảng sức chứa thật — card 6 GB

Hồ sơVRAM mỗi máyDriver nóiThực tếKiểm chứng
1Q / 1B / 1A1 GB246đã đo — máy thứ 7 bị từ chối
2Q / 2B / 2A2 GB123đã đo — máy thứ 4 bị từ chối
3Q / 3A3 GB82theo công thức
4Q / 4A4 GB61theo công thức
6Q / 6A6 GB41dùng hết sạch VRAM
8Q · 12Q · 24Q8–24 GB1–30lớn hơn card, không bao giờ chạy

Bảng điều khiển B43 đã tính lại các con số này (mã: app/vgpu_facts.py) và ẩn hẳn 6 hồ sơ không dùng được, nên giao diện chỉ hiện những lựa chọn có thật.

Vượt quá thì sao

Hỏng an toàn: máy ảo thứ tư đơn giản là không khởi động, báo QEMU exited with code 1. Máy chủ và các máy đang chạy không hề bị ảnh hưởng.

Trường hợp Intel — số liệu ngược lại

Máy pve-wp2-01 dùng đồ hoạ tích hợp Intel với công nghệ GVT-g, hoàn toàn khác. Điểm quan trọng: với GVT-g, available_instances đọc thẳng từ sysfs của kernel nên đáng tin tuyệt đối — ngược hẳn với NVIDIA.

Cách chia hợp lệ — sinh từ VRAM, không viết cứng

Không phải số nào cũng chia được. Ràng buộc thật chỉ có hai, và cả hai đều đến từ bảng hồ sơ của driver chứ không phải từ số học:

  1. Mỗi phần phải là số GB nguyên và chia hết tổng VRAM. Khung đệm lẻ không tồn tại trong bảng hồ sơ, nên "5 phần từ card 12 GB" không phải một cách chia — nó chỉ là một phép chia không dư trên giấy.
  2. Cỡ đó phải có hồ sơ mdev thật trên chính card đang xét.
CardCác cách chia có thậtSố lựa chọn
6 GB (GTX 1660 Ti — đang dùng) 1×6 · 2×3 · 3×2 · 6×14
8 GB1×8 · 2×4 · 4×2 · 8×14
12 GB1×12 · 2×6 · 3×4 · 4×3 · 6×2 · 12×16
16 GB1×16 · 2×8 · 4×4 · 8×2 · 16×15

Bảng điều khiển sinh danh sách này tại chỗ (vgpu_profile.splits_for_vram()) thay cho hằng số viết cứng cho card 6 GB của bản đầu — cắm card khác vào là bốn lựa chọn cũ đều sai mà không có gì báo.

Máy chủ có nhiều card

Đây là chỗ trực giác đánh lừa mạnh nhất. Người ta mặc định "mỗi card cấu hình riêng được". Không phải. Tệp ghi đè hồ sơ khoá theo mã hồ sơ, phạm vi toàn máy chủ:

# /etc/vgpu_unlock/profile_override.toml
[profile.nvidia-256]      # ← không có chỗ nào ghi "card nào"
framebuffer = 0x3A000000
Hệ quả khi mua card

Hai card CÙNG ĐỜI trên một máy chủ buộc phải chia giống hệt nhau — chúng dùng chung bộ mã mdev, nên một dòng ghi đè áp cho cả hai.

Muốn hai cách chia khác nhau thì phải là hai đời card khác nhau (ví dụ một card 8 GB và một card 12 GB): mỗi đời có bộ mã mdev riêng nên ghi đè độc lập được.

Bẫy định dạng địa chỉ PCI

nvidia-smi in miền PCI thành 8 chữ số, còn sysfs và Proxmox dùng 4. Không chuẩn hoá thì phép ghép card ↔ hồ sơ không bao giờ khớp, và phần mềm lặng lẽ kết luận card "không có hồ sơ nào":

nvidia-smi : 00000000:81:00.0
sysfs      : 0000:81:00.0      ← dạng chuẩn

Điều kiện cần trước khi bắt đầu

Yêu cầuVì sao
Card NVIDIA Maxwell trở lênvgpu_unlock chỉ hỗ trợ Maxwell, Pascal, Turing, Ampere
IOMMU bật trong BIOS (VT-d / AMD-Vi)Không có thì VFIO không hoạt động
Kernel phù hợp với driverĐây là rào cản hay gặp nhất — xem bước 1
Bộ driver vgpu-kvm có bản váPhải tải từ NVIDIA Licensing Portal
Máy chủ cấp phépMáy khách hết hạn giấy phép sẽ bị giảm hiệu năng

Bảy bước

  1. Chọn kernel khớp với driver — làm trước tiên

    Đây là chỗ dễ mất nhiều giờ nhất. Driver NVIDIA biên dịch một module vào nhân Linux, và nhân thay đổi API liên tục. Kiểm tra bằng cách biên dịch thử, không cài gì cả:

    sh NVIDIA-Linux-x86_64-<ver>-vgpu-kvm.run --extract-only --target /root/nv
    cd /root/nv/kernel && make -j$(nproc) SYSSRC=/lib/modules/$(uname -r)/build modules

    Thất bại thì cài kernel cũ hơn và ghim lại. Trên hệ thống này: kernel 7.0 hỏng, kernel 6.14 chạy.

  2. Chuẩn bị máy chủ
    # Bật IOMMU trong tham số khởi động
    intel_iommu=on iommu=pt        # hoặc amd_iommu=on
    
    # Chặn driver nguồn mở, nếu không nó chiếm mất card
    echo -e "blacklist nouveau\nblacklist nvidiafb\noptions nouveau modeset=0" \
      > /etc/modprobe.d/blacklist-nouveau.conf
    update-initramfs -u && reboot
  3. Tải driver — và kiểm ngay tính toàn vẹn

    Lấy từ NVIDIA Licensing Portal (đăng ký dùng thử miễn phí; nên dùng email tên miền công ty, email gmail hay bị xét duyệt tay).

    sh <file>.run --check      # PHẢI ra "check sums and md5 sums are ok"
    Dấu hiệu tải dở

    Kích thước tệp là bội số chẵn của 256 KiB. Đã gặp thật: cả 4 tệp của một bộ driver đều tải dở, và tất cả đều có kích thước tròn trịa như vậy.

  4. Vá driver — cần đúng công cụ

    Bản vá là diff nhị phân. patch phiên bản 2.8 (Debian 13) từ chối dữ liệu chứa byte NUL. Phải lấy bản 2.7.6:

    curl -fL http://deb.debian.org/debian/pool/main/p/patch/patch_2.7.6-7_amd64.deb -o p.deb
    dpkg-deb -x p.deb /root/p276 && mkdir -p /root/bin
    cp /root/p276/usr/bin/patch /root/bin/patch     # KHÔNG cài đè hệ thống
    
    PATH=/root/bin:$PATH ./driver.run --apply-patch <ver>.patch
    # → sinh ra driver-custom.run

    Bản vá lấy ở polloloco/vgpu-proxmox. Mỗi bản vá chỉ dùng cho đúng một phiên bản driver — sai phiên bản sẽ báo hunks FAILED.

  5. Cài driver đã vá
    ./driver-custom.run --dkms -m=kernel

    --dkms để driver tự biên dịch lại khi kernel cập nhật.

  6. Cài lớp mở khoá tầng người dùng
    cd /opt && git clone https://github.com/mbilker/vgpu_unlock-rs.git
    curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal
    source $HOME/.cargo/env && cd vgpu_unlock-rs && cargo build --release
    
    mkdir -p /etc/vgpu_unlock /etc/systemd/system/nvidia-vgpud.service.d \
             /etc/systemd/system/nvidia-vgpu-mgr.service.d
    touch /etc/vgpu_unlock/profile_override.toml
    for s in nvidia-vgpud nvidia-vgpu-mgr; do
      printf '[Service]\nEnvironment=LD_PRELOAD=/opt/vgpu_unlock-rs/target/release/libvgpu_unlock_rs.so\n' \
        > /etc/systemd/system/$s.service.d/vgpu_unlock.conf
    done
    systemctl daemon-reload && reboot     # reboot là BẮT BUỘC
  7. Kiểm tra và gán cho máy ảo
    ls /sys/bus/pci/devices/0000:<slot>/mdev_supported_types/   # phải ra danh sách
    qm set <VMID> --hostpci0 0000:<slot>,mdev=nvidia-257        # 257 = hồ sơ 2 GB

    Trong máy khách: cài driver -grid.run cùng bộ, rồi trỏ giấy phép về máy chủ DLS.

Có đường tắt

Từ 2026-08-29, bảng điều khiển Lotus Cloud (B43) làm hộ toàn bộ phần "trong máy khách" bằng một nút: tab Cài đặt của máy ảo → Cài driver vGPU vào máy khách. Nó cài mã nguồn nhân, tải đúng phiên bản driver khách khớp nhánh với máy chủ, cài, lấy token cấp phép rồi kiểm tra lại — mất 3–8 phút, có hiện tiến độ.

Lệnh đi qua QEMU Guest Agent chứ không phải SSH, nên không cần mật khẩu hay khoá của máy khách. Điều kiện: máy đang chạy, đã gắn vGPU, và đang chạy dịch vụ qemu-guest-agent bên trong.

Ba chỗ vấp khi tự làm tay, nay đã xử lý sẵn:

  • Debian 13 bỏ gói ảo linux-headers mà gói .deb của NVIDIA khai Depends. Header đúng phiên bản vẫn cài rồi nhưng apt không nối được ⇒ không sửa được bằng cài thêm gói, phải lùi sang bản .run.
  • Trình cài .run chết bằng SIGBUS vì giải nén vào /tmp, mà ảnh cloud để /tmp là tmpfs 2 GB trong RAM ⇒ --tmpdir=/var/tmp.
  • Ảnh cloud không kèm qemu-guest-agent; bật --agent enabled=1 mới chỉ tạo cổng phía máy chủ, chưa có gì ở đầu bên kia.

Cấp phép bằng FastAPI-DLS

Máy khách không có giấy phép sẽ bị NVIDIA giảm hiệu năng sau khoảng 20 phút. FastAPI-DLS là bản thay thế mã nguồn mở cho máy chủ cấp phép.

# Trên máy khách
mkdir -p /etc/nvidia/ClientConfigToken
curl -sk https://<dls>:<port>/-/client-token \
  -o /etc/nvidia/ClientConfigToken/client_configuration_token_$(date +%d-%m-%Y).tok
sed -i 's/^FeatureType=.*/FeatureType=1/' /etc/nvidia/gridd.conf
systemctl restart nvidia-gridd
nvidia-smi -q | grep -i "License Status"     # mong đợi: Licensed
Bẫy dễ dính

Địa chỉ khai trong biến DLS_URL được nhúng thẳng vào token. Khai IP nội bộ của Docker thì máy ảo không bao giờ với tới được. Phải khai địa chỉ mà máy ảo gọi được, và chứng chỉ phải liệt kê địa chỉ đó trong SAN.

Dành cho AI Agent dựng lại hệ thống này

Phần này viết cho tác nhân tự động. Thứ tự dưới đây được sắp xếp để thất bại sớm và rẻ — các phép kiểm tốn ít thời gian nhất nhưng loại bỏ nhiều khả năng sai nhất được đặt lên trước.

Quy tắc số một

Đo trước khi kết luận. Trong quá trình dựng hệ thống này, hai giả thuyết hợp lý đã bị thực nghiệm bác bỏ: (1) phải trừ VRAM dự trữ cho máy chủ — sai, không trừ gì; (2) bản vá lỗi vì sai phiên bản driver — sai, lỗi vì sai phiên bản công cụ patch. Cả hai đều sẽ dẫn tới kết luận sai nếu chỉ suy luận mà không chạy thử.

Giai đoạn 0 — Khảo sát (không thay đổi gì)

lspci -nn | grep -i -E "vga|3d"      # lấy mã card, vd 10de:2182
uname -r; pveversion                     # phiên bản kernel
cat /proc/cmdline | grep -o "iommu[^ ]*" # IOMMU đã bật chưa
dpkg -l | grep -E "proxmox-headers"  # có header để biên dịch không
qm list                                  # máy nào đang chạy (cần trước khi reboot)
pvecm status 2>/dev/null || echo standalone # có cluster không

Giai đoạn 1 — Ba phép kiểm quyết định

  1. Card có trong danh sách hỗ trợ không? Giải nén driver, tìm mã card trong vgpuConfig.xml. Không có ⇒ bắt buộc phải vá.
  2. Có bản vá cho ĐÚNG phiên bản driver này không? Đối chiếu với danh sách tệp .patch trong repo. Không có ⇒ dừng lại và đổi phiên bản driver, đừng cố vá bằng bản vá của phiên bản khác.
  3. Driver biên dịch được trên kernel này không? Chạy make modules như ở tab trước. Hỏng ⇒ hạ kernel trước, mọi việc khác vô nghĩa.

Giai đoạn 2 — Hạ kernel an toàn khi máy ở xa

Máy không có IPMI thì đây là bước rủi ro nhất

Trước khi khởi động lại, phải xác minh driver mạng của kernel mới có tồn tại. Mất driver mạng là mất máy, phải tới tận nơi.

apt-get install -y proxmox-kernel-6.14
# BẮT BUỘC kiểm: driver mạng hiện tại có trong kernel mới không
for i in $(ls /sys/class/net | grep -v lo); do
  drv=$(basename $(readlink /sys/class/net/$i/device/driver))
  find /lib/modules/<kernel-moi> -name "$drv.ko*" | head -1
done
# cũng kiểm: zfs.ko nếu dùng ZFS, và dkms status

Ghim kernel bằng GRUB_DEFAULT tường minh rồi xác minh trong grub.cfg trước khi reboot:

SUB=$(grep -m1 -o "gnulinux-advanced-[^']*" /boot/grub/grub.cfg)
ENT=$(grep -m1 -o "gnulinux-<ver>-advanced-[^']*" /boot/grub/grub.cfg)
sed -i "s|^GRUB_DEFAULT=.*|GRUB_DEFAULT=\"$SUB>$ENT\"|" /etc/default/grub
update-grub
grep -n "set default" /boot/grub/grub.cfg   # dòng THỨ HAI mới là thật
Đừng tin grub-reboot khi /boot nằm trên LVM

GRUB không ghi được vào grubenv trên LVM, nên cờ "khởi động một lần" không tự xoá — nó lặng lẽ trở thành mặc định vĩnh viễn. Dùng GRUB_DEFAULT tường minh, kiểm chứng được.

Giai đoạn 3 — Xác minh sau mỗi bước

Sau bướcLệnh kiểmMong đợi
Cài drivernvidia-smihiện tên card thật
Cài unlock + rebootls .../mdev_supported_types/ | wc -l> 0
Gán vGPUnvidia-smi vgpuliệt kê máy ảo
Driver máy kháchnvidia-smi trong máy ảotên hồ sơ + đúng VRAM
Cấp phépnvidia-smi -q | grep LicenseLicensed
Bền vữngreboot rồi kiểm lại tất cảmọi thứ tự lên lại

Bước cuối không được bỏ qua: một thiết lập không sống sót qua khởi động lại thì chưa xong.

Việc phải hỏi người dùng, không tự quyết

Nhận diện nhanh khi gặp lỗi

Triệu chứngNguyên nhân thật
patch line N contains NUL byteSai phiên bản công cụ patch, không phải sai driver
N out of N hunks FAILEDBản vá không khớp phiên bản driver
GPU not supported by vGPU sau khi đã váThiếu vgpu_unlock-rs, hoặc chưa reboot
Biên dịch lỗi in_irq, __vm_flagsKernel quá mới so với driver
Trình cài driver chết vì SIGBUS/tmp là tmpfs bị đầy — thêm --tmpdir
Máy ảo thứ N không bật đượcHết VRAM — đếm lại theo công thức thật
Máy khách bị giảm hiệu năng sau ~20 phútChưa cấp phép

Giấy phép: thứ quyết định card chạy nhanh hay chậm

vGPU của NVIDIA không khoá theo kiểu bật/tắt. Máy khách thiếu giấy phép vẫn khởi động, vẫn thấy card, vẫn dựng hình — trong khoảng 20 phút ân hạn. Hết hạn đó, driver tự hạ hiệu năng xuống mức gần như không dùng nổi và tắt CUDA. Đây là lý do một máy "chạy tốt lúc thử" lại chậm như bò vào hôm sau.

Trạng thái nvidia-smi -qNghĩa là gìHiệu năng
Licensed (Expiry: …)Đã xin được giấy phép, còn hạnđầy đủ
Unlicensed (Unrestricted)Chưa có giấy phép nhưng vẫn trong 20 phút ân hạnđầy đủ, tạm thời
Unlicensed (Restricted)Hết ân hạn. NVIDIA đã bóp hiệu năngbị hạ cấp
Hai chữ trong ngoặc đơn

Restricted và Unrestricted trông gần giống nhau và rất dễ đọc lướt, nhưng đó là ranh giới giữa "chưa sao" và "đã hỏng". Nhìn thấy Restricted nghĩa là phải đi sửa ngay, không phải chờ thêm.

Đường xin giấy phép — bốn chặng

Máy khách nói chuyện với máy chủ cấp phép tự dựng (FastAPI-DLS, container lotus_dls) theo đúng bốn bước. Biết chuỗi này thì nhìn nhật ký là biết hỏng ở đâu:

🔴 Thủ phạm khó ngờ nhất: đồng hồ UTC lệch

Máy khách Windows xin giấy phép mãi không xong, dừng ngay chặng 1. Mạng thông, DLS chạy, một máy khách khác trên cùng máy chủ DLS lại xin được bình thường.

Vì sao nó xảy ra

  1. Proxmox trình đồng hồ phần cứng cho máy ảo theo giờ địa phương của máy chủ (UTC+7), không phải UTC.
  2. Windows bên trong lại đang để múi giờ Pacific (UTC−7).
  3. Windows tính ngược ra UTC bằng cách trừ đi múi giờ của nó ⇒ UTC lệch 14 tiếng.
  4. JWT của DLS chỉ sống 900 giây. Lệch 14 tiếng thì mọi vé đều nằm ngoài cửa sổ hiệu lực — không bao giờ có ngoại lệ.
Vì sao cực khó phát hiện

Giờ hiển thị trên màn hình vẫn ĐÚNG. Đồng hồ góc phải chỉ đúng giờ Việt Nam, lịch đúng, không có gì bất thường để mà nghi. Chỉ có UTC — thứ không ai nhìn — là sai. Không một thông báo lỗi nào nhắc tới thời gian.

Cách kiểm và cách chữa

# So GIỜ UTC, không so giờ hiển thị — giờ hiển thị luôn đúng
[System.DateTime]::UtcNow
tzutil /g

# Chữa: đặt lại múi giờ rồi ép đồng bộ
tzutil /s "SE Asia Standard Time"
w32tm /resync /force
Cách chẩn đoán tổng quát

Có hai máy khách trên cùng một máy chủ, một chạy một không — thì nguyên nhân gần như chắc chắn nằm bên trong máy hỏng, không nằm ở máy chủ hay ở mạng. So hai máy theo từng thuộc tính cho tới khi tìm ra thứ khác nhau.

⚠ Driver GRID có thể treo máy khách

Trên card đã vgpu_unlock, driver khách 553.74 treo cứng Windows ngay lúc nạp — không màn hình xanh, không nhật ký, máy đơn giản là đứng. Cứu bằng cách tắt cứng rồi qm set <vmid> --delete hostpci0, khởi động lại là vào được để gỡ driver.

Trước khi nâng driver khách

Chụp snapshot hoặc sao lưu trước. Một bản driver không hợp với card đã mở khoá sẽ khoá bạn ra khỏi chính máy đó, và đường quay lại duy nhất là gỡ card ra.

Console đen sau khi driver GRID nạp — đúng thiết kế

Máy khách gắn vGPU dựng hình bằng driver NVIDIA GRID, nên bộ khung hình giả lập của QEMU không còn được vẽ vào nữa. Hệ quả:

Đường vào đúng là Remote Desktop. Bảng điều khiển B43 phát hiện khung hình đồng màu và hiện thẳng lời giải thích kèm địa chỉ RDP, thay vì để người dùng nhìn một ô đen và tưởng máy đã chết.

Đã thử và KHÔNG làm được

Chụp màn hình từ bên trong máy khách bằng GDI (CopyFromScreen) cũng không cứu được: chạy ở phiên 0 thì báo The handle is invalid; chạy đúng phiên người dùng qua schtasks /it thì ra một ảnh trắng đều. Muốn có ảnh thật phải dùng DXGI Desktop Duplication — một tác nhân riêng chạy trong máy khách.

Bản mẫu Windows: hai điều phải nhớ

Những cái bẫy đã trả giá

Mỗi mục dưới đây là một lỗi thật đã gặp trong quá trình dựng hệ thống này, kèm cách nhận ra nó.

1 · Tệp tải dở trông y như tệp tốt

Bốn tệp driver đều tải thiếu, nhưng chỉ lộ ra khi giải nén thất bại. Dấu hiệu: kích thước là bội số chẵn của 256 KiB. Tệp nguyên vẹn hầu như luôn có kích thước lẻ.

Phòng tránh: luôn chạy --check ngay sau khi tải, trước khi làm bất cứ việc gì khác.

2 · Sai công cụ, tưởng sai phiên bản

Cả hai phiên bản driver đều không vá được, thông báo giống nhau. Kết luận tự nhiên là "bản vá không khớp". Thực ra patch 2.8 của Debian 13 từ chối byte NUL — kể cả với bản vá hoàn toàn đúng phiên bản.

Cách phân biệt: contains NUL byte = lỗi công cụ. hunks FAILED = lỗi phiên bản.

3 · Kernel mới hơn không phải lúc nào cũng tốt hơn

Proxmox 9 chạy kernel 7.0. Driver NVIDIA mới nhất vẫn không biên dịch nổi trên đó — in_irq() đã bị xoá khỏi nhân, vm_area_struct.__vm_flags đổi tên. Đây là lỗi API, không vá bằng cờ biên dịch được.

4 · Vá driver xong vẫn bị từ chối

Sau khi vá, thông báo từ chối gần như y hệt — chỉ khác một trường phụ đổi từ 0x8841 sang 0x0000. Chi tiết nhỏ đó là bằng chứng bản vá đã có tác dụng; thứ còn thiếu là lớp vgpu_unlock-rs ở tầng người dùng, và một lần reboot.

5 · SIGBUS khi cài driver trong máy ảo

Ảnh đĩa đám mây đặt /tmp làm tmpfs 2 GB. Trình cài driver giải nén ~1,5 GB vào đó rồi chết vì SIGBUS — dấu hiệu kinh điển của ghi tràn tmpfs, không phải lỗi driver.

Cách chữa: --tmpdir=/var/tmp/nv.

6 · Địa chỉ máy chủ cấp phép bị nhúng vào token

Khai DLS_URL bằng IP nội bộ của Docker thì token tải về mang theo địa chỉ đó, và máy ảo ở mạng khác không bao giờ với tới. Sửa cấu hình xong phải tải lại token — token cũ vẫn giữ địa chỉ cũ.

7 · Con số của driver không phải sự thật

Sau khi mở khoá, driver nói gì cũng theo bảng thông số của card mà nó tưởng mình đang là. Bảng điều khiển hiển thị lại nguyên văn sẽ dẫn người dùng lên kế hoạch sai.

Nguyên tắc: con số nào suy ra từ dung lượng card thì phải tính lại; con số nào driver thật sự áp đặt lên máy khách (VRAM mỗi máy, độ phân giải, giới hạn fps) thì tin được.

8 · "Khởi động một lần" không phải lúc nào cũng một lần

grub-reboot ghi cờ vào grubenv. Khi /boot nằm trên LVM, GRUB không ghi lại được lúc khởi động nên cờ đó không bao giờ tự xoá — nó lặng lẽ trở thành mặc định vĩnh viễn. GRUB có cảnh báo, nhưng rất dễ lướt qua.

9 · hostpci0 thiếu chữ mdev=

Gán vGPU bằng chuỗi "0000:81:00.0,nvidia-256" bị Proxmox trả 400 Parameter verification failed — mà câu đó không nói sai ở đâu. Dạng đúng là "0000:81:00.0,mdev=nvidia-256".

Nơi giấu manh mối: tên khoá hỏng nằm ở trường errors của phản hồi, không nằm ở message. Đọc mỗi message là vứt đi đúng phần có ích.

10 · Bản mẫu ghim phiên bản máy ảo hoá của node cũ

Máy nhân bản từ bản mẫu kế thừa machine: pc-q35-9.0+pve1. Node khác không có phiên bản +pveN đó sẽ từ chối bật máy.

Nơi giấu manh mối: lời gọi API trả về "thành công" — lỗi chỉ nằm trong nhật ký tác vụ của Proxmox. Ai chỉ nhìn mã trả về sẽ thấy một máy "bật thành công" mà không bao giờ chạy.

11 · Giải mã base64 hai lần ra rác, không báo lỗi

agent/exec-status của Proxmox trả out-data đã giải mã sẵn, không còn lớp base64 của QEMU Guest Agent. Giải thêm lần nữa thì base64.b64decode của Python bỏ qua ký tự không hợp lệ và lặng lẽ trả về rác nhị phân — trông y như lỗi mã hoá ký tự, dẫn người sửa đi lạc rất xa.

Điểm chung của mười một cái bẫy

Gần như trường hợp nào thông báo lỗi cũng trỏ sai hướng — và ba cái tệ nhất (đồng hồ UTC, ghim +pveN, giải mã hai lần) thì không có thông báo lỗi nào cả. Cách thoát ra luôn giống nhau: dựng một phép thử nhỏ, cô lập, cho kết quả dứt khoát — rồi tin kết quả đó hơn tin thông báo lỗi.