226 lines
11 KiB
Markdown
226 lines
11 KiB
Markdown
# GPU Passthrough in Qubes OS — Summary & Lessons Learned
|
|
|
|
## What We Did
|
|
|
|
Passed an NVIDIA GTX 1080 Ti (BDF `65:00.0` / `65:00.1`) through from Qubes dom0 to a StandaloneVM called `ai`, installed NVIDIA drivers and CUDA toolkit, and rebuilt whisper.cpp with CUDA support for GPU-accelerated speech-to-text.
|
|
|
|
## Environment
|
|
|
|
| Component | Detail |
|
|
|-----------|--------|
|
|
| Host OS | Qubes OS with Xen hypervisor |
|
|
| dom0 display GPU | AMD (stays in dom0) |
|
|
| Passthrough GPU | NVIDIA GeForce GTX 1080 Ti (Pascal/GP102, 11GB VRAM, compute 6.1) |
|
|
| Second NVIDIA GPU | RTX 3050 6GB at BDF `17:00.0` (not used for this project) |
|
|
| Target VM | `ai` — originally an AppVM, converted to StandaloneVM |
|
|
| VM OS | Debian 13 (bookworm/trixie) with XFCE |
|
|
| NVIDIA driver | 550.127.05 |
|
|
| CUDA toolkit | 12.6 |
|
|
|
|
## Timeline of Steps
|
|
|
|
### Phase 0: dom0 GPU Passthrough
|
|
|
|
1. **Identified GPU BDF addresses** — `lspci` in dom0 showed two NVIDIA GPUs. We targeted the GTX 1080 Ti at `65:00.0` (GPU) and `65:00.1` (HDMI audio controller).
|
|
|
|
2. **Confirmed boot loader** — Verified Qubes was using GRUB (not systemd-boot) by checking `/etc/default/grub` existed.
|
|
|
|
3. **Hid GPU from dom0** — Added `rd.qubes.hide_pci=65:00.0,65:00.1` to `GRUB_CMDLINE_LINUX` in `/etc/default/grub`.
|
|
|
|
4. **Regenerated GRUB config** — `sudo grub2-mkconfig -o /boot/efi/EFI/qubes/grub.cfg` (EFI path, not legacy BIOS path).
|
|
|
|
5. **Rebooted dom0** — Full system reboot required for PCI hide to take effect.
|
|
|
|
6. **Verified GPU was assignable** — `xl pci-assignable-list` showed `0000:65:00.0` and `0000:65:00.1`.
|
|
|
|
7. **Attached GPU to VM** — `qvm-pci attach ai dom0:65_00.0 --persistent -o permissive=true` (and same for `65_00.1`).
|
|
|
|
### Phase 1: Driver & CUDA Installation (inside ai VM)
|
|
|
|
8. **Installed build prerequisites** — `build-essential`, `linux-headers-$(uname -r)`.
|
|
|
|
9. **Installed NVIDIA driver** — Downloaded `.run` installer, ran with `--no-opengl-files --dkms`.
|
|
|
|
10. **Installed CUDA toolkit** — Via NVIDIA's Debian repo + `cuda-toolkit-12-6`.
|
|
|
|
11. **Verified** — `nvidia-smi` showed GTX 1080 Ti, `nvcc --version` showed CUDA 12.6.
|
|
|
|
### Phase 2: Application Rebuild
|
|
|
|
12. **Rebuilt whisper.cpp with CUDA** — `WHISPER_CUDA=1 bash ./build.sh`.
|
|
|
|
13. **Verified CUDA linkage** — `ldd ./voice_linux | grep whisper|ggml` confirmed CUDA libraries linked.
|
|
|
|
14. **Tested GPU inference** — Ran with GPU mode, confirmed `nvidia-smi` showed process using GPU memory.
|
|
|
|
## Problems Encountered & Solutions
|
|
|
|
### Problem 1: AppVM vs StandaloneVM confusion
|
|
|
|
**What happened**: The `ai` VM was initially described as an AppVM (template-based). In an AppVM, anything installed outside `/home` is lost on reboot — NVIDIA drivers would vanish.
|
|
|
|
**Discovery**: User clarified it was actually a StandaloneVM, not template-based.
|
|
|
|
**Lesson**: Always verify VM type before planning driver installation. In Qubes:
|
|
- **AppVM**: Root filesystem resets to template on reboot. Drivers must go in the template or use bind-dirs.
|
|
- **StandaloneVM**: Full persistent root filesystem. Drivers persist normally.
|
|
- Check with: `qvm-prefs ai virt_mode` and `qvm-ls --fields name,klass ai`
|
|
|
|
### Problem 2: VM must be shut down before PCI attach
|
|
|
|
**What happened**: Attempted to run `qvm-pci attach` while the `ai` VM was running. The attach appeared to succeed but the device wasn't visible inside the VM.
|
|
|
|
**Discovery**: `qvm-device pci list ai` showed empty even though attach command ran.
|
|
|
|
**Lesson**: The VM must be **shut down** before attaching PCI devices with `--persistent`. The workflow is:
|
|
1. Shut down VM
|
|
2. Attach PCI device
|
|
3. Start VM
|
|
4. Verify inside VM with `lspci`
|
|
|
|
### Problem 3: "Already assigned" error on re-attach
|
|
|
|
**What happened**: After a reboot cycle, running the attach command again produced an "already assigned" message.
|
|
|
|
**Lesson**: `--persistent` means the attachment survives reboots. Don't re-run the attach command after reboot — it's already configured. Verify with `qvm-pci list ai` from dom0.
|
|
|
|
### Problem 4: lspci not found inside VM
|
|
|
|
**What happened**: Tried to verify GPU visibility inside the VM but `lspci` wasn't installed.
|
|
|
|
**Solution**: `sudo apt install pciutils` then `lspci | grep -i nvidia`.
|
|
|
|
**Lesson**: Minimal Debian VMs may not have `pciutils` installed. Include it in prerequisites.
|
|
|
|
### Problem 5: Script couldn't be copy-pasted into dom0
|
|
|
|
**What happened**: Created a comprehensive dom0 shell script for GPU passthrough, but Qubes security model prevents clipboard paste from other VMs into dom0.
|
|
|
|
**Solution**: Provided numbered step-by-step commands that could be typed manually, and also created the script as a file that could be transferred via `qvm-run` or Qubes file copy.
|
|
|
|
**Lesson**: When automating dom0 tasks in Qubes:
|
|
- Dom0 is intentionally isolated — no clipboard sharing
|
|
- Scripts must be typed manually or transferred via `qvm-copy-to-vm` (from dom0 to VM) or `qvm-run -p` pipes
|
|
- Keep dom0 scripts short and simple
|
|
- Always include a revert mechanism
|
|
|
|
### Problem 6: Reboot scope confusion
|
|
|
|
**What happened**: Unclear whether "reboot" meant just the VM or the entire Qubes system (dom0 + all VMs).
|
|
|
|
**Clarification**: GRUB changes require a **full system reboot** (dom0 reboot, which takes down all VMs). PCI attachment changes only require the target VM to be restarted.
|
|
|
|
**Lesson**: Be explicit about reboot scope:
|
|
- `sudo reboot` in dom0 = full system reboot
|
|
- `qvm-shutdown ai && qvm-start ai` = just the VM
|
|
|
|
### Problem 7: Build accidentally ran without whisper support
|
|
|
|
**What happened**: After some iteration, a build was accidentally triggered with `WITH_WHISPER=0`, producing a binary that showed "[transcription unavailable: rebuild with WITH_WHISPER=1]".
|
|
|
|
**Solution**: Rebuilt with `WITH_WHISPER=1 bash ./build.sh`.
|
|
|
|
**Lesson**: The build system defaults matter. Our `build.sh` auto-detects whisper if the vendor directory exists, but explicit `WITH_WHISPER=0` overrides that. Always verify the build output includes whisper support with `ldd ./voice_linux | grep whisper`.
|
|
|
|
### Problem 8: Dirty git worktree blocking version increment
|
|
|
|
**What happened**: The `increment_and_push.sh` script refused to run because of uncommitted changes in the working tree.
|
|
|
|
**Solution**: Committed the intended changes first, then ran the increment script.
|
|
|
|
**Lesson**: The increment script enforces a clean worktree policy. Always commit your changes before running it. If there are unrelated/untracked files, `git stash` them first.
|
|
|
|
## Key Qubes-Specific Knowledge
|
|
|
|
### PCI Passthrough Essentials
|
|
|
|
```
|
|
# Hide device from dom0 (GRUB, requires full reboot):
|
|
rd.qubes.hide_pci=BDF1,BDF2
|
|
|
|
# Attach to VM (VM must be off):
|
|
qvm-pci attach VMNAME dom0:BDF --persistent -o permissive=true
|
|
|
|
# Verify assignment:
|
|
qvm-pci list VMNAME # from dom0
|
|
lspci | grep -i nvidia # from inside VM
|
|
```
|
|
|
|
### NVIDIA Driver in Qubes VM
|
|
|
|
- **Always use `--no-opengl-files`** — Qubes VMs use a virtual GPU for display. Installing OpenGL files would break the display.
|
|
- **Use `--dkms`** — Ensures kernel module rebuilds on kernel updates.
|
|
- **StandaloneVM recommended** — Avoids template pollution and persistence issues.
|
|
- **`permissive=true`** — Required for NVIDIA GPUs in Qubes due to how they access PCI config space.
|
|
|
|
### What Persists Where
|
|
|
|
| VM Type | /home | /usr, /lib, /etc | Drivers |
|
|
|---------|-------|-------------------|---------|
|
|
| AppVM | ✅ Persists | ❌ Resets to template | ❌ Lost on reboot |
|
|
| StandaloneVM | ✅ Persists | ✅ Persists | ✅ Persists |
|
|
| Template | ✅ Persists | ✅ Persists | ✅ Persists (shared to AppVMs) |
|
|
|
|
### Dom0 Safety
|
|
|
|
- Dom0 has no network access by design
|
|
- Clipboard is one-way (dom0 → VM, not VM → dom0) and requires explicit Ctrl+Shift+C/V
|
|
- Always have a revert plan for GRUB changes (keep a backup of `/etc/default/grub`)
|
|
- Log what you change — our script wrote to `/var/log/gpu_passthrough.log`
|
|
|
|
## Architecture Diagram
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ dom0 (Xen Hypervisor) │
|
|
│ │
|
|
│ AMD GPU ──── Display │
|
|
│ GTX 1080 Ti ──── HIDDEN via rd.qubes.hide_pci │
|
|
│ │ │
|
|
│ │ PCI passthrough (qvm-pci attach --persistent) │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ ai StandaloneVM │ │
|
|
│ │ │ │
|
|
│ │ NVIDIA Driver 550.x (--no-opengl-files --dkms) │ │
|
|
│ │ │ │ │
|
|
│ │ CUDA Toolkit 12.6 │ │
|
|
│ │ │ │ │
|
|
│ │ whisper.cpp (GGML_CUDA=ON) │ │
|
|
│ │ │ │ │
|
|
│ │ voice_linux (GPU-accelerated transcription) │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Performance Results
|
|
|
|
| Model | VRAM Used | Decode Time (10s audio) | Quality |
|
|
|-------|-----------|------------------------|---------|
|
|
| tiny.en | ~1GB | <0.5s | Fair |
|
|
| base.en | ~1GB | <1s | Good |
|
|
| small.en | ~2GB | ~1-2s | Very good |
|
|
| medium.en | ~5GB | ~2-3s | Excellent |
|
|
| large-v3-turbo | ~6GB | ~3-4s | Excellent+ |
|
|
| large-v3 | ~10GB | ~4-6s | Best |
|
|
|
|
The GTX 1080 Ti with 11GB VRAM can run all models including large-v3. For real-time dictation, medium.en provides the best accuracy-to-speed tradeoff.
|
|
|
|
## Files Created During This Process
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| [`plans/gpu_enablement_plan.md`](gpu_enablement_plan.md) | Step-by-step GPU passthrough and driver installation plan |
|
|
| [`plans/architecture.md`](architecture.md) | Full system architecture including hardware details |
|
|
| [`scripts/download_models.sh`](../scripts/download_models.sh) | Downloads all compatible whisper models |
|
|
|
|
## If You Had to Do It Again
|
|
|
|
1. **Verify VM type first** — `qvm-ls --fields name,klass ai` before planning anything
|
|
2. **Shut down VM before PCI operations** — always
|
|
3. **Back up GRUB config** — `sudo cp /etc/default/grub /etc/default/grub.bak` before editing
|
|
4. **Install pciutils early** — `sudo apt install pciutils` so you can verify GPU visibility
|
|
5. **Use the .run installer, not apt packages** — NVIDIA's apt packages for Debian can conflict with Qubes' virtual GPU setup; the `.run` installer with `--no-opengl-files` is cleaner
|
|
6. **Test CPU mode first** — Build and verify voice_linux works on CPU before adding GPU complexity
|
|
7. **Keep dom0 commands minimal** — Type them manually, don't try to automate complex scripts in dom0
|