import type { Metadata } from "next" import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" import { Link } from "@/i18n/navigation" import Image from "next/image" import { DocHeader } from "@/components/ui/doc-header" import { Callout } from "@/components/ui/callout" import { Prerequisites } from "@/components/ui/prerequisites" import { Steps } from "@/components/ui/steps" import { SwitchModeGraphic } from "@/components/ui/switch-mode-graphic" import CopyableCode from "@/components/CopyableCode" export async function generateMetadata({ params, }: { params: Promise<{ locale: string }> }): Promise { const { locale } = await params const t = await getTranslations({ locale, namespace: "docs.hardware.switchGpuMode.meta" }) return { title: t("title"), description: t("description"), } } type WhenRow = { situation?: string; situationRich?: string; use?: string; useRich?: string } type DirectionItem = string type RelatedItem = { label: string; href: string; tail?: string } export default async function SwitchGpuModePage({ params, }: { params: Promise<{ locale: string }> }) { const { locale } = await params setRequestLocale(locale) const t = await getTranslations({ locale, namespace: "docs.hardware.switchGpuMode" }) const messages = (await getMessages({ locale })) as unknown as { docs: { hardware: { switchGpuMode: { when: { rows: WhenRow[] } walkthrough: { direction: { items: DirectionItem[] } } related: { items: RelatedItem[] } } } } } const whenRows = messages.docs.hardware.switchGpuMode.when.rows const directionItems = messages.docs.hardware.switchGpuMode.walkthrough.direction.items const relatedItems = messages.docs.hardware.switchGpuMode.related.items const code = (chunks: React.ReactNode) => {chunks} const strong = (chunks: React.ReactNode) => {chunks} const em = (chunks: React.ReactNode) => {chunks} const vmLink = (chunks: React.ReactNode) => ( {chunks} ) const lxcLink = (chunks: React.ReactNode) => ( {chunks} ) return (
{t.rich("intro.body", { code, em, strong })}

{t("when.heading")}

{t.rich("when.intro", { strong })}

{whenRows.map((row, idx) => ( ))}
{t("when.headerSituation")} {t("when.headerUse")}
{row.situationRich ? t.rich(`when.rows.${idx}.situationRich`, { code }) : row.situation} {row.useRich ? t.rich(`when.rows.${idx}.useRich`, { vmLink, lxcLink, strong }) : row.use}
{t.rich("prereqs.assigned", { strong })} }, { label: <>{t.rich("prereqs.iommu", { strong, em })}, check: t("prereqs.iommuCheck"), }, { label: <>{t.rich("prereqs.reboot", { strong })} }, { label: <>{t.rich("prereqs.knowList", { strong })} }, ]} /> {t.rich("blocklist.body", { code, em })}

{t("running.heading")}

{t.rich("running.body", { strong })}

{t("running.imageAlt")}

{t("howRuns.heading")}

{t("howRuns.body")}

{`┌─────────────────────────────────────────────┐
│  PHASE 1 — Detect, select, plan             │
│  (nothing touched yet)                      │
└──────────────────┬──────────────────────────┘
                   ▼
  lspci detects every GPU + current driver
  (vfio-pci, nvidia, amdgpu, i915, …)
                   │
                   ▼
  User selects GPU(s) to switch
  (checklist; auto-selects if only one)
                   │
                   ▼
  Uniform current mode check
  ├─ All in VM mode    → target = LXC
  ├─ All in LXC mode   → target = VM
  └─ Mixed             → reject, reselect
                   │
                   ▼
  Validations
  ├─ SR-IOV VF / active PF?       → block
  ├─ Target = VM and blocked ID?  → block
  └─ IOMMU parameter present?     → warn if missing
                   │
                   ▼
  Find affected workloads
  ├─ LXC configs referencing the GPU
  └─ VM configs with hostpci for the GPU
      (precise BDF regex, no substring false-positives)
                   │
                   ▼
  Conflict policy per affected workload
  ┌──────────────────────────────────────┐
  │ Keep config, disable onboot          │
  │   └─ safest; workload stays defined  │
  │      but won't auto-start broken     │
  │ Remove GPU lines from config         │
  │   └─ clean; workload works without   │
  │      the GPU after the switch        │
  └──────────────────────────────────────┘
                   │
                   ▼
  If target = LXC (leaving VM mode):
  └─ Orphan audio cascade
     (offer to remove companion audio
      hostpci + clean vfio.conf if the
      audio ID isn't used by any other VM)
                   │
                   ▼
  Confirmation summary
  (target mode + affected workloads +
   host changes about to happen)
                   │
     ┌─────── Cancel   OR   Confirm ────┐
     ▼                                  ▼
 Exit, nothing       ┌──────────────────┴──────────────────┐
 was changed         │  PHASE 2 — Apply                    │
                     └──────────────────┬──────────────────┘
                                        ▼
                       Target = VM (bind to vfio-pci):
                       ├─ /etc/modprobe.d/vfio.conf
                       │    add vendor:device + disable_vga=1
                       ├─ /etc/modprobe.d/blacklist.conf
                       │    add type-specific blacklists
                       ├─ /etc/modules
                       │    add vfio-pci, vfio
                       ├─ NVIDIA: sanitize host stack
                       │    (disable udev rule, hard-blacklist)
                       └─ AMD: softdep vfio-pci

                       Target = LXC (back to native driver):
                       ├─ /etc/modprobe.d/vfio.conf
                       │    drop vendor:device IDs for this GPU
                       │    (delete line if now empty)
                       ├─ /etc/modprobe.d/blacklist.conf
                       │    drop type blacklists if no GPU of
                       │    that type remains in vfio.conf
                       ├─ /etc/modules
                       │    drop vfio-pci if no GPU in vfio.conf
                       └─ NVIDIA: restore host stack
                          (re-enable udev, drop hard-blacklist)
                                        │
                                        ▼
                       Apply workload conflict policy
                       (pct set onboot=0  OR  sed hostpci/dev
                        lines out of VM/LXC configs)
                                        │
                                        ▼
                       update-initramfs -u -k all
                       (only if host config actually changed)
                                        │
                                        ▼
                       Reboot prompt — required for the new
                       binding to take effect`}
      

{t("walkthrough.heading")}

{t.rich("walkthrough.detect.body", { code })}

{t("walkthrough.detect.imageAlt")}

{t.rich("walkthrough.pickGpu.body", { em })}

{t("walkthrough.pickGpu.tipBody")}

{t("walkthrough.direction.intro")}

    {directionItems.map((_, idx) => (
  • {t.rich(`walkthrough.direction.items.${idx}`, { strong, code })}
  • ))}

{t("walkthrough.direction.outro")}

{t.rich("walkthrough.conflict.body", { code })}

{t("walkthrough.conflict.headerPolicy")} {t("walkthrough.conflict.headerEffect")} {t("walkthrough.conflict.headerWhen")}
{t("walkthrough.conflict.keepPolicy")} {t.rich("walkthrough.conflict.keepEffect", { code })} {t("walkthrough.conflict.keepWhen")}
{t("walkthrough.conflict.removePolicy")} {t.rich("walkthrough.conflict.removeEffect", { code })} {t("walkthrough.conflict.removeWhen")}
{t("walkthrough.conflict.imageAlt")}

{t.rich("walkthrough.audio.body1", { code })}

{t.rich("walkthrough.audio.body2", { code })}

{t.rich("walkthrough.apply.body", { code })}

{t("walkthrough.reboot.body")}

{t("walkthrough.reboot.imageAlt")}

{t("manual.heading")}

{t.rich("manual.intro", { strong, code })}

.conf # Rebuild initramfs and reboot update-initramfs -u -k all reboot`} className="my-4" />

{t.rich("manual.lxcToVm", { strong })}

> /etc/modprobe.d/vfio.conf # Blacklist the native driver so vfio-pci can claim the card cat >> /etc/modprobe.d/blacklist.conf <<'EOF' blacklist nouveau blacklist nvidia blacklist nvidia_drm blacklist nvidia_modeset blacklist nvidia_uvm blacklist nvidiafb options nouveau modeset=0 EOF # Make sure vfio-pci loads at boot grep -q '^vfio-pci$' /etc/modules || echo 'vfio-pci' >> /etc/modules # Rebuild initramfs and reboot update-initramfs -u -k all reboot`} className="my-4" /> {t.rich("manual.oneVmBody", { code })}

{t("verification.heading")}

# Expected (LXC mode): "Kernel driver in use: nvidia" (or amdgpu, i915) # Expected (VM mode): "Kernel driver in use: vfio-pci" # LXC mode — is the host tool happy? nvidia-smi # if NVIDIA intel_gpu_top # if Intel iGPU # VM mode — ready to be claimed by a VM start lsmod | grep vfio`} className="my-4" />

{t("troubleshoot.heading")}

{t.rich("troubleshoot.stillVfioBody", { code })} {t.rich("troubleshoot.vmFailBody", { code, em })} \\.[0-7]([,[:space:]]|$)/d' \\ /etc/pve/qemu-server/.conf`} className="my-4" /> {t.rich("troubleshoot.smiFailBody", { code })} {t.rich("troubleshoot.logBody", { code })}

{t("related.heading")}

    {relatedItems.map((item) => (
  • {item.label} {item.tail}
  • ))}
) }