Files
voice_linux/plans/gpu_passthrough_lessons_learned.md

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